bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen#include "lib.h"
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen#include "str.h"
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen#include "safe-mkstemp.h"
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen#include "mkdir-parents.h"
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen#include "file-lock.h"
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen#include "file-create-locked.h"
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen#include <unistd.h>
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen#include <fcntl.h>
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen#include <sys/stat.h>
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen/* Try mkdir() + lock creation multiple times. This allows the lock file
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen creation to work even while the directory is simultaneously being
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen rmdir()ed. */
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen#define MAX_MKDIR_COUNT 10
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen#define MAX_RETRY_COUNT 1000
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainenstatic int
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainentry_lock_existing(int fd, const char *path,
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen const struct file_create_settings *set,
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen struct file_lock **lock_r, const char **error_r)
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen{
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen struct stat st1, st2;
799b19bb4f69f7a5491b4c4c3fcfc7b346297762Timo Sirainen int ret;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen if (fstat(fd, &st1) < 0) {
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen *error_r = t_strdup_printf("fstat(%s) failed: %m", path);
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen return -1;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen }
715fa0c143cf457c94bbac7554767c28413cf680Timo Sirainen if (file_wait_lock_error(fd, path, F_WRLCK, set->lock_method,
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen set->lock_timeout_secs, lock_r, error_r) <= 0)
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen return -1;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen if (stat(path, &st2) == 0) {
799b19bb4f69f7a5491b4c4c3fcfc7b346297762Timo Sirainen ret = st1.st_ino == st2.st_ino &&
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen CMP_DEV_T(st1.st_dev, st2.st_dev) ? 1 : 0;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen } else if (errno == ENOENT) {
799b19bb4f69f7a5491b4c4c3fcfc7b346297762Timo Sirainen ret = 0;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen } else {
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen *error_r = t_strdup_printf("stat(%s) failed: %m", path);
799b19bb4f69f7a5491b4c4c3fcfc7b346297762Timo Sirainen ret = -1;
799b19bb4f69f7a5491b4c4c3fcfc7b346297762Timo Sirainen }
799b19bb4f69f7a5491b4c4c3fcfc7b346297762Timo Sirainen if (ret <= 0) {
799b19bb4f69f7a5491b4c4c3fcfc7b346297762Timo Sirainen /* the fd is closed next - no need to unlock */
799b19bb4f69f7a5491b4c4c3fcfc7b346297762Timo Sirainen file_lock_free(lock_r);
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen }
799b19bb4f69f7a5491b4c4c3fcfc7b346297762Timo Sirainen return ret;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen}
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainenstatic int
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainentry_mkdir(const char *path, const struct file_create_settings *set,
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen const char **error_r)
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen{
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen uid_t uid = set->mkdir_uid != 0 ? set->mkdir_uid : (uid_t)-1;
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen gid_t gid = set->mkdir_gid != 0 ? set->mkdir_gid : (gid_t)-1;
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen const char *p = strrchr(path, '/');
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen if (p == NULL)
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen return 0;
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen const char *dir = t_strdup_until(path, p);
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen int ret;
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen if (uid != (uid_t)-1)
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen ret = mkdir_parents_chown(dir, set->mkdir_mode, uid, gid);
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen else {
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen ret = mkdir_parents_chgrp(dir, set->mkdir_mode,
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen gid, set->gid_origin);
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen }
2eeca70fd1d9543ea856506e25ee9daf0efc33c7Timo Sirainen if (ret < 0 && errno != EEXIST) {
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen *error_r = t_strdup_printf("mkdir_parents(%s) failed: %m", dir);
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen return -1;
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen }
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen return 1;
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen}
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainenstatic int
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainentry_create_new(const char *path, const struct file_create_settings *set,
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen int *fd_r, struct file_lock **lock_r, const char **error_r)
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen{
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen string_t *temp_path = t_str_new(128);
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen int fd, orig_errno, ret = 1;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen int mode = set->mode != 0 ? set->mode : 0600;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen uid_t uid = set->uid != 0 ? set->uid : (uid_t)-1;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen uid_t gid = set->gid != 0 ? set->gid : (gid_t)-1;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen str_append(temp_path, path);
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen for (unsigned int i = 0; ret > 0; i++) {
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen if (uid != (uid_t)-1)
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen fd = safe_mkstemp(temp_path, mode, uid, gid);
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen else
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen fd = safe_mkstemp_group(temp_path, mode, gid, set->gid_origin);
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen if (fd != -1 || errno != ENOENT || set->mkdir_mode == 0 ||
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen i >= MAX_MKDIR_COUNT)
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen break;
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen int orig_errno = errno;
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen if ((ret = try_mkdir(path, set, error_r)) < 0)
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen return -1;
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen errno = orig_errno;
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen }
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen if (fd == -1) {
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen *error_r = t_strdup_printf("safe_mkstemp(%s) failed: %m", path);
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen return -1;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen }
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen
d6e3deea85740b3af60e9f5bab32e98beba12d93Timo Sirainen ret = -1;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen if (file_try_lock_error(fd, str_c(temp_path), F_WRLCK,
715fa0c143cf457c94bbac7554767c28413cf680Timo Sirainen set->lock_method, lock_r, error_r) <= 0) {
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen } else if (link(str_c(temp_path), path) < 0) {
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen if (errno == EEXIST) {
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen /* just created by somebody else */
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen ret = 0;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen } else if (errno == ENOENT) {
ee0a6db8fe59b301c58b99e073687ae049d29a53Timo Sirainen /* nobody should be deleting the temp file unless the
ee0a6db8fe59b301c58b99e073687ae049d29a53Timo Sirainen entire directory is deleted. */
ee0a6db8fe59b301c58b99e073687ae049d29a53Timo Sirainen *error_r = t_strdup_printf(
ee0a6db8fe59b301c58b99e073687ae049d29a53Timo Sirainen "Temporary file %s was unexpectedly deleted",
ee0a6db8fe59b301c58b99e073687ae049d29a53Timo Sirainen str_c(temp_path));
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen } else {
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen *error_r = t_strdup_printf("link(%s, %s) failed: %m",
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen str_c(temp_path), path);
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen }
33c7e05213e802495887c2d4a94c22c68d4d0c84Timo Sirainen file_lock_free(lock_r);
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen } else {
2605b390b4f622faf86d7c27e450b62f3a16b2dcTimo Sirainen file_lock_set_path(*lock_r, path);
46b823ac3bce2c0f9f0fc73911e48d3a77b04fbeTimo Sirainen i_unlink_if_exists(str_c(temp_path));
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen *fd_r = fd;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen return 1;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen }
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen orig_errno = errno;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen i_close_fd(&fd);
46b823ac3bce2c0f9f0fc73911e48d3a77b04fbeTimo Sirainen i_unlink_if_exists(str_c(temp_path));
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen errno = orig_errno;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen return ret;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen}
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainenint file_create_locked(const char *path, const struct file_create_settings *set,
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen struct file_lock **lock_r, bool *created_r,
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen const char **error_r)
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen{
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen unsigned int i;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen int fd, ret;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen for (i = 0; i < MAX_RETRY_COUNT; i++) {
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen fd = open(path, O_RDWR);
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen if (fd != -1) {
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen ret = try_lock_existing(fd, path, set, lock_r, error_r);
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen if (ret > 0) {
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen /* successfully locked an existing file */
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen *created_r = FALSE;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen return fd;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen }
799b19bb4f69f7a5491b4c4c3fcfc7b346297762Timo Sirainen i_close_fd(&fd);
799b19bb4f69f7a5491b4c4c3fcfc7b346297762Timo Sirainen if (ret < 0)
799b19bb4f69f7a5491b4c4c3fcfc7b346297762Timo Sirainen return -1;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen } else if (errno != ENOENT) {
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen *error_r = t_strdup_printf("open(%s) failed: %m", path);
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen return -1;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen } else {
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen /* try to create the file */
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen ret = try_create_new(path, set, &fd, lock_r, error_r);
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen if (ret < 0)
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen return -1;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen if (ret > 0) {
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen /* successfully created a new locked file */
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen *created_r = TRUE;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen return fd;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen }
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen /* the file was just created - try again opening and
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen locking it */
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen }
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen }
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen *error_r = t_strdup_printf("Creating a locked file %s keeps failing", path);
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen errno = EINVAL;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen return -1;
a63907c14bd96eec1493eb893a043137c1799485Timo Sirainen}