Mercurial > dovecot > core-2.2
changeset 18740:78bbfe4e4e8e
lib: Added file_create_locked()
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Sat, 23 May 2015 16:41:34 -0400 |
parents | b23a19faf304 |
children | 350f11821be2 |
files | src/lib/Makefile.am src/lib/file-create-locked.c src/lib/file-create-locked.h |
diffstat | 3 files changed, 157 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- 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 \
--- /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; +}
--- /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