]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: file_create_locked() - Add settings to mkdir() missing parent directories
authorTimo Sirainen <timo.sirainen@dovecot.fi>
Wed, 28 Jun 2017 14:48:01 +0000 (17:48 +0300)
committerTimo Sirainen <timo.sirainen@dovecot.fi>
Mon, 3 Jul 2017 12:14:08 +0000 (15:14 +0300)
src/lib/file-create-locked.c
src/lib/file-create-locked.h
src/lib/test-file-create-locked.c

index 57ee123ba8b2147bda9440a31dcd7ff0f8f4debb..2ac30c1658f2fd17b9b61dc54012238e42fb3d08 100644 (file)
@@ -3,6 +3,7 @@
 #include "lib.h"
 #include "str.h"
 #include "safe-mkstemp.h"
+#include "mkdir-parents.h"
 #include "file-lock.h"
 #include "file-create-locked.h"
 
 #include <fcntl.h>
 #include <sys/stat.h>
 
+/* Try mkdir() + lock creation multiple times. This allows the lock file
+   creation to work even while the directory is simultaneously being
+   rmdir()ed. */
+#define MAX_MKDIR_COUNT 10
 #define MAX_RETRY_COUNT 1000
 
 static int
@@ -43,25 +48,62 @@ try_lock_existing(int fd, const char *path,
        return ret;
 }
 
+static int
+try_mkdir(const char *path, const struct file_create_settings *set,
+         const char **error_r)
+{
+       uid_t uid = set->mkdir_uid != 0 ? set->mkdir_uid : (uid_t)-1;
+       gid_t gid = set->mkdir_gid != 0 ? set->mkdir_gid : (gid_t)-1;
+       const char *p = strrchr(path, '/');
+       if (p == NULL)
+               return 0;
+
+       const char *dir = t_strdup_until(path, p);
+       int ret;
+       if (uid != (uid_t)-1)
+               ret = mkdir_parents_chown(dir, set->mkdir_mode, uid, gid);
+       else {
+               ret = mkdir_parents_chgrp(dir, set->mkdir_mode,
+                                         gid, set->gid_origin);
+       }
+       if (ret < 0) {
+               *error_r = t_strdup_printf("mkdir_parents(%s) failed: %m", dir);
+               return -1;
+       }
+       return 1;
+}
+
 static int
 try_create_new(const char *path, const struct file_create_settings *set,
               int *fd_r, struct file_lock **lock_r,  const char **error_r)
 {
        string_t *temp_path = t_str_new(128);
-       int fd, orig_errno, ret = -1;
+       int fd, orig_errno, ret = 1;
        int mode = set->mode != 0 ? set->mode : 0600;
        uid_t uid = set->uid != 0 ? set->uid : (uid_t)-1;
        uid_t gid = set->gid != 0 ? set->gid : (gid_t)-1;
 
        str_append(temp_path, path);
-       if (uid != (uid_t)-1)
-               fd = safe_mkstemp(temp_path, mode, uid, gid);
-       else
-               fd = safe_mkstemp_group(temp_path, mode, gid, set->gid_origin);
+       for (unsigned int i = 0; ret > 0; i++) {
+               if (uid != (uid_t)-1)
+                       fd = safe_mkstemp(temp_path, mode, uid, gid);
+               else
+                       fd = safe_mkstemp_group(temp_path, mode, gid, set->gid_origin);
+               if (fd != -1 || errno != ENOENT || set->mkdir_mode == 0 ||
+                   i >= MAX_MKDIR_COUNT)
+                       break;
+
+               int orig_errno = errno;
+               if ((ret = try_mkdir(path, set, error_r)) < 0)
+                       return -1;
+               errno = orig_errno;
+       }
        if (fd == -1) {
                *error_r = t_strdup_printf("safe_mkstemp(%s) failed: %m", path);
                return -1;
        }
+
+       ret = -1;
        if (file_try_lock_error(fd, str_c(temp_path), F_WRLCK,
                                set->lock_method, lock_r, error_r) <= 0) {
        } else if (link(str_c(temp_path), path) < 0) {
index 29bbb8144b01d916cb6c9375a19d23db36c741bb..cba44767074cc3402594c87ac90ff410611f7a16 100644 (file)
@@ -15,6 +15,17 @@ struct file_create_settings {
        /* 0 = default */
        gid_t gid;
        const char *gid_origin;
+
+       /* If parent directory doesn't exist, mkdir() it with this mode.
+          0 = don't mkdir(). The parent directories are assumed to be
+          potentially rmdir() simultaneously, so the mkdir()+locking may be
+          attempted multiple times. */
+       int mkdir_mode;
+       /* 0 = default */
+       uid_t mkdir_uid;
+       /* 0 = default */
+       gid_t mkdir_gid;
+       const char *mkdir_gid_origin;
 };
 
 /* Either open an existing file and lock it, or create the file locked.
index d60445891590681730cec60a471400a219116a94..6cd5f88fe42f58a2f6f0b6c4937725785d0dce29 100644 (file)
@@ -1,6 +1,7 @@
 /* Copyright (c) 2017 Dovecot authors, see the included COPYING file */
 
 #include "test-lib.h"
+#include "unlink-directory.h"
 #include "file-create-locked.h"
 
 #include <fcntl.h>
@@ -85,7 +86,51 @@ static void test_file_create_locked_basic(void)
        test_end();
 }
 
+static void test_file_create_locked_mkdir(void)
+{
+       struct file_create_settings set = {
+               .lock_timeout_secs = 0,
+               .lock_method = FILE_LOCK_METHOD_FCNTL,
+       };
+       const char *path = ".test-file-create-locked";
+       struct file_lock *lock;
+       const char *error, *dir;
+       bool created;
+       int fd;
+
+       test_begin("file_create_locked() with mkdir");
+
+       dir = ".test-temp-file-create-locked-dir";
+       if (unlink_directory(dir, UNLINK_DIRECTORY_FLAG_RMDIR) < 0)
+               i_fatal("unlink_directory(%s) failed: %m", dir);
+       path = t_strconcat(dir, "/lockfile", NULL);
+
+       /* try without mkdir enabled */
+       test_assert(file_create_locked(path, &set, &lock, &created, &error) == -1);
+       test_assert(errno == ENOENT);
+
+       /* try with mkdir enabled */
+       set.mkdir_mode = 0700;
+       fd = file_create_locked(path, &set, &lock, &created, &error);
+       test_assert(fd > 0);
+       test_assert(created);
+       i_close_fd(&fd);
+
+       struct stat st;
+       if (stat(dir, &st) < 0)
+               i_error("stat(%s) failed: %m", dir);
+       test_assert((st.st_mode & 0777) == 0700);
+       i_unlink(path);
+       file_lock_free(&lock);
+
+       if (unlink_directory(dir, UNLINK_DIRECTORY_FLAG_RMDIR) < 0)
+               i_fatal("unlink_directory(%s) failed: %m", dir);
+
+       test_end();
+}
+
 void test_file_create_locked(void)
 {
        test_file_create_locked_basic();
+       test_file_create_locked_mkdir();
 }