tests: fix rmrf helper to handle paths exceeding PATH_MAX

The previous implementation built full paths by concatenation, which
failed with ENAMETOOLONG on macOS (PATH_MAX=1024) when cleaning up
deeply nested directories like those in darwintrace_readdir_at_maxpathlen.

Use chdir()/fchdir() to descend into directories instead, keeping all
syscall arguments to single-component relative names. All functions
used are standard POSIX available on OS X 10.5+.

This likely went unnoticed in CI because GitHub Actions runs Linux where
PATH_MAX is 4096.
This commit is contained in:
Herby Gillot
2026-03-21 07:23:04 -04:00
parent 3cc6816f84
commit f75f634ec1
+54 -46
View File
@@ -7,6 +7,13 @@
#include <sys/stat.h>
#include <unistd.h>
/*
* 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.
*/
static int rmrf(const char *path) {
struct stat st;
@@ -14,54 +21,55 @@ static int rmrf(const char *path) {
return errno == ENOENT ? 0 : -1;
}
if (S_ISDIR(st.st_mode)) {
DIR *dir = opendir(path);
if (dir == NULL) {
return -1;
}
for (;;) {
errno = 0;
struct dirent *entry = readdir(dir);
if (entry == NULL) {
int saved_errno = errno;
closedir(dir);
if (saved_errno != 0) {
errno = saved_errno;
return -1;
}
break;
}
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
size_t child_len = strlen(path) + 1 + strlen(entry->d_name) + 1;
char *child = malloc(child_len);
if (child == NULL) {
int saved_errno = errno;
closedir(dir);
errno = saved_errno;
return -1;
}
snprintf(child, child_len, "%s/%s", path, entry->d_name);
int rc = rmrf(child);
int saved_errno = errno;
free(child);
if (rc != 0) {
closedir(dir);
errno = saved_errno;
return -1;
}
}
return rmdir(path);
if (!S_ISDIR(st.st_mode)) {
return unlink(path);
}
return unlink(path);
if (chdir(path) != 0) {
return -1;
}
DIR *dir = opendir(".");
if (dir == NULL) {
int saved_errno = errno;
chdir("..");
errno = saved_errno;
return -1;
}
int ret = 0;
for (;;) {
errno = 0;
struct dirent *entry = readdir(dir);
if (entry == NULL) {
if (errno != 0) {
ret = -1;
}
break;
}
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
if (rmrf(entry->d_name) != 0) {
ret = -1;
break;
}
}
int saved_errno = errno;
closedir(dir);
/* Return to the parent directory before rmdir. */
chdir("..");
if (ret != 0) {
errno = saved_errno;
return ret;
}
return rmdir(path);
}
int main(int argc, char *argv[]) {