#include <dirent.h>
#include <sys/stat.h>
+#define ERROR_FORMAT "%s(%s) failed: %m"
+#define ERROR_FORMAT_DNAME "%s(%s/%s) failed: %m"
+
+static void ATTR_FORMAT(3,4)
+unlink_directory_error(const char **error,
+ int *first_errno,
+ const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ const char *err = t_strdup_vprintf(fmt, args);
+ if (*error == NULL) {
+ if (first_errno != NULL)
+ *first_errno = errno;
+ *error = err;
+ } else
+ i_error("%s", err);
+ va_end(args);
+}
+
static int
unlink_directory_r(const char *dir, enum unlink_directory_flags flags,
- const char **error_r)
+ const char **error)
{
DIR *dirp;
struct dirent *d;
#ifdef O_NOFOLLOW
dir_fd = open(dir, O_RDONLY | O_NOFOLLOW);
if (dir_fd == -1) {
- *error_r = t_strdup_printf(
- "open(%s, O_RDONLY | O_NOFOLLOW) failed: %m", dir);
+ unlink_directory_error(error, NULL,
+ "open(%s, O_RDONLY | O_NOFOLLOW) failed: %m",
+ dir);
return -1;
}
#else
struct stat st2;
if (lstat(dir, &st) < 0) {
- *error_r = t_strdup_printf("lstat(%s) failed: %m", dir);
+ unlink_directory_error(error_r, NULL, ERROR_FORMAT, "lstat", dir);
return -1;
}
if (!S_ISDIR(st.st_mode)) {
if ((st.st_mode & S_IFMT) != S_IFLNK) {
- *error_r = t_strdup_printf("%s is not a directory: %s", dir);
+ unlink_directory_error(error_r, NULL, "%s is not a directory: %s", dir);
errno = ENOTDIR;
} else {
/* be compatible with O_NOFOLLOW */
errno = ELOOP;
- *error_r = t_strdup_printf("%s is a symlink, not a directory: %s", dir);
+ unlink_directory_error(error_r, NULL, "%s is a symlink, not a directory: %s", dir);
}
return -1;
}
dir_fd = open(dir, O_RDONLY);
if (dir_fd == -1) {
- *error_r = t_strdup_printf("open(%s, O_RDONLY) failed: %m", dir);
+ unlink_directory_error(error_r, NULL, "open(%s, O_RDONLY) failed: %m", dir);
return -1;
}
if (fstat(dir_fd, &st2) < 0) {
i_close_fd(&dir_fd);
- *error_r = t_strdup_printf("fstat(%s) failed: %m", dir);
+ unlink_directory_error(error_r, NULL, ERROR_FORMAT, "fstat", dir);
return -1;
}
/* directory was just replaced with something else. */
i_close_fd(&dir_fd);
errno = ENOTDIR;
- *error_r = t_strdup_printf(
- "%s race condition: directory was just replaced", dir);
+ unlink_directory_error(error_r, NULL, "%s race condition: directory was just replaced", dir);
return -1;
}
#endif
if (fchdir(dir_fd) < 0) {
i_close_fd(&dir_fd);
- *error_r = t_strdup_printf("fchdir(%s) failed: %m", dir);
+ unlink_directory_error(error, NULL, ERROR_FORMAT, "fchdir", dir);
return -1;
}
dirp = opendir(".");
if (dirp == NULL) {
i_close_fd(&dir_fd);
- *error_r = t_strdup_printf("opendir(.) (in %s) failed: %m", dir);
+ unlink_directory_error(error, NULL, "opendir(.) (in %s) failed: %m", dir);
return -1;
}
- errno = 0;
- while ((d = readdir(dirp)) != NULL) {
+ int first_errno = 0;
+ for (;;) {
+ errno = 0;
+ d = readdir(dirp);
+ if (d == NULL) {
+ if (errno != 0) {
+ unlink_directory_error(error,
+ &first_errno,
+ ERROR_FORMAT,
+ "readdir",
+ dir);
+ }
+ break;
+ }
if (d->d_name[0] == '.') {
if ((d->d_name[1] == '\0' ||
(d->d_name[1] == '.' && d->d_name[2] == '\0'))) {
old_errno = errno;
if (lstat(d->d_name, &st) < 0) {
- if (errno != ENOENT)
+ if (errno != ENOENT) {
+ unlink_directory_error(error,
+ &first_errno,
+ ERROR_FORMAT,
+ "lstat",
+ dir);
break;
- errno = 0;
+ }
} else if (S_ISDIR(st.st_mode) &&
(flags & UNLINK_DIRECTORY_FLAG_FILES_ONLY) == 0) {
- if (unlink_directory_r(d->d_name, flags, error_r) < 0) {
+ if (unlink_directory_r(d->d_name, flags, error) < 0) {
+ if (first_errno == 0)
+ first_errno = errno;
if (errno != ENOENT)
break;
- errno = 0;
}
- if (fchdir(dir_fd) < 0)
+ if (fchdir(dir_fd) < 0) {
+ unlink_directory_error(error,
+ &first_errno,
+ ERROR_FORMAT,
+ "fchdir",
+ dir);
break;
+ }
- if (rmdir(d->d_name) < 0) {
- if (errno != ENOENT) {
- if (errno == EEXIST) {
- /* standardize errno */
- errno = ENOTEMPTY;
- }
- break;
- }
- errno = 0;
+ if (rmdir(d->d_name) < 0 &&
+ errno != ENOENT) {
+ if (errno == EEXIST)
+ /* standardize errno */
+ errno = ENOTEMPTY;
+ unlink_directory_error(error,
+ &first_errno,
+ ERROR_FORMAT,
+ "rmdir",
+ dir);
+ break;
}
} else if (S_ISDIR(st.st_mode) &&
(flags & UNLINK_DIRECTORY_FLAG_FILES_ONLY) != 0) {
in use. let the caller decide if this error
is worth logging about */
break;
- } else {
+ } else
/* so it wasn't a directory */
- errno = old_errno;
- i_error("unlink(%s/%s) failed: %m",
- dir, d->d_name);
- break;
- }
+ unlink_directory_error(error,
+ &first_errno,
+ ERROR_FORMAT_DNAME,
+ "unlink",
+ dir,
+ d->d_name);
}
}
- old_errno = errno;
i_close_fd(&dir_fd);
- if (closedir(dirp) < 0) {
- *error_r = t_strdup_printf("closedir(%s) failed: %m", dir);
- return -1;
- }
+ if (closedir(dirp) < 0)
+ unlink_directory_error(error,
+ &first_errno,
+ ERROR_FORMAT,
+ "closedir",
+ dir);
- if (old_errno != 0) {
- errno = old_errno;
- *error_r = t_strdup_printf("readdir(%s) failed: %m", dir);
+ if (*error != NULL) {
+ errno = first_errno;
return -1;
}
-
return 0;
}
return -1;
}
+ /* Cannot set error_r to NULL inside of unlink_directory_r()
+ because of recursion */
+ *error_r = NULL;
ret = unlink_directory_r(dir, flags, error_r);
if (ret < 0 && errno == ENOENT)
ret = 0;