]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: unlink_directory_r() is refactored.
authorSergey Kitov <sergey.kitov@open-xchange.com>
Wed, 20 Sep 2017 07:45:26 +0000 (10:45 +0300)
committerAki Tuomi <aki.tuomi@dovecot.fi>
Wed, 27 Sep 2017 07:47:04 +0000 (10:47 +0300)
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().

src/lib/unlink-directory.c

index 6965a266617a49aa14199b7dfad249bbff5f58f9..1930a1f1ae9181f9e521be127f677fc6b0ebf979 100644 (file)
 #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;
@@ -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;