Skip to content

Commit

Permalink
lib: file_create_locked() - Add settings to mkdir() missing parent di…
Browse files Browse the repository at this point in the history
…rectories
  • Loading branch information
sirainen authored and GitLab committed Jun 28, 2017
1 parent 9a18972 commit d6e3dee
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 5 deletions.
52 changes: 47 additions & 5 deletions src/lib/file-create-locked.c
Expand Up @@ -3,13 +3,18 @@
#include "lib.h"
#include "str.h"
#include "safe-mkstemp.h"
#include "mkdir-parents.h"
#include "file-lock.h"
#include "file-create-locked.h"

#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

/* Try mkdir() + lock creation multiple times. This allows the lock file
creation to work even while the directory is simultaneously being
rmdir()ed. */
#define MAX_MKDIR_COUNT 10
#define MAX_RETRY_COUNT 1000

static int
Expand Down Expand Up @@ -43,25 +48,62 @@ try_lock_existing(int fd, const char *path,
return ret;
}

static int
try_mkdir(const char *path, const struct file_create_settings *set,
const char **error_r)
{
uid_t uid = set->mkdir_uid != 0 ? set->mkdir_uid : (uid_t)-1;
gid_t gid = set->mkdir_gid != 0 ? set->mkdir_gid : (gid_t)-1;
const char *p = strrchr(path, '/');
if (p == NULL)
return 0;

const char *dir = t_strdup_until(path, p);
int ret;
if (uid != (uid_t)-1)
ret = mkdir_parents_chown(dir, set->mkdir_mode, uid, gid);
else {
ret = mkdir_parents_chgrp(dir, set->mkdir_mode,
gid, set->gid_origin);
}
if (ret < 0) {
*error_r = t_strdup_printf("mkdir_parents(%s) failed: %m", dir);
return -1;
}
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 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);
for (unsigned int i = 0; ret > 0; i++) {
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 || errno != ENOENT || set->mkdir_mode == 0 ||
i >= MAX_MKDIR_COUNT)
break;

int orig_errno = errno;
if ((ret = try_mkdir(path, set, error_r)) < 0)
return -1;
errno = orig_errno;
}
if (fd == -1) {
*error_r = t_strdup_printf("safe_mkstemp(%s) failed: %m", path);
return -1;
}

ret = -1;
if (file_try_lock_error(fd, str_c(temp_path), F_WRLCK,
set->lock_method, lock_r, error_r) <= 0) {
} else if (link(str_c(temp_path), path) < 0) {
Expand Down
11 changes: 11 additions & 0 deletions src/lib/file-create-locked.h
Expand Up @@ -15,6 +15,17 @@ struct file_create_settings {
/* 0 = default */
gid_t gid;
const char *gid_origin;

/* If parent directory doesn't exist, mkdir() it with this mode.
0 = don't mkdir(). The parent directories are assumed to be
potentially rmdir() simultaneously, so the mkdir()+locking may be
attempted multiple times. */
int mkdir_mode;
/* 0 = default */
uid_t mkdir_uid;
/* 0 = default */
gid_t mkdir_gid;
const char *mkdir_gid_origin;
};

/* Either open an existing file and lock it, or create the file locked.
Expand Down
45 changes: 45 additions & 0 deletions src/lib/test-file-create-locked.c
@@ -1,6 +1,7 @@
/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */

#include "test-lib.h"
#include "unlink-directory.h"
#include "file-create-locked.h"

#include <fcntl.h>
Expand Down Expand Up @@ -85,7 +86,51 @@ static void test_file_create_locked_basic(void)
test_end();
}

static void test_file_create_locked_mkdir(void)
{
struct file_create_settings set = {
.lock_timeout_secs = 0,
.lock_method = FILE_LOCK_METHOD_FCNTL,
};
const char *path = ".test-file-create-locked";
struct file_lock *lock;
const char *error, *dir;
bool created;
int fd;

test_begin("file_create_locked() with mkdir");

dir = ".test-temp-file-create-locked-dir";
if (unlink_directory(dir, UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0)
i_fatal("unlink_directory(%s) failed: %s", dir, error);
path = t_strconcat(dir, "/lockfile", NULL);

/* try without mkdir enabled */
test_assert(file_create_locked(path, &set, &lock, &created, &error) == -1);
test_assert(errno == ENOENT);

/* try with mkdir enabled */
set.mkdir_mode = 0700;
fd = file_create_locked(path, &set, &lock, &created, &error);
test_assert(fd > 0);
test_assert(created);
i_close_fd(&fd);

struct stat st;
if (stat(dir, &st) < 0)
i_error("stat(%s) failed: %m", dir);
test_assert((st.st_mode & 0777) == 0700);
i_unlink(path);
file_lock_free(&lock);

if (unlink_directory(dir, UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0)
i_fatal("unlink_directory(%s) failed: %s", dir, error);

test_end();
}

void test_file_create_locked(void)
{
test_file_create_locked_basic();
test_file_create_locked_mkdir();
}

0 comments on commit d6e3dee

Please sign in to comment.