tests: fix rmrf helper to avoid too many open files on deep trees

The previous chdir()-based implementation held an opendir(".") DIR
handle open at every recursion level. With deeply nested trees (e.g.,
270 levels in darwintrace_readdir_at_maxpathlen), this exceeded the
default file descriptor limit (EMFILE).

Collect directory entry names into a list and close the DIR handle
before recursing, so at most one file descriptor is open at any
recursion depth.

See: https://trac.macports.org/ticket/73697
This commit is contained in:
Herby Gillot
2026-03-21 16:46:26 -04:00
parent 90eefc6648
commit 2424fd25f4
+35 -5
View File
@@ -11,8 +11,10 @@
* Recursively remove a file or directory. Uses chdir() to descend into
* directories so that all syscalls operate on short, relative names.
* This avoids ENAMETOOLONG on deeply nested trees that exceed PATH_MAX.
* Uses chdir("..") to return rather than fchdir() to avoid accumulating
* open file descriptors during deep recursion.
*
* Collects directory entries before closing the DIR handle, then recurses.
* This keeps at most one file descriptor open at any recursion depth,
* avoiding EMFILE ("Too many open files") on deep trees.
*/
static int rmrf(const char *path) {
struct stat st;
@@ -37,7 +39,12 @@ static int rmrf(const char *path) {
return -1;
}
/* Collect entry names first so we can close the DIR before recursing. */
char **names = NULL;
size_t count = 0;
size_t capacity = 0;
int ret = 0;
for (;;) {
errno = 0;
struct dirent *entry = readdir(dir);
@@ -52,20 +59,43 @@ static int rmrf(const char *path) {
continue;
}
if (rmrf(entry->d_name) != 0) {
if (count == capacity) {
size_t new_cap = capacity == 0 ? 8 : capacity * 2;
char **tmp = realloc(names, new_cap * sizeof(*names));
if (tmp == NULL) {
ret = -1;
break;
}
names = tmp;
capacity = new_cap;
}
names[count] = strdup(entry->d_name);
if (names[count] == NULL) {
ret = -1;
break;
}
count++;
}
int saved_errno = errno;
closedir(dir);
/* Now recurse over collected entries with the DIR closed. */
for (size_t i = 0; i < count && ret == 0; i++) {
if (rmrf(names[i]) != 0) {
ret = -1;
}
}
for (size_t i = 0; i < count; i++) {
free(names[i]);
}
free(names);
/* Return to the parent directory before rmdir. */
chdir("..");
if (ret != 0) {
errno = saved_errno;
return ret;
}