dovecot-2.2: lib: Added file_create_locked()

dovecot at dovecot.org dovecot at dovecot.org
Sat May 23 20:43:59 UTC 2015


details:   http://hg.dovecot.org/dovecot-2.2/rev/78bbfe4e4e8e
changeset: 18740:78bbfe4e4e8e
user:      Timo Sirainen <tss at iki.fi>
date:      Sat May 23 16:41:34 2015 -0400
description:
lib: Added file_create_locked()

diffstat:

 src/lib/Makefile.am          |    2 +
 src/lib/file-create-locked.c |  126 +++++++++++++++++++++++++++++++++++++++++++
 src/lib/file-create-locked.h |   29 +++++++++
 3 files changed, 157 insertions(+), 0 deletions(-)

diffs (182 lines):

diff -r b23a19faf304 -r 78bbfe4e4e8e src/lib/Makefile.am
--- a/src/lib/Makefile.am	Fri May 22 19:07:56 2015 -0400
+++ b/src/lib/Makefile.am	Sat May 23 16:41:34 2015 -0400
@@ -35,6 +35,7 @@
 	fdatasync-path.c \
 	fdpass.c \
 	file-cache.c \
+	file-create-locked.c \
 	file-copy.c \
 	file-dotlock.c \
 	file-lock.c \
@@ -171,6 +172,7 @@
 	fdatasync-path.h \
 	fdpass.h \
 	file-cache.h \
+	file-create-locked.h \
 	file-copy.h \
 	file-dotlock.h \
 	file-lock.h \
diff -r b23a19faf304 -r 78bbfe4e4e8e src/lib/file-create-locked.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/file-create-locked.c	Sat May 23 16:41:34 2015 -0400
@@ -0,0 +1,126 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "safe-mkstemp.h"
+#include "file-lock.h"
+#include "file-create-locked.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#define MAX_RETRY_COUNT 1000
+
+static int
+try_lock_existing(int fd, const char *path,
+		  const struct file_create_settings *set,
+		  struct file_lock **lock_r, const char **error_r)
+{
+	struct stat st1, st2;
+
+	if (fstat(fd, &st1) < 0) {
+		*error_r = t_strdup_printf("fstat(%s) failed: %m", path);
+		return -1;
+	}
+	if (file_wait_lock_error(fd, path, F_WRLCK, FILE_LOCK_METHOD_FCNTL,
+				 set->lock_timeout_secs, lock_r, error_r) <= 0)
+		return -1;
+	if (stat(path, &st2) == 0) {
+		return st1.st_ino == st2.st_ino &&
+			CMP_DEV_T(st1.st_dev, st2.st_dev) ? 1 : 0;
+	} else if (errno == ENOENT) {
+		return 0;
+	} else {
+		*error_r = t_strdup_printf("stat(%s) failed: %m", path);
+		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 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);
+	if (fd == -1) {
+		*error_r = t_strdup_printf("safe_mkstemp(%s) failed: %m", path);
+		return -1;
+	}
+	if (file_try_lock_error(fd, str_c(temp_path), F_WRLCK,
+				FILE_LOCK_METHOD_FCNTL,
+				lock_r, error_r) <= 0) {
+	} else if (link(str_c(temp_path), path) < 0) {
+		if (errno == EEXIST) {
+			/* just created by somebody else */
+			ret = 0;
+		} else if (errno == ENOENT) {
+			/* our temp file was just deleted by somebody else,
+			   retry creating it. */
+			ret = 0;
+		} else {
+			*error_r = t_strdup_printf("link(%s, %s) failed: %m",
+						   str_c(temp_path), path);
+		}
+	} else {
+		*fd_r = fd;
+		return 1;
+	}
+	orig_errno = errno;
+	i_close_fd(&fd);
+	if (unlink(str_c(temp_path)) < 0)
+		i_error("unlink(%s) failed: %m", str_c(temp_path));
+	errno = orig_errno;
+	return ret;
+}
+
+int file_create_locked(const char *path, const struct file_create_settings *set,
+		       struct file_lock **lock_r, bool *created_r,
+		       const char **error_r)
+{
+	unsigned int i;
+	int fd, ret;
+
+	for (i = 0; i < MAX_RETRY_COUNT; i++) {
+		fd = open(path, O_RDWR);
+		if (fd != -1) {
+			ret = try_lock_existing(fd, path, set, lock_r, error_r);
+			if (ret < 0) {
+				i_close_fd(&fd);
+				return -1;
+			}
+			if (ret > 0) {
+				/* successfully locked an existing file */
+				*created_r = FALSE;
+				return fd;
+			}
+		} else if (errno != ENOENT) {
+			*error_r = t_strdup_printf("open(%s) failed: %m", path);
+			return -1;
+		} else {
+			/* try to create the file */
+			ret = try_create_new(path, set, &fd, lock_r, error_r);
+			if (ret < 0)
+				return -1;
+			if (ret > 0) {
+				/* successfully created a new locked file */
+				*created_r = TRUE;
+				return fd;
+			}
+			/* the file was just created - try again opening and
+			   locking it */
+		}
+	}
+	*error_r = t_strdup_printf("Creating a locked file %s keeps failing", path);
+	errno = EINVAL;
+	return -1;
+}
diff -r b23a19faf304 -r 78bbfe4e4e8e src/lib/file-create-locked.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/file-create-locked.h	Sat May 23 16:41:34 2015 -0400
@@ -0,0 +1,29 @@
+#ifndef FILE_CREATE_LOCKED_H
+#define FILE_CREATE_LOCKED_H
+
+struct file_lock;
+
+struct file_create_settings {
+	/* 0 = try locking without waiting */
+	unsigned int lock_timeout_secs;
+
+	/* 0 = 0600 */
+	int mode;
+	/* 0 = default */
+	uid_t uid;
+	/* 0 = default */
+	gid_t gid;
+	const char *gid_origin;
+};
+
+/* Either open an existing file and lock it, or create the file locked.
+   The creation is done by creating a temp file and link()ing it to path.
+   If link() fails, opening is retried again. Returns fd on success,
+   -1 on error. errno is preserved for the last failed syscall, so most
+   importantly ENOENT could mean that the directory doesn't exist and EAGAIN
+   means locking timed out. */
+int file_create_locked(const char *path, const struct file_create_settings *set,
+		       struct file_lock **lock_r, bool *created_r,
+		       const char **error_r);
+
+#endif


More information about the dovecot-cvs mailing list