]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Moved all dotlocking code to lib/. Also we now use temp file + link() rather
authorTimo Sirainen <tss@iki.fi>
Sat, 5 Jul 2003 20:33:18 +0000 (23:33 +0300)
committerTimo Sirainen <tss@iki.fi>
Sat, 5 Jul 2003 20:33:18 +0000 (23:33 +0300)
than rely on working O_EXCL.

--HG--
branch : HEAD

src/imap/main.c
src/lib-index/mail-index-open.c
src/lib-index/maildir/maildir-uidlist.c
src/lib-index/mbox/mbox-lock.c
src/lib/file-dotlock.c
src/lib/file-dotlock.h
src/lib/randgen.c

index 2142abfb34b64394793963a87476ebe691d1bc23..f30ce7716ab622214de0d51543dc37e55b2df0ff 100644 (file)
@@ -9,6 +9,7 @@
 #include "restrict-access.h"
 #include "fd-close-on-exec.h"
 #include "process-title.h"
+#include "randgen.h"
 #include "module-dir.h"
 #include "mail-storage.h"
 #include "commands.h"
@@ -70,6 +71,10 @@ static void drop_privileges(void)
        /* Log file or syslog opening probably requires roots */
        open_logfile();
 
+       /* Most likely needed. Have to open /dev/urandom before possible
+          chrooting. */
+       random_init();
+
        restrict_access_by_env(!IS_STANDALONE());
 }
 
@@ -169,6 +174,7 @@ static void main_deinit(void)
        commands_deinit();
        clients_deinit();
         mail_storage_deinit();
+       random_deinit();
 
        closelog();
 }
index 09e47afb51fc3c1bf5427fe7159c2f08e48671c4..e3c00f3d452aec9efcd8c72b4755413149762889 100644 (file)
@@ -263,8 +263,8 @@ void mail_index_init_header(struct mail_index *index,
 
 static void mail_index_cleanup_temp_files(const char *dir)
 {
-       unlink_lockfiles(dir, t_strconcat("temp.", my_hostname, NULL),
-                        "temp.", time(NULL) - TEMP_FILE_TIMEOUT);
+       unlink_lockfiles(dir, t_strconcat(".temp.", my_hostname, ".", NULL),
+                        ".temp.", time(NULL) - TEMP_FILE_TIMEOUT);
 }
 
 void mail_index_init(struct mail_index *index, const char *dir)
index 07fe2334e68af3bef4d11d6cc3b4aac80d8fcbfc..26398db0fc3364f0b8bd7669f47a9a18cd08c20a 100644 (file)
 
 int maildir_uidlist_try_lock(struct mail_index *index)
 {
-       struct stat st;
        const char *path;
-       int fd, i;
+       int fd;
 
        if (INDEX_IS_UIDLIST_LOCKED(index))
                return 1;
 
-       path = t_strconcat(index->control_dir,
-                          "/" MAILDIR_UIDLIST_NAME ".lock", NULL);
-       for (i = 0; i < 2; i++) {
-               fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
-               if (fd != -1)
-                       break;
-
-               if (errno != EEXIST) {
-                       if (errno == EACCES) {
-                               /* read-only mailbox */
-                               return 0;
-                       }
-                       index_file_set_syscall_error(index, path, "open()");
-                       return -1;
-               }
-
-               /* exists, is it stale? */
-               if (stat(path, &st) < 0) {
-                       if (errno == ENOENT) {
-                               /* try again */
-                               continue;
-                       }
-                       index_file_set_syscall_error(index, path, "stat()");
-                       return -1;
-               }
-
-               if (st.st_mtime < ioloop_time - UIDLIST_LOCK_STALE_TIMEOUT) {
-                       if (unlink(path) < 0 && errno != ENOENT) {
-                               index_file_set_syscall_error(index, path,
-                                                            "unlink()");
-                               return -1;
-                       }
-                       /* try again */
-                       continue;
-               }
-               return 0;
+       path = t_strconcat(index->control_dir, "/" MAILDIR_UIDLIST_NAME, NULL);
+       fd = file_dotlock_open(path, NULL, 0, UIDLIST_LOCK_STALE_TIMEOUT,
+                              NULL, NULL);
+       if (fd == -1) {
+               if (errno == EAGAIN)
+                       return 0;
+               return -1;
        }
 
        index->maildir_lock_fd = fd;
@@ -75,13 +45,8 @@ void maildir_uidlist_unlock(struct mail_index *index)
        if (!INDEX_IS_UIDLIST_LOCKED(index))
                return;
 
-       path = t_strconcat(index->control_dir,
-                          "/" MAILDIR_UIDLIST_NAME ".lock", NULL);
-       if (unlink(path) < 0 && errno != ENOENT)
-               index_file_set_syscall_error(index, path, "unlink()");
-
-       if (close(index->maildir_lock_fd) < 0)
-               index_file_set_syscall_error(index, path, "close()");
+       path = t_strconcat(index->control_dir, "/" MAILDIR_UIDLIST_NAME, NULL);
+       (void)file_dotlock_delete(path, index->maildir_lock_fd);
        index->maildir_lock_fd = -1;
 }
 
@@ -252,22 +217,22 @@ int maildir_uidlist_rewrite(struct mail_index *index, time_t *mtime)
                                "/" MAILDIR_UIDLIST_NAME ".lock", NULL);
 
        failed = !maildir_uidlist_rewrite_fd(index, temp_path, mtime);
-       if (close(index->maildir_lock_fd) < 0) {
-               index_file_set_syscall_error(index, temp_path, "close()");
-               failed = TRUE;
-       }
-        index->maildir_lock_fd = -1;
 
        if (!failed) {
                db_path = t_strconcat(index->control_dir,
                                      "/" MAILDIR_UIDLIST_NAME, NULL);
 
-               if (rename(temp_path, db_path) < 0) {
-                       index_set_error(index, "rename(%s, %s) failed: %m",
-                                       temp_path, db_path);
+               if (file_dotlock_replace(db_path, index->maildir_lock_fd,
+                                        FALSE) <= 0) {
+                       index_set_error(index,
+                                       "file_dotlock_replace(%s) failed: %m",
+                                       db_path);
                        failed = TRUE;
                }
+       } else {
+               (void)close(index->maildir_lock_fd);
        }
+        index->maildir_lock_fd = -1;
 
        if (failed)
                (void)unlink(temp_path);
index dc20f8b8dfbe798024d357d43f73946c65fdf3aa..2f7c1b19dbfa9cfe6b7adb3615151ac76960d8fd 100644 (file)
@@ -257,7 +257,7 @@ int mbox_lock(struct mail_index *index, enum mail_lock_type lock_type)
                ctx.lock_type = lock_type;
                ctx.last_stale = -1;
 
-               ret = file_lock_dotlock(index->mailbox_path,
+               ret = file_lock_dotlock(index->mailbox_path, NULL,
                                        lock_type == MAIL_LOCK_SHARED &&
                                        !use_read_dotlock, lock_timeout,
                                        dotlock_change_timeout,
index b25a2fa60944f8be70128f83bb24ab03ae25d003..cfd9e931af08ef977b8c4af86770e4a9e2a64fe6 100644 (file)
@@ -1,10 +1,14 @@
 /* Copyright (C) 2003 Timo Sirainen */
 
 #include "lib.h"
+#include "str.h"
+#include "hex-binary.h"
 #include "hostpid.h"
+#include "randgen.h"
 #include "write-full.h"
 #include "file-dotlock.h"
 
+#include <stdio.h>
 #include <stdlib.h>
 #include <signal.h>
 #include <time.h>
 #define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)rand() % 100000)
 
 struct lock_info {
-       const char *path, *lock_path;
+       const char *path, *lock_path, *temp_path;
        unsigned int stale_timeout;
+       int fd;
 
        dev_t dev;
        ino_t ino;
        off_t size;
-       time_t mtime;
+       time_t ctime, mtime;
 
        off_t last_size;
-       time_t last_mtime;
+       time_t last_ctime, last_mtime;
        time_t last_change;
 
        pid_t pid;
@@ -80,12 +85,14 @@ static int check_lock(time_t now, struct lock_info *lock_info)
 
        if (lock_info->ino != st.st_ino ||
            !CMP_DEV_T(lock_info->dev, st.st_dev) ||
+           lock_info->ctime != st.st_ctime ||
            lock_info->mtime != st.st_mtime ||
            lock_info->size != st.st_size) {
                /* either our first check or someone else got the lock file.
                   check if it contains a pid whose existence we can verify */
                lock_info->dev = st.st_dev;
                lock_info->ino = st.st_ino;
+               lock_info->ctime = st.st_ctime;
                lock_info->mtime = st.st_mtime;
                lock_info->size = st.st_size;
                lock_info->pid = read_local_pid(lock_info->lock_path);
@@ -119,9 +126,11 @@ static int check_lock(time_t now, struct lock_info *lock_info)
                        return -1;
                }
        } else if (lock_info->last_size != st.st_size ||
+                   lock_info->last_ctime != st.st_ctime ||
                   lock_info->last_mtime != st.st_mtime) {
                lock_info->last_change = now;
                lock_info->last_size = st.st_size;
+               lock_info->last_ctime = st.st_ctime;
                lock_info->last_mtime = st.st_mtime;
        }
 
@@ -137,57 +146,99 @@ static int check_lock(time_t now, struct lock_info *lock_info)
        return 0;
 }
 
-static int try_create_lock(const char *lock_path, struct dotlock *dotlock_r)
+static int create_temp_file(const char *prefix, const char **path_r)
 {
-       const char *str;
+       string_t *path;
+       size_t len;
        struct stat st;
+       char randbuf[8];
        int fd;
 
-       fd = open(lock_path, O_WRONLY | O_EXCL | O_CREAT, 0644);
-       if (fd == -1)
-               return -1;
+       path = t_str_new(256);
+       str_append(path, prefix);
+       len = str_len(path);
 
-       /* write our pid and host, if possible */
-       str = t_strdup_printf("%s:%s", my_pid, my_hostname);
-       if (write_full(fd, str, strlen(str)) < 0) {
-               /* failed, leave it empty then */
-               if (ftruncate(fd, 0) < 0) {
-                       i_error("ftruncate(%s) failed: %m", lock_path);
-                       (void)unlink(lock_path);
-                       (void)close(fd);
+       for (;;) {
+               do {
+                       random_fill(randbuf, sizeof(randbuf));
+                       str_truncate(path, len);
+                       str_append(path,
+                                  binary_to_hex(randbuf, sizeof(randbuf)));
+                       *path_r = str_c(path);
+               } while (stat(*path_r, &st) == 0);
+
+               if (errno != ENOENT) {
+                       i_error("stat(%s) failed: %m", *path_r);
+                       return -1;
+               }
+
+               fd = open(*path_r, O_RDWR | O_EXCL | O_CREAT, 0644);
+               if (fd != -1)
+                       return fd;
+
+               if (errno != EEXIST) {
+                       i_error("open(%s) failed: %m", *path_r);
                        return -1;
                }
        }
+}
 
-       /* save the inode info after writing */
-       if (fstat(fd, &st) < 0) {
-               i_error("fstat(%s) failed: %m", lock_path);
-               (void)close(fd);
-               return -1;
+static int try_create_lock(struct lock_info *lock_info, const char *temp_prefix)
+{
+       const char *str, *p;
+
+       if (lock_info->temp_path == NULL) {
+               /* we'll need our temp file first. */
+               if (temp_prefix == NULL) {
+                       temp_prefix = t_strconcat(".temp.", my_hostname, ".",
+                                                 my_pid, ".", NULL);
+               }
+
+               p = *temp_prefix == '/' ? NULL :
+                       strrchr(lock_info->lock_path, '/');
+               if (p != NULL) {
+                       str = t_strdup_until(lock_info->lock_path, p+1);
+                       temp_prefix = t_strconcat(str, temp_prefix, NULL);
+               }
+
+               lock_info->fd = create_temp_file(temp_prefix, &str);
+               if (lock_info->fd == -1)
+                       return -1;
+
+                lock_info->temp_path = str;
        }
 
-       dotlock_r->dev = st.st_dev;
-       dotlock_r->ino = st.st_ino;
-       dotlock_r->mtime = st.st_mtime;
+       if (link(lock_info->temp_path, lock_info->lock_path) < 0) {
+               if (errno == EEXIST)
+                       return 0;
 
-       if (close(fd) < 0) {
-               i_error("close(%s) failed: %m", lock_path);
-               (void)unlink(lock_path);
+               i_error("link(%s, %s) failed: %m",
+                       lock_info->temp_path, lock_info->lock_path);
                return -1;
        }
+
+       if (unlink(lock_info->temp_path) < 0 && errno != ENOENT) {
+               i_error("unlink(%s) failed: %m", lock_info->temp_path);
+               /* non-fatal, continue */
+       }
+       lock_info->temp_path = NULL;
+
        return 1;
 }
 
-int file_lock_dotlock(const char *path, int checkonly,
-                     unsigned int timeout, unsigned int stale_timeout,
-                     int (*callback)(unsigned int secs_left, int stale,
-                                     void *context),
-                     void *context, struct dotlock *dotlock_r)
+static int dotlock_create(const char *path, const char *temp_prefix,
+                         int checkonly, int *fd,
+                         unsigned int timeout, unsigned int stale_timeout,
+                         int (*callback)(unsigned int secs_left, int stale,
+                                         void *context),
+                         void *context)
 {
        const char *lock_path;
         struct lock_info lock_info;
        unsigned int stale_notify_threshold;
+       unsigned int change_secs, wait_left;
        time_t now, max_wait_time, last_notify;
+       int do_wait, ret;
 
        now = time(NULL);
 
@@ -195,76 +246,118 @@ int file_lock_dotlock(const char *path, int checkonly,
        stale_notify_threshold = stale_timeout / 2;
        max_wait_time = now + timeout;
 
-       /* There's two ways to do this:
-
-          a) Rely on O_EXCL. Historically this hasn't always worked with NFS.
-          b) Create temp file and link() it to the file we want.
-
-          We now use a). It's easier to do and it never leaves temporary files
-          lying around. Also Postfix relies on it too, so I guess it's safe
-          enough nowadays.
-       */
-
        memset(&lock_info, 0, sizeof(lock_info));
        lock_info.path = path;
        lock_info.lock_path = lock_path;
        lock_info.stale_timeout = stale_timeout;
        lock_info.last_change = now;
+       lock_info.fd = -1;
 
-       last_notify = 0;
+       last_notify = 0; do_wait = FALSE;
 
        do {
-               switch (check_lock(now, &lock_info)) {
-               case -1:
-                       return -1;
-               case 0:
-                       if (last_notify != now && callback != NULL) {
-                               unsigned int change_secs;
-                               unsigned int wait_left;
-
-                               last_notify = now;
-                               change_secs = now - lock_info.last_change;
-                               wait_left = max_wait_time - now;
-
-                               if (change_secs >= stale_notify_threshold &&
-                                   change_secs <= wait_left) {
-                                       if (!callback(stale_timeout -
-                                                     change_secs,
-                                                     TRUE, context)) {
-                                               /* we don't want to override */
-                                               lock_info.last_change = now;
-                                       }
-                               } else {
-                                       (void)callback(wait_left, FALSE,
-                                                      context);
-                               }
-                       }
-
+               if (do_wait) {
                        usleep(LOCK_RANDOM_USLEEP_TIME);
+                       do_wait = FALSE;
+               }
+
+               ret = check_lock(now, &lock_info);
+               if (ret < 0)
                        break;
-               default:
-                       if (checkonly ||
-                           try_create_lock(lock_path, dotlock_r) > 0)
-                               return 1;
-
-                       if (errno != EEXIST) {
-                               i_error("open(%s) failed: %m", lock_path);
-                               return -1;
+
+               if (ret == 1) {
+                       if (checkonly)
+                               break;
+
+                       ret = try_create_lock(&lock_info, temp_prefix);
+                       if (ret != 0)
+                               break;
+               }
+
+               do_wait = TRUE;
+               if (last_notify != now && callback != NULL) {
+                       last_notify = now;
+                       change_secs = now - lock_info.last_change;
+                       wait_left = max_wait_time - now;
+
+                       t_push();
+                       if (change_secs >= stale_notify_threshold &&
+                           change_secs <= wait_left) {
+                               if (!callback(stale_timeout - change_secs,
+                                             TRUE, context)) {
+                                       /* we don't want to override */
+                                       lock_info.last_change = now;
+                               }
+                       } else {
+                               (void)callback(wait_left, FALSE, context);
                        }
-                       break;
+                       t_pop();
                }
 
                now = time(NULL);
        } while (now < max_wait_time);
 
-       errno = EAGAIN;
-       return 0;
+       if (ret <= 0) {
+               (void)close(lock_info.fd);
+               lock_info.fd = -1;
+       }
+       *fd = lock_info.fd;
+
+       if (ret == 0)
+               errno = EAGAIN;
+       return ret;
 }
 
-int file_unlock_dotlock(const char *path, const struct dotlock *dotlock)
+int file_lock_dotlock(const char *path, const char *temp_prefix, int checkonly,
+                     unsigned int timeout, unsigned int stale_timeout,
+                     int (*callback)(unsigned int secs_left, int stale,
+                                     void *context),
+                     void *context, struct dotlock *dotlock_r)
 {
-       const char *lock_path;
+       const char *lock_path, *str;
        struct stat st;
+       int fd, ret;
+
+       lock_path = t_strconcat(path, ".lock", NULL);
+
+       ret = dotlock_create(path, temp_prefix, checkonly, &fd,
+                            timeout, stale_timeout, callback, context);
+       if (ret <= 0 || checkonly)
+               return ret;
+
+       /* write our pid and host, if possible */
+       str = t_strdup_printf("%s:%s", my_pid, my_hostname);
+       if (write_full(fd, str, strlen(str)) < 0) {
+               /* failed, leave it empty then */
+               if (ftruncate(fd, 0) < 0) {
+                       i_error("ftruncate(%s) failed: %m", lock_path);
+                       (void)close(fd);
+                       return -1;
+               }
+       }
+
+       /* save the inode info after writing */
+       if (fstat(fd, &st) < 0) {
+               i_error("fstat(%s) failed: %m", lock_path);
+               (void)close(fd);
+               return -1;
+       }
+
+       if (close(fd) < 0) {
+               i_error("fstat(%s) failed: %m", lock_path);
+               return -1;
+       }
+
+       dotlock_r->dev = st.st_dev;
+       dotlock_r->ino = st.st_ino;
+       dotlock_r->mtime = st.st_mtime;
+       return 1;
+}
+
+static int dotlock_delete(const char *path, const struct dotlock *dotlock)
+{
+       const char *lock_path;
+        struct stat st;
 
        lock_path = t_strconcat(path, ".lock", NULL);
 
@@ -302,3 +395,87 @@ int file_unlock_dotlock(const char *path, const struct dotlock *dotlock)
 
        return 1;
 }
+
+int file_unlock_dotlock(const char *path, const struct dotlock *dotlock)
+{
+       return dotlock_delete(path, dotlock);
+}
+
+int file_dotlock_open(const char *path, const char *temp_prefix,
+                     unsigned int timeout, unsigned int stale_timeout,
+                     int (*callback)(unsigned int secs_left, int stale,
+                                     void *context),
+                     void *context)
+{
+       int ret, fd;
+
+       ret = dotlock_create(path, temp_prefix, FALSE, &fd,
+                            timeout, stale_timeout, callback, context);
+       if (ret <= 0)
+               return -1;
+       return fd;
+}
+
+int file_dotlock_replace(const char *path, int fd, int verify_owner)
+{
+       struct stat st, st2;
+       const char *lock_path;
+
+       lock_path = t_strconcat(path, ".lock", NULL);
+       if (verify_owner) {
+               if (fstat(fd, &st) < 0) {
+                       i_error("fstat(%s) failed: %m", lock_path);
+                       (void)close(fd);
+                       return -1;
+               }
+       }
+       if (close(fd) < 0) {
+               i_error("close(%s) failed: %m", lock_path);
+               return -1;
+       }
+
+       if (verify_owner) {
+               if (lstat(lock_path, &st2) < 0) {
+                       i_error("lstat(%s) failed: %m", lock_path);
+                       return -1;
+               }
+
+               if (st.st_ino != st2.st_ino ||
+                   !CMP_DEV_T(st.st_dev, st2.st_dev)) {
+                       i_warning("Our dotlock file %s was overridden",
+                                 lock_path);
+                       return 0;
+               }
+       }
+
+       if (rename(lock_path, path) < 0) {
+               i_error("rename(%s, %s) failed: %m", lock_path, path);
+               return -1;
+       }
+       return 1;
+}
+
+int file_dotlock_delete(const char *path, int fd)
+{
+       struct dotlock dotlock;
+       struct stat st;
+
+       if (fstat(fd, &st) < 0) {
+               i_error("fstat(%s) failed: %m",
+                       t_strconcat(path, ".lock", NULL));
+               (void)close(fd);
+               return -1;
+       }
+
+       if (close(fd) < 0) {
+               i_error("close(%s) failed: %m",
+                       t_strconcat(path, ".lock", NULL));
+               return -1;
+       }
+
+       dotlock.dev = st.st_dev;
+       dotlock.ino = st.st_ino;
+       dotlock.mtime = st.st_mtime;
+
+       return dotlock_delete(path, &dotlock);
+}
index 17f9246c8cfb086dc777bc2482cffa8457f168ff..f60a48863f65238fd5ee6394012cc0d32edbdd5a 100644 (file)
@@ -19,10 +19,14 @@ struct dotlock {
    If checkonly is TRUE, we don't actually create the lock file, only make
    sure that it doesn't exist. This is racy, so you shouldn't rely on it.
 
+   Dotlock files are created by first creating a temp file and then link()ing
+   it to the dotlock. temp_prefix specifies the prefix to use for temp files.
+   It may contain a full path. If it's NULL, ".temp.hostname.pid." is used
+
    callback is called once in a while. stale is set to TRUE if stale lock is
    detected and will be overridden in secs_left. If callback returns FALSE
    then, the lock will not be overridden. */
-int file_lock_dotlock(const char *path, int checkonly,
+int file_lock_dotlock(const char *path, const char *temp_prefix, int checkonly,
                      unsigned int timeout, unsigned int stale_timeout,
                      int (*callback)(unsigned int secs_left, int stale,
                                      void *context),
@@ -32,4 +36,18 @@ int file_lock_dotlock(const char *path, int checkonly,
    been deleted or reused by someone else, -1 if error. */
 int file_unlock_dotlock(const char *path, const struct dotlock *dotlock);
 
+/* Use dotlock as the new content for file. This provides read safety without
+   locks, but not very good for large files. Returns fd for lock file.
+   If dotlock is stale, returns -1 and errno = EAGAIN. */
+int file_dotlock_open(const char *path, const char *temp_prefix,
+                     unsigned int timeout, unsigned int stale_timeout,
+                     int (*callback)(unsigned int secs_left, int stale,
+                                     void *context),
+                     void *context);
+/* Replaces path with path.lock file. Closes given fd. If verify_owner is TRUE,
+   it checks that lock file hasn't been overwritten before renaming. */
+int file_dotlock_replace(const char *path, int fd, int verify_owner);
+/* Like file_unlock_dotlock(). Closes given fd. */
+int file_dotlock_delete(const char *path, int fd);
+
 #endif
index 2c997bf115507189f51161b2a820e9cc579a79cf..8c802bae636fc0fbd2aaf33ac0eae8a8f8604880 100644 (file)
@@ -107,16 +107,5 @@ void random_init(void) {}
 void random_deinit(void) {}
 
 #else
-#  ifdef __GNUC__
-#    warning Random generator disabled
-#  endif
-
-void random_fill(void *buf __attr_unused__, size_t size __attr_unused__)
-{
-       i_fatal("random_fill(): No random source");
-}
-
-void random_init(void) {}
-void random_deinit(void) {}
-
+#  error No random number generator, use eg. OpenSSL.
 #endif