[dovecot-cvs] dovecot/src/lib file-dotlock.c,1.5,1.6 file-dotlock.h,1.2,1.3 randgen.c,1.10,1.11

cras at procontrol.fi cras at procontrol.fi
Sun Jul 6 00:33:20 EEST 2003


Update of /home/cvs/dovecot/src/lib
In directory danu:/tmp/cvs-serv27940/lib

Modified Files:
	file-dotlock.c file-dotlock.h randgen.c 
Log Message:
Moved all dotlocking code to lib/. Also we now use temp file + link() rather
than rely on working O_EXCL.



Index: file-dotlock.c
===================================================================
RCS file: /home/cvs/dovecot/src/lib/file-dotlock.c,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- file-dotlock.c	17 Apr 2003 14:20:47 -0000	1.5
+++ file-dotlock.c	5 Jul 2003 20:33:18 -0000	1.6
@@ -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,57 +146,99 @@
 	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 @@
 	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;
+		if (do_wait) {
+			usleep(LOCK_RANDOM_USLEEP_TIME);
+			do_wait = FALSE;
+		}
 
-				last_notify = now;
-				change_secs = now - lock_info.last_change;
-				wait_left = max_wait_time - now;
+		ret = check_lock(now, &lock_info);
+		if (ret < 0)
+			break;
 
-				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 (ret == 1) {
+			if (checkonly)
+				break;
 
-			usleep(LOCK_RANDOM_USLEEP_TIME);
-			break;
-		default:
-			if (checkonly ||
-			    try_create_lock(lock_path, dotlock_r) > 0)
-				return 1;
+			ret = try_create_lock(&lock_info, temp_prefix);
+			if (ret != 0)
+				break;
+		}
 
-			if (errno != EEXIST) {
-				i_error("open(%s) failed: %m", lock_path);
-				return -1;
+		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);
 
@@ -301,4 +394,88 @@
 	}
 
 	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: file-dotlock.h
===================================================================
RCS file: /home/cvs/dovecot/src/lib/file-dotlock.h,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- file-dotlock.h	14 Feb 2003 12:56:32 -0000	1.2
+++ file-dotlock.h	5 Jul 2003 20:33:18 -0000	1.3
@@ -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),
@@ -31,5 +35,19 @@
 /* Delete the dotlock file. Returns 1 if successful, 0 if the file was already
    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: randgen.c
===================================================================
RCS file: /home/cvs/dovecot/src/lib/randgen.c,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -d -r1.10 -r1.11
--- randgen.c	18 Jun 2003 01:14:14 -0000	1.10
+++ randgen.c	5 Jul 2003 20:33:18 -0000	1.11
@@ -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



More information about the dovecot-cvs mailing list