Mercurial > dovecot > original-hg > dovecot-1.2
changeset 1597:9f503b7851ab HEAD
Moved all dotlocking code to lib/. Also we now use temp file + link() rather
than rely on working O_EXCL.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Sat, 05 Jul 2003 23:33:18 +0300 |
parents | beab37ab1617 |
children | d45c613e3b15 |
files | 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 |
diffstat | 7 files changed, 323 insertions(+), 168 deletions(-) [+] |
line wrap: on
line diff
--- a/src/imap/main.c Thu Jul 03 04:01:20 2003 +0300 +++ b/src/imap/main.c Sat Jul 05 23:33:18 2003 +0300 @@ -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 @@ /* 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 @@ commands_deinit(); clients_deinit(); mail_storage_deinit(); + random_deinit(); closelog(); }
--- a/src/lib-index/mail-index-open.c Thu Jul 03 04:01:20 2003 +0300 +++ b/src/lib-index/mail-index-open.c Sat Jul 05 23:33:18 2003 +0300 @@ -263,8 +263,8 @@ 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)
--- a/src/lib-index/maildir/maildir-uidlist.c Thu Jul 03 04:01:20 2003 +0300 +++ b/src/lib-index/maildir/maildir-uidlist.c Sat Jul 05 23:33:18 2003 +0300 @@ -19,49 +19,19 @@ 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 @@ 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 @@ "/" 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);
--- a/src/lib-index/mbox/mbox-lock.c Thu Jul 03 04:01:20 2003 +0300 +++ b/src/lib-index/mbox/mbox-lock.c Sat Jul 05 23:33:18 2003 +0300 @@ -257,7 +257,7 @@ 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,
--- a/src/lib/file-dotlock.c Thu Jul 03 04:01:20 2003 +0300 +++ b/src/lib/file-dotlock.c Sat Jul 05 23:33:18 2003 +0300 @@ -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> @@ -14,16 +18,17 @@ #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 @@ 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 @@ 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,15 +146,184 @@ 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) + path = t_str_new(256); + str_append(path, prefix); + len = str_len(path); + + 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; + } + } +} + +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; + } + + if (link(lock_info->temp_path, lock_info->lock_path) < 0) { + if (errno == EEXIST) + return 0; + + 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; +} + +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); + + lock_path = t_strconcat(path, ".lock", NULL); + stale_notify_threshold = stale_timeout / 2; + max_wait_time = now + timeout; + + 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; do_wait = FALSE; + + do { + if (do_wait) { + usleep(LOCK_RANDOM_USLEEP_TIME); + do_wait = FALSE; + } + + ret = check_lock(now, &lock_info); + if (ret < 0) + break; + + 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); + } + t_pop(); + } + + now = time(NULL); + } while (now < max_wait_time); + + 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_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, *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); @@ -153,7 +331,6 @@ /* 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); return -1; } @@ -166,105 +343,21 @@ 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; - - if (close(fd) < 0) { - i_error("close(%s) failed: %m", lock_path); - (void)unlink(lock_path); - return -1; - } 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_delete(const char *path, const struct dotlock *dotlock) { const char *lock_path; - struct lock_info lock_info; - unsigned int stale_notify_threshold; - time_t now, max_wait_time, last_notify; - - now = time(NULL); - - lock_path = t_strconcat(path, ".lock", NULL); - 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; - - last_notify = 0; - - 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); - } - } - - usleep(LOCK_RANDOM_USLEEP_TIME); - 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; - } - break; - } - - now = time(NULL); - } while (now < max_wait_time); - - errno = EAGAIN; - return 0; -} - -int file_unlock_dotlock(const char *path, const struct dotlock *dotlock) -{ - const char *lock_path; - struct stat st; + struct stat st; lock_path = t_strconcat(path, ".lock", NULL); @@ -302,3 +395,87 @@ 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); +}
--- a/src/lib/file-dotlock.h Thu Jul 03 04:01:20 2003 +0300 +++ b/src/lib/file-dotlock.h Sat Jul 05 23:33:18 2003 +0300 @@ -19,10 +19,14 @@ 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 @@ 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
--- a/src/lib/randgen.c Thu Jul 03 04:01:20 2003 +0300 +++ b/src/lib/randgen.c Sat Jul 05 23:33:18 2003 +0300 @@ -107,16 +107,5 @@ 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