changeset 643:da34bdd4e0c6 HEAD

Added mbox lock settings to config file. Support timeouting fcntl() and flock() locks. Plus before the fcntl/flocks weren't even set.
author Timo Sirainen <tss@iki.fi>
date Thu, 21 Nov 2002 22:13:32 +0200
parents 5f5fdc45c19d
children 415498fa78d6
files dovecot-example.conf src/lib-index/mail-index.h src/lib-index/mbox/mbox-lock.c src/master/imap-process.c src/master/settings.c src/master/settings.h
diffstat 6 files changed, 244 insertions(+), 61 deletions(-) [+]
line wrap: on
line diff
--- a/dovecot-example.conf	Thu Nov 21 16:56:07 2002 +0200
+++ b/dovecot-example.conf	Thu Nov 21 22:13:32 2002 +0200
@@ -177,6 +177,26 @@
 # down things as extra stat() needs to be called for each file.
 #maildir_check_content_changes = no
 
+# Which locking methods to use for locking mbox. All of them are used by
+# default. flock is ignored in systems which don't have it. Note that the
+# order of fcntl and flock are important to prevent deadlocks if they're both
+# also used by other programs accessing the mailbox. Dotlock file is always
+# created first.
+#mbox_locks = dotlock fcntl flock
+
+# Should we create dotlock file even when we want only a read-lock? Setting
+# this to yes hurts the performance when the mailbox is accessed simultaneously
+# by multiple processes, but it's needed for reliable reading if no other
+# locking methods are available.
+#mbox_read_dotlock = no
+
+# Maximum time in seconds to wait for lock (all of them) before aborting.
+#mbox_lock_timeout = 300
+
+# If dotlock exists but the mailbox isn't modified in any way, override the
+# lock file after this many seconds.
+#mbox_dotlock_change_timeout = 30
+
 # If main index file is incompatible with us, should we overwrite it or
 # create a new index with another name. Unless you are running Dovecot in
 # multiple computers with different architectures accessing the same
--- a/src/lib-index/mail-index.h	Thu Nov 21 16:56:07 2002 +0200
+++ b/src/lib-index/mail-index.h	Thu Nov 21 22:13:32 2002 +0200
@@ -333,6 +333,8 @@
 	int mbox_fd;
 	IBuffer *mbox_inbuf;
 	MailLockType mbox_lock_type;
+	dev_t mbox_dotlock_dev;
+	ino_t mbox_dotlock_ino;
 
 	/* these counters can be used to check that we've synced the mailbox
 	   after locking it */
@@ -374,7 +376,7 @@
 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
-	0, 0, 0, 0
+	0, 0, 0, 0, 0, 0
 
 /* defaults - same as above but prefixed with mail_index_. */
 int mail_index_open(MailIndex *index, int update_recent, int fast);
--- a/src/lib-index/mbox/mbox-lock.c	Thu Nov 21 16:56:07 2002 +0200
+++ b/src/lib-index/mbox/mbox-lock.c	Thu Nov 21 22:13:32 2002 +0200
@@ -18,17 +18,53 @@
 /* 0.1 .. 0.2msec */
 #define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)rand() % 100000)
 
-/* assume stale dotlock if mbox file hasn't changed for 5 seconds */
-#define MAX_UNCHANGED_LOCK_WAIT 5
+/* lock methods to use in wanted order */
+#define DEFAULT_LOCK_METHODS "dotlock fcntl flock"
+/* lock timeout */
+#define DEFAULT_LOCK_TIMEOUT 300
+/* assume stale dotlock if mbox file hasn't changed for n seconds */
+#define DEFAULT_DOTLOCK_CHANGE_TIMEOUT 30
+
+static int lock_settings_initialized = FALSE;
+static int use_dotlock, use_fcntl_lock, use_flock, fcntl_before_flock;
+static int use_read_dotlock, lock_timeout, dotlock_change_timeout;
+
+static void mbox_init_lock_settings(void)
+{
+	const char *str;
+	char *const *lock;
+
+        use_dotlock = use_fcntl_lock = use_flock = fcntl_before_flock = FALSE;
 
-/* abort trying to get lock after 30 seconds */
-#define MAX_LOCK_WAIT 30
+	str = getenv("MBOX_LOCKS");
+	if (str == NULL) str = DEFAULT_LOCK_METHODS;
+	for (lock = t_strsplit(str, " "); *lock != NULL; lock++) {
+		if (strcasecmp(*lock, "dotlock") == 0)
+			use_dotlock = TRUE;
+		else if (strcasecmp(*lock, "fcntl") == 0) {
+			use_fcntl_lock = TRUE;
+			fcntl_before_flock = use_flock == FALSE;
+		} else if (strcasecmp(*lock, "flock") == 0)
+			use_flock = TRUE;
+		else
+			i_fatal("MBOX_LOCKS: Invalid value %s", *lock);
+	}
 
-/* remove lock after 10 mins */
-#define STALE_LOCK_TIMEOUT (60*10)
+	use_read_dotlock = getenv("MBOX_READ_DOTLOCK") != NULL;
+
+	str = getenv("MBOX_LOCK_TIMEOUT");
+	lock_timeout = str == NULL ? DEFAULT_LOCK_TIMEOUT : atoi(str);
+
+	str = getenv("MBOX_DOTLOCK_CHANGE_TIMEOUT");
+	dotlock_change_timeout = str == NULL ?
+		DEFAULT_DOTLOCK_CHANGE_TIMEOUT : atoi(str);
+
+        lock_settings_initialized = TRUE;
+}
 
 #ifdef HAVE_FLOCK
-static int mbox_lock_flock(MailIndex *index, MailLockType lock_type)
+static int mbox_lock_flock(MailIndex *index, MailLockType lock_type,
+			   time_t max_wait_time)
 {
 	if (lock_type == MAIL_LOCK_EXCLUSIVE)
 		lock_type = LOCK_EX;
@@ -37,15 +73,27 @@
 	else
 		lock_type = LOCK_UN;
 
-	if (flock(index->mbox_fd, lock_type) < 0)
-		return index_file_set_syscall_error(index, index->mbox_path,
-						    "flock()");
+	while (flock(index->mbox_fd, lock_type | LOCK_NB) < 0) {
+		if (errno != EWOULDBLOCK) {
+			index_file_set_syscall_error(index, index->mbox_path,
+						     "flock()");
+			return FALSE;
+		}
+
+		if (max_wait_time != 0 && time(NULL) >= max_wait_time) {
+			errno = EAGAIN;
+			return FALSE;
+		}
+
+		usleep(LOCK_RANDOM_USLEEP_TIME);
+	}
 
 	return TRUE;
 }
 #endif
 
-static int mbox_lock_fcntl(MailIndex *index, MailLockType lock_type)
+static int mbox_lock_fcntl(MailIndex *index, MailLockType lock_type,
+			   time_t max_wait_time)
 {
 	struct flock fl;
 
@@ -60,45 +108,34 @@
 						     "fcntl()");
 			return FALSE;
 		}
+
+		if (max_wait_time != 0 && time(NULL) >= max_wait_time) {
+			errno = EAGAIN;
+			return FALSE;
+		}
 	}
 
 	return TRUE;
 }
 
-static int mbox_lock_dotlock(MailIndex *index, const char *path, int set)
+static int mbox_lock_dotlock(MailIndex *index, const char *path,
+			     time_t max_wait_time)
 {
 	struct stat st;
-	time_t now, max_wait_time, last_change, last_mtime;
+	time_t now, last_change, last_mtime;
 	off_t last_size;
 	int fd;
 
 	path = t_strconcat(path, ".lock", NULL);
-	if (!set) {
-		if (unlink(path) == 0 || errno == ENOENT)
-			return TRUE;
-
-		return index_file_set_syscall_error(index, path, "unlink()");
-	}
 
 	/* don't bother with the temp files as we'd just leave them lying
 	   around. besides, postfix also relies on O_EXCL working so we
 	   might as well. */
-	max_wait_time = time(NULL) + MAX_LOCK_WAIT;
 	last_change = time(NULL); last_size = 0; last_mtime = 0;
 	do {
 		now = time(NULL);
 
 		if (stat(path, &st) == 0) {
-			/* lock exists, see if it's too old */
-			if (now > st.st_ctime + STALE_LOCK_TIMEOUT) {
-				if (unlink(path) < 0 && errno != ENOENT) {
-					index_file_set_syscall_error(
-						index, path, "unlink()");
-					break;
-				}
-				continue;
-			}
-
 			/* see if there's been any changes in mbox */
 			if (stat(index->mbox_path, &st) < 0) {
 				mbox_set_syscall_error(index, "stat()");
@@ -112,7 +149,7 @@
 				last_mtime = st.st_mtime;
 			}
 
-			if (now > last_change + MAX_UNCHANGED_LOCK_WAIT) {
+			if (now > last_change + dotlock_change_timeout) {
 				/* no changes for a while, assume stale lock */
 				if (unlink(path) < 0 && errno != ENOENT) {
 					index_file_set_syscall_error(
@@ -129,9 +166,20 @@
 		fd = open(path, O_WRONLY | O_EXCL | O_CREAT, 0);
 		if (fd != -1) {
 			/* got it */
+			if (fstat(fd, &st) < 0) {
+				index_file_set_syscall_error(index, path,
+							     "fstat()");
+				(void)close(fd);
+				return FALSE;
+			}
+
+			index->mbox_dotlock_dev = st.st_dev;
+			index->mbox_dotlock_ino = st.st_ino;
+
 			if (close(fd) < 0) {
 				index_file_set_syscall_error(index, path,
 							     "close()");
+				return FALSE;
 			}
 			return TRUE;
 		}
@@ -147,9 +195,67 @@
 	return FALSE;
 }
 
+static int mbox_unlock_dotlock(MailIndex *index, const char *path)
+{
+	struct stat st;
+	dev_t old_dev;
+	ino_t old_ino;
+
+	path = t_strconcat(path, ".lock", NULL);
+
+        old_dev = index->mbox_dotlock_dev;
+        old_ino = index->mbox_dotlock_ino;
+
+        index->mbox_dotlock_dev = 0;
+        index->mbox_dotlock_ino = 0;
+
+	if (stat(path, &st) < 0) {
+		if (errno == ENOENT)
+			return TRUE; /* doesn't exist anymore, ignore */
+
+		index_file_set_syscall_error(index, path, "stat()");
+		return FALSE;
+	}
+
+	/* make sure it's still our dotlock */
+	if (old_dev != st.st_dev || old_ino != st.st_ino) {
+		index_set_error(index,
+			"Warning: Our dotlock file %s was overridden", path);
+		return FALSE;
+	}
+
+	if (unlink(path) < 0 && errno != ENOENT)
+		return index_file_set_syscall_error(index, path, "unlink()");
+
+	return TRUE;
+}
+
+static int mbox_file_locks(MailIndex *index, time_t max_wait_time)
+{
+	if (use_fcntl_lock && fcntl_before_flock) {
+		if (!mbox_lock_fcntl(index, index->mbox_lock_type,
+				     max_wait_time))
+			return FALSE;
+	}
+#ifdef HAVE_FLOCK
+	if (use_flock) {
+		if (!mbox_lock_flock(index, index->mbox_lock_type,
+				     max_wait_time))
+			return FALSE;
+	}
+#endif
+	if (use_fcntl_lock && !fcntl_before_flock) {
+		if (!mbox_lock_fcntl(index, index->mbox_lock_type,
+				     max_wait_time))
+			return FALSE;
+	}
+	return TRUE;
+}
+
 int mbox_lock(MailIndex *index, MailLockType lock_type)
 {
 	struct stat st;
+	time_t max_wait_time;
 
 	/* index must be locked before mbox file, to avoid deadlocks */
 	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);
@@ -163,40 +269,39 @@
 	if (index->mbox_lock_type == lock_type)
 		return TRUE;
 
+	if (!lock_settings_initialized)
+                mbox_init_lock_settings();
+
+	max_wait_time = time(NULL) + lock_timeout;
+
 	/* make .lock file first to protect overwriting the file */
-	if (index->mbox_lock_type == MAIL_LOCK_UNLOCK) {
-		if (!mbox_lock_dotlock(index, index->mbox_path, TRUE))
+	if (use_dotlock && index->mbox_dotlock_ino == 0 &&
+	    (lock_type == MAIL_LOCK_EXCLUSIVE || use_read_dotlock)) {
+		if (!mbox_lock_dotlock(index, index->mbox_path, max_wait_time))
 			return FALSE;
 	}
 
 	/* now we need to have the file itself locked. open it if needed. */
-	do {
-		if (stat(index->mbox_path, &st) < 0)
-			return mbox_set_syscall_error(index, "stat()");
+	if (stat(index->mbox_path, &st) < 0)
+		return mbox_set_syscall_error(index, "stat()");
 
-		if (st.st_dev != index->mbox_dev ||
-		    st.st_ino != index->mbox_ino)
-			mbox_file_close_fd(index);
-
-		if (index->mbox_fd == -1) {
-			if (!mbox_file_open(index))
-				break;
-		}
+	if (st.st_dev != index->mbox_dev || st.st_ino != index->mbox_ino)
+		mbox_file_close_fd(index);
 
-		if (!mbox_lock_fcntl(index, index->mbox_lock_type))
-			break;
-#ifdef HAVE_FLOCK
-		if (!mbox_lock_flock(index, index->mbox_lock_type))
-			break;
-#endif
-		index->mbox_lock_type = lock_type;
-		return TRUE;
-	} while (0);
+	if (index->mbox_fd == -1) {
+		if (!mbox_file_open(index)) {
+			(void)mbox_unlock(index);
+			return FALSE;
+		}
+	}
 
-	if (index->mbox_lock_type == MAIL_LOCK_UNLOCK)
-		(void)mbox_lock_dotlock(index, index->mbox_path, FALSE);
+	index->mbox_lock_type = lock_type;
+	if (!mbox_file_locks(index, max_wait_time)) {
+		(void)mbox_unlock(index);
+		return FALSE;
+	}
 
-	return FALSE;
+	return TRUE;
 }
 
 int mbox_unlock(MailIndex *index)
@@ -211,15 +316,18 @@
 	failed = FALSE;
 	if (index->mbox_fd != -1) {
 #ifdef HAVE_FLOCK
-		if (!mbox_lock_flock(index, MAIL_LOCK_UNLOCK))
+		if (use_flock && !mbox_lock_flock(index, MAIL_LOCK_UNLOCK, 0))
 			failed = TRUE;
 #endif
-		if (!mbox_lock_fcntl(index, MAIL_LOCK_UNLOCK))
+		if (use_fcntl_lock &&
+		    !mbox_lock_fcntl(index, MAIL_LOCK_UNLOCK, 0))
 			failed = TRUE;
 	}
 
-	if (!mbox_lock_dotlock(index, index->mbox_path, FALSE))
-		failed = TRUE;
+	if (index->mbox_dotlock_ino != 0) {
+		if (!mbox_unlock_dotlock(index, index->mbox_path))
+			failed = TRUE;
+	}
 
 	/* make sure we don't keep mmap() between locks - there could have
 	   been changes to file size which would break things. or actually
--- a/src/master/imap-process.c	Thu Nov 21 16:56:07 2002 +0200
+++ b/src/master/imap-process.c	Thu Nov 21 22:13:32 2002 +0200
@@ -133,6 +133,14 @@
 	if (umask(set_umask) != set_umask)
 		i_fatal("Invalid umask: %o", set_umask);
 
+	putenv((char *) t_strconcat("MBOX_LOCKS=", set_mbox_locks, NULL));
+	putenv((char *) t_strdup_printf("MBOX_LOCK_TIMEOUT=%u",
+					set_mbox_lock_timeout));
+	putenv((char *) t_strdup_printf("MBOX_DOTLOCK_CHANGE_TIMEOUT=%u",
+					set_mbox_dotlock_change_timeout));
+	if (set_mbox_read_dotlock)
+		putenv("MBOX_READ_DOTLOCK=1");
+
 	if (set_verbose_proctitle && net_ip2host(ip, host) == 0) {
 		i_snprintf(title, sizeof(title), "[%s %s]", user, host);
 		argv[2] = title;
--- a/src/master/settings.c	Thu Nov 21 16:56:07 2002 +0200
+++ b/src/master/settings.c	Thu Nov 21 22:13:32 2002 +0200
@@ -66,6 +66,11 @@
 				SET_BOOL,&set_maildir_copy_with_hardlinks },
 	{ "maildir_check_content_changes",
 				SET_BOOL,&set_maildir_check_content_changes },
+	{ "mbox_locks",		SET_STR, &set_mbox_locks, },
+	{ "mbox_read_dotlock",	SET_BOOL,&set_mbox_read_dotlock, },
+	{ "mbox_lock_timeout",	SET_INT, &set_mbox_lock_timeout, },
+	{ "mbox_dotlock_change_timeout",
+				SET_INT, &set_mbox_dotlock_change_timeout, },
 	{ "overwrite_incompatible_index",
 				SET_BOOL,&set_overwrite_incompatible_index },
 	{ "umask",		SET_INT, &set_umask },
@@ -118,6 +123,10 @@
 int set_mail_save_crlf = FALSE;
 int set_maildir_copy_with_hardlinks = FALSE;
 int set_maildir_check_content_changes = FALSE;
+char *set_mbox_locks = "dotlock fcntl flock";
+int set_mbox_read_dotlock = FALSE;
+unsigned int set_mbox_lock_timeout = 300;
+unsigned int set_mbox_dotlock_change_timeout = 30;
 int set_overwrite_incompatible_index = FALSE;
 unsigned int set_umask = 0077;
 
@@ -154,6 +163,9 @@
 
 static void settings_verify(void)
 {
+	char *const *str;
+	int dotlock_got, fcntl_got, flock_got;
+
 	get_login_uid();
 
 	if (access(set_login_executable, X_OK) < 0) {
@@ -188,6 +200,35 @@
 	    set_first_valid_gid > set_last_valid_gid)
 		i_fatal("first_valid_gid can't be larger than last_valid_gid");
 
+	dotlock_got = fcntl_got = flock_got = FALSE;
+	for (str = t_strsplit(set_mbox_locks, " "); *str != NULL; str++) {
+		if (strcasecmp(*str, "dotlock") == 0)
+			dotlock_got = TRUE;
+		else if (strcasecmp(*str, "fcntl") == 0)
+			fcntl_got = TRUE;
+		else if (strcasecmp(*str, "flock") == 0)
+			flock_got = TRUE;
+		else
+			i_fatal("mbox_locks: Invalid value %s", *str);
+	}
+
+#ifndef HAVE_FLOCK
+	if (fcntl_got && !dotlock_got && !flock_got) {
+		i_fatal("mbox_locks: Only flock selected, "
+			"and flock() isn't supported in this system");
+	}
+	flock_got = FALSE;
+#endif
+
+	if (!dotlock_got && !fcntl_got && !flock_got)
+		i_fatal("mbox_locks: No mbox locking methods selected");
+
+	if (dotlock_got && !set_mbox_read_dotlock && !fcntl_got && !flock_got) {
+		i_warning("mbox_locks: Only dotlock selected, forcing "
+			  "mbox_read_dotlock = yes to avoid corruption.");
+                set_mbox_read_dotlock = TRUE;
+	}
+
 	auth_settings_verify();
 }
 
--- a/src/master/settings.h	Thu Nov 21 16:56:07 2002 +0200
+++ b/src/master/settings.h	Thu Nov 21 22:13:32 2002 +0200
@@ -45,6 +45,10 @@
 extern int set_mail_save_crlf;
 extern int set_maildir_copy_with_hardlinks;
 extern int set_maildir_check_content_changes;
+extern char *set_mbox_locks;
+extern int set_mbox_read_dotlock;
+extern unsigned int set_mbox_lock_timeout;
+extern unsigned int set_mbox_dotlock_change_timeout;
 extern int set_overwrite_incompatible_index;
 extern unsigned int set_umask;