From: Sergey Kitov Date: Wed, 20 Sep 2017 07:45:26 +0000 (+0300) Subject: lib: unlink_directory_r() is refactored. X-Git-Tag: 2.3.0.rc1~960 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=69458644d03fbb37ffdcbd6d7e1d09b230e2ee72;p=thirdparty%2Fdovecot%2Fcore.git lib: unlink_directory_r() is refactored. unlinking doesn't stop at unlink() errors, first error is written to error_r, consequent errors are logged via i_error errno set to first happened error. unlink_directory_r() is expecting *error_r to be set to NULL. errno is set to 0 before each call to readdir(). --- diff --git a/src/lib/unlink-directory.c b/src/lib/unlink-directory.c index 6965a26661..1930a1f1ae 100644 --- a/src/lib/unlink-directory.c +++ b/src/lib/unlink-directory.c @@ -41,9 +41,29 @@ #include #include +#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; @@ -53,39 +73,40 @@ unlink_directory_r(const char *dir, enum unlink_directory_flags flags, #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; } @@ -94,26 +115,37 @@ unlink_directory_r(const char *dir, enum unlink_directory_flags flags, /* 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'))) { @@ -128,28 +160,42 @@ unlink_directory_r(const char *dir, enum unlink_directory_flags flags, 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) { @@ -160,29 +206,29 @@ unlink_directory_r(const char *dir, enum unlink_directory_flags flags, 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; } @@ -206,6 +252,9 @@ int unlink_directory(const char *dir, enum unlink_directory_flags flags, 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;