summaryrefslogtreecommitdiffstats
path: root/src/lib-fs/fs-posix.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-fs/fs-posix.c
parentInitial commit. (diff)
downloaddovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz
dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/lib-fs/fs-posix.c1028
1 files changed, 1028 insertions, 0 deletions
diff --git a/src/lib-fs/fs-posix.c b/src/lib-fs/fs-posix.c
new file mode 100644
index 0000000..657938e
--- /dev/null
+++ b/src/lib-fs/fs-posix.c
@@ -0,0 +1,1028 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "str.h"
+#include "guid.h"
+#include "istream.h"
+#include "ostream.h"
+#include "safe-mkstemp.h"
+#include "mkdir-parents.h"
+#include "write-full.h"
+#include "file-lock.h"
+#include "file-dotlock.h"
+#include "time-util.h"
+#include "fs-api-private.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#define FS_POSIX_DOTLOCK_STALE_TIMEOUT_SECS (60*10)
+#define MAX_MKDIR_RETRY_COUNT 5
+#define FS_DEFAULT_MODE 0600
+
+enum fs_posix_lock_method {
+ FS_POSIX_LOCK_METHOD_FLOCK,
+ FS_POSIX_LOCK_METHOD_DOTLOCK
+};
+
+struct posix_fs {
+ struct fs fs;
+ char *temp_file_prefix, *root_path, *path_prefix;
+ size_t temp_file_prefix_len;
+ enum fs_posix_lock_method lock_method;
+ mode_t mode;
+ bool mode_auto;
+ bool have_dirs;
+ bool disable_fsync;
+ bool accurate_mtime;
+};
+
+struct posix_fs_file {
+ struct fs_file file;
+ char *temp_path, *full_path;
+ int fd;
+ enum fs_open_mode open_mode;
+ enum fs_open_flags open_flags;
+
+ buffer_t *write_buf;
+
+ bool seek_to_beginning;
+};
+
+struct posix_fs_lock {
+ struct fs_lock lock;
+ struct file_lock *file_lock;
+ struct dotlock *dotlock;
+};
+
+struct posix_fs_iter {
+ struct fs_iter iter;
+ char *path;
+ DIR *dir;
+ int err;
+};
+
+static struct fs *fs_posix_alloc(void)
+{
+ struct posix_fs *fs;
+
+ fs = i_new(struct posix_fs, 1);
+ fs->fs = fs_class_posix;
+ return &fs->fs;
+}
+
+static int
+fs_posix_init(struct fs *_fs, const char *args, const struct fs_settings *set,
+ const char **error_r)
+{
+ struct posix_fs *fs = container_of(_fs, struct posix_fs, fs);
+ const char *const *tmp;
+
+ fs->temp_file_prefix = set->temp_file_prefix != NULL ?
+ i_strdup(set->temp_file_prefix) : i_strdup("temp.dovecot.");
+ fs->temp_file_prefix_len = strlen(fs->temp_file_prefix);
+ fs->root_path = i_strdup(set->root_path);
+ fs->fs.set.temp_file_prefix = fs->temp_file_prefix;
+ fs->fs.set.root_path = fs->root_path;
+
+ fs->lock_method = FS_POSIX_LOCK_METHOD_FLOCK;
+ fs->mode = FS_DEFAULT_MODE;
+
+ tmp = t_strsplit_spaces(args, ":");
+ for (; *tmp != NULL; tmp++) {
+ const char *arg = *tmp;
+
+ if (strcmp(arg, "lock=flock") == 0)
+ fs->lock_method = FS_POSIX_LOCK_METHOD_FLOCK;
+ else if (strcmp(arg, "lock=dotlock") == 0)
+ fs->lock_method = FS_POSIX_LOCK_METHOD_DOTLOCK;
+ else if (str_begins(arg, "prefix=")) {
+ i_free(fs->path_prefix);
+ fs->path_prefix = i_strdup(arg + 7);
+ } else if (strcmp(arg, "mode=auto") == 0) {
+ fs->mode_auto = TRUE;
+ } else if (strcmp(arg, "dirs") == 0) {
+ fs->have_dirs = TRUE;
+ } else if (strcmp(arg, "no-fsync") == 0) {
+ fs->disable_fsync = TRUE;
+ } else if (strcmp(arg, "accurate-mtime") == 0) {
+ fs->accurate_mtime = TRUE;
+ } else if (str_begins(arg, "mode=")) {
+ unsigned int mode;
+ if (str_to_uint_oct(arg+5, &mode) < 0) {
+ *error_r = t_strdup_printf("Invalid mode value: %s", arg+5);
+ return -1;
+ }
+ fs->mode = mode & 0666;
+ if (fs->mode == 0) {
+ *error_r = t_strdup_printf("Invalid mode: %s", arg+5);
+ return -1;
+ }
+ } else {
+ *error_r = t_strdup_printf("Unknown arg '%s'", arg);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void fs_posix_free(struct fs *_fs)
+{
+ struct posix_fs *fs = container_of(_fs, struct posix_fs, fs);
+
+ i_free(fs->temp_file_prefix);
+ i_free(fs->root_path);
+ i_free(fs->path_prefix);
+ i_free(fs);
+}
+
+static enum fs_properties fs_posix_get_properties(struct fs *_fs)
+{
+ struct posix_fs *fs = container_of(_fs, struct posix_fs, fs);
+ enum fs_properties props =
+ FS_PROPERTY_LOCKS | FS_PROPERTY_FASTCOPY | FS_PROPERTY_RENAME |
+ FS_PROPERTY_STAT | FS_PROPERTY_ITER | FS_PROPERTY_RELIABLEITER;
+
+ /* FS_PROPERTY_DIRECTORIES is not returned normally because fs_delete()
+ automatically rmdir()s parents. For backwards compatibility
+ (especially with SIS code) we'll do it that way, but optionally with
+ "dirs" parameter enable them. This is especially important to be
+ able to use doveadm fs commands to delete empty directories. */
+ if (fs->have_dirs)
+ props |= FS_PROPERTY_DIRECTORIES;
+ return props;
+}
+
+static int
+fs_posix_get_mode(struct posix_fs_file *file, const char *path, mode_t *mode_r)
+{
+ struct posix_fs *fs = (struct posix_fs *)file->file.fs;
+ struct stat st;
+ const char *p;
+
+ *mode_r = fs->mode;
+
+ /* This function is used to get mode of the parent directory, so path
+ is never the same as file->path. The file is used just to set the
+ errors. */
+ while (stat(path, &st) < 0) {
+ if (errno != ENOENT) {
+ fs_set_error_errno(file->file.event,
+ "stat(%s) failed: %m", path);
+ return -1;
+ }
+ p = strrchr(path, '/');
+ if (p != NULL)
+ path = t_strdup_until(path, p);
+ else if (strcmp(path, ".") != 0)
+ path = ".";
+ else
+ return 0;
+ }
+ if ((st.st_mode & S_ISGID) != 0) {
+ /* setgid set - copy mode from parent */
+ *mode_r = st.st_mode & 0666;
+ }
+ return 0;
+}
+
+static int fs_posix_mkdir_parents(struct posix_fs_file *file, const char *path)
+{
+ const char *dir, *fname;
+ mode_t mode, dir_mode;
+
+ fname = strrchr(path, '/');
+ if (fname == NULL)
+ return 1;
+ dir = t_strdup_until(path, fname);
+
+ if (fs_posix_get_mode(file, dir, &mode) < 0)
+ return -1;
+ dir_mode = mode;
+ if ((dir_mode & 0600) != 0) dir_mode |= 0100;
+ if ((dir_mode & 0060) != 0) dir_mode |= 0010;
+ if ((dir_mode & 0006) != 0) dir_mode |= 0001;
+
+ if (mkdir_parents(dir, dir_mode) == 0)
+ return 0;
+ else if (errno == EEXIST)
+ return 1;
+ else {
+ fs_set_error_errno(file->file.event,
+ "mkdir_parents(%s) failed: %m", dir);
+ return -1;
+ }
+}
+
+static int fs_posix_rmdir_parents(struct posix_fs_file *file, const char *path)
+{
+ struct posix_fs *fs = (struct posix_fs *)file->file.fs;
+ const char *p;
+
+ if (fs->have_dirs)
+ return 0;
+ if (fs->root_path == NULL && fs->path_prefix == NULL)
+ return 0;
+
+ while ((p = strrchr(path, '/')) != NULL) {
+ path = t_strdup_until(path, p);
+ if ((fs->root_path != NULL && strcmp(path, fs->root_path) == 0) ||
+ (fs->path_prefix != NULL && str_begins(fs->path_prefix, path)))
+ break;
+ if (rmdir(path) == 0) {
+ /* success, continue to parent */
+ } else if (errno == ENOTEMPTY || errno == EEXIST) {
+ /* there are other entries in this directory */
+ break;
+ } else if (errno == EBUSY || errno == ENOENT) {
+ /* some other not-unexpected error */
+ break;
+ } else {
+ fs_set_error_errno(file->file.event,
+ "rmdir(%s) failed: %m", path);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int fs_posix_create(struct posix_fs_file *file)
+{
+ struct posix_fs *fs = container_of(file->file.fs, struct posix_fs, fs);
+ string_t *str = t_str_new(256);
+ const char *slash;
+ unsigned int try_count = 0;
+ mode_t mode;
+ int fd;
+
+ i_assert(file->temp_path == NULL);
+
+ if ((slash = strrchr(file->full_path, '/')) != NULL) {
+ str_append_data(str, file->full_path, slash - file->full_path);
+ if (fs_posix_get_mode(file, str_c(str), &mode) < 0)
+ return -1;
+ str_append_c(str, '/');
+ } else {
+ if (fs_posix_get_mode(file, ".", &mode) < 0)
+ return -1;
+ }
+ str_append(str, fs->temp_file_prefix);
+
+ fd = safe_mkstemp_hostpid(str, mode, (uid_t)-1, (gid_t)-1);
+ while (fd == -1 && errno == ENOENT &&
+ try_count <= MAX_MKDIR_RETRY_COUNT) {
+ if (fs_posix_mkdir_parents(file, str_c(str)) < 0)
+ return -1;
+ fd = safe_mkstemp_hostpid(str, mode, (uid_t)-1, (gid_t)-1);
+ try_count++;
+ }
+ if (fd == -1) {
+ fs_set_error_errno(file->file.event,
+ "safe_mkstemp(%s) failed: %m", str_c(str));
+ return -1;
+ }
+ file->temp_path = i_strdup(str_c(str));
+ return fd;
+}
+
+static int fs_posix_open(struct posix_fs_file *file)
+{
+ const char *path = file->full_path;
+
+ i_assert(file->fd == -1);
+
+ switch (file->open_mode) {
+ case FS_OPEN_MODE_READONLY:
+ file->fd = open(path, O_RDONLY);
+ if (file->fd == -1)
+ fs_set_error_errno(file->file.event,
+ "open(%s) failed: %m", path);
+ break;
+ case FS_OPEN_MODE_APPEND:
+ file->fd = open(path, O_RDWR | O_APPEND);
+ if (file->fd == -1)
+ fs_set_error_errno(file->file.event,
+ "open(%s) failed: %m", path);
+ break;
+ case FS_OPEN_MODE_CREATE_UNIQUE_128:
+ case FS_OPEN_MODE_CREATE:
+ case FS_OPEN_MODE_REPLACE:
+ T_BEGIN {
+ file->fd = fs_posix_create(file);
+ } T_END;
+ break;
+ }
+ if (file->fd == -1)
+ return -1;
+ return 0;
+}
+
+static struct fs_file *fs_posix_file_alloc(void)
+{
+ struct posix_fs_file *file = i_new(struct posix_fs_file, 1);
+ return &file->file;
+}
+
+static void
+fs_posix_file_init(struct fs_file *_file, const char *path,
+ enum fs_open_mode mode, enum fs_open_flags flags)
+{
+ struct posix_fs_file *file =
+ container_of(_file, struct posix_fs_file, file);
+ struct posix_fs *fs = container_of(_file->fs, struct posix_fs, fs);
+ guid_128_t guid;
+ size_t path_len = strlen(path);
+
+ if (path_len > 0 && path[path_len-1] == '/') {
+ /* deleting "path/" (used e.g. by doveadm fs delete) - strip
+ out the trailing "/" since it doesn't work well with NFS. */
+ path = t_strndup(path, path_len-1);
+ }
+
+ if (mode != FS_OPEN_MODE_CREATE_UNIQUE_128)
+ file->file.path = i_strdup(path);
+ else {
+ guid_128_generate(guid);
+ file->file.path = i_strdup_printf("%s/%s", path,
+ guid_128_to_string(guid));
+ }
+ file->full_path = fs->path_prefix == NULL ? i_strdup(file->file.path) :
+ i_strconcat(fs->path_prefix, file->file.path, NULL);
+ file->open_mode = mode;
+ file->open_flags = flags;
+ file->fd = -1;
+}
+
+static void fs_posix_file_close(struct fs_file *_file)
+{
+ struct posix_fs_file *file =
+ container_of(_file, struct posix_fs_file, file);
+
+ if (file->fd != -1 && file->file.output == NULL) {
+ if (close(file->fd) < 0) {
+ e_error(_file->event, "close(%s) failed: %m",
+ file->full_path);
+ }
+ file->fd = -1;
+ }
+}
+
+static void fs_posix_file_deinit(struct fs_file *_file)
+{
+ struct posix_fs_file *file =
+ container_of(_file, struct posix_fs_file, file);
+
+ i_assert(_file->output == NULL);
+
+ switch (file->open_mode) {
+ case FS_OPEN_MODE_READONLY:
+ case FS_OPEN_MODE_APPEND:
+ break;
+ case FS_OPEN_MODE_CREATE_UNIQUE_128:
+ case FS_OPEN_MODE_CREATE:
+ case FS_OPEN_MODE_REPLACE:
+ if (file->temp_path == NULL)
+ break;
+ /* failed to create/replace this. delete the temp file */
+ if (unlink(file->temp_path) < 0) {
+ e_error(_file->event, "unlink(%s) failed: %m",
+ file->temp_path);
+ }
+ break;
+ }
+
+ fs_file_free(_file);
+ i_free(file->temp_path);
+ i_free(file->full_path);
+ i_free(file->file.path);
+ i_free(file);
+}
+
+static int fs_posix_open_for_read(struct posix_fs_file *file)
+{
+ i_assert(file->file.output == NULL);
+ i_assert(file->temp_path == NULL);
+
+ if (file->fd == -1) {
+ if (fs_posix_open(file) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static bool fs_posix_prefetch(struct fs_file *_file, uoff_t length ATTR_UNUSED)
+{
+ struct posix_fs_file *file =
+ container_of(_file, struct posix_fs_file, file);
+
+ if (fs_posix_open_for_read(file) < 0)
+ return TRUE;
+
+/* HAVE_POSIX_FADVISE alone isn't enough for CentOS 4.9 */
+#if defined(HAVE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
+ if (posix_fadvise(file->fd, 0, length, POSIX_FADV_WILLNEED) < 0) {
+ e_error(_file->event, "posix_fadvise(%s) failed: %m", file->full_path);
+ return TRUE;
+ }
+#endif
+ return FALSE;
+}
+
+static ssize_t fs_posix_read(struct fs_file *_file, void *buf, size_t size)
+{
+ struct posix_fs_file *file =
+ container_of(_file, struct posix_fs_file, file);
+ ssize_t ret;
+
+ if (fs_posix_open_for_read(file) < 0)
+ return -1;
+
+ if (file->seek_to_beginning) {
+ file->seek_to_beginning = FALSE;
+ if (lseek(file->fd, 0, SEEK_SET) < 0) {
+ fs_set_error_errno(_file->event,
+ "lseek(%s, 0) failed: %m",
+ file->full_path);
+ return -1;
+ }
+ }
+
+ ret = read(file->fd, buf, size);
+ if (ret < 0)
+ fs_set_error_errno(_file->event, "read(%s) failed: %m",
+ file->full_path);
+ fs_posix_file_close(_file);
+ return ret;
+}
+
+static struct istream *
+fs_posix_read_stream(struct fs_file *_file, size_t max_buffer_size)
+{
+ struct posix_fs_file *file =
+ container_of(_file, struct posix_fs_file, file);
+ struct istream *input;
+ int fd_dup;
+
+ if (fs_posix_open_for_read(file) < 0)
+ input = i_stream_create_error_str(errno, "%s", fs_file_last_error(_file));
+ else if ((fd_dup = dup(file->fd)) == -1)
+ input = i_stream_create_error_str(errno, "dup() failed: %m");
+ else {
+ /* The stream could live even after the fs_file.
+ Don't use file->fd directly, because the fd may still be
+ used for other purposes. It's especially important for files
+ that were just created. */
+ input = i_stream_create_fd_autoclose(&fd_dup, max_buffer_size);
+ }
+ i_stream_set_name(input, file->full_path);
+ return input;
+}
+
+static void fs_posix_write_rename_if_needed(struct posix_fs_file *file)
+{
+ struct posix_fs *fs = container_of(file->file.fs, struct posix_fs, fs);
+ const char *new_fname;
+
+ new_fname = fs_metadata_find(&file->file.metadata, FS_METADATA_WRITE_FNAME);
+ if (new_fname == NULL)
+ return;
+
+ i_free(file->file.path);
+ file->file.path = i_strdup(new_fname);
+
+ i_free(file->full_path);
+ file->full_path = fs->path_prefix == NULL ? i_strdup(file->file.path) :
+ i_strconcat(fs->path_prefix, file->file.path, NULL);
+}
+
+static int fs_posix_write_finish_link(struct posix_fs_file *file)
+{
+ unsigned int try_count = 0;
+ int ret;
+
+ ret = link(file->temp_path, file->full_path);
+ while (ret < 0 && errno == ENOENT &&
+ try_count <= MAX_MKDIR_RETRY_COUNT) {
+ if (fs_posix_mkdir_parents(file, file->full_path) < 0)
+ return -1;
+ ret = link(file->temp_path, file->full_path);
+ try_count++;
+ }
+ if (ret < 0) {
+ fs_set_error_errno(file->file.event, "link(%s, %s) failed: %m",
+ file->temp_path, file->full_path);
+ }
+ return ret;
+}
+
+static int fs_posix_write_finish(struct posix_fs_file *file)
+{
+ struct posix_fs *fs = container_of(file->file.fs, struct posix_fs, fs);
+ unsigned int try_count = 0;
+ int ret, old_errno;
+
+ if ((file->open_flags & FS_OPEN_FLAG_FSYNC) != 0 &&
+ !fs->disable_fsync) {
+ if (fdatasync(file->fd) < 0) {
+ fs_set_error_errno(file->file.event,
+ "fdatasync(%s) failed: %m",
+ file->full_path);
+ return -1;
+ }
+ }
+ if (fs->accurate_mtime) {
+ /* Linux updates the mtime timestamp only on timer interrupts.
+ This isn't anywhere close to being microsecond precision.
+ If requested, use utimes() to explicitly set a more accurate
+ mtime. */
+ struct timeval tv[2];
+ i_gettimeofday(&tv[0]);
+ tv[1] = tv[0];
+ if ((utimes(file->temp_path, tv)) < 0) {
+ fs_set_error_errno(file->file.event,
+ "utimes(%s) failed: %m",
+ file->temp_path);
+ return -1;
+ }
+ }
+
+ fs_posix_write_rename_if_needed(file);
+ switch (file->open_mode) {
+ case FS_OPEN_MODE_CREATE_UNIQUE_128:
+ case FS_OPEN_MODE_CREATE:
+ ret = fs_posix_write_finish_link(file);
+ old_errno = errno;
+ if (unlink(file->temp_path) < 0) {
+ fs_set_error_errno(file->file.event,
+ "unlink(%s) failed: %m",
+ file->temp_path);
+ }
+ errno = old_errno;
+ if (ret < 0) {
+ fs_posix_file_close(&file->file);
+ i_free_and_null(file->temp_path);
+ return -1;
+ }
+ break;
+ case FS_OPEN_MODE_REPLACE:
+ ret = rename(file->temp_path, file->full_path);
+ while (ret < 0 && errno == ENOENT &&
+ try_count <= MAX_MKDIR_RETRY_COUNT) {
+ if (fs_posix_mkdir_parents(file, file->full_path) < 0)
+ return -1;
+ ret = rename(file->temp_path, file->full_path);
+ try_count++;
+ }
+ if (ret < 0) {
+ fs_set_error_errno(file->file.event,
+ "rename(%s, %s) failed: %m",
+ file->temp_path, file->full_path);
+ return -1;
+ }
+ break;
+ default:
+ i_unreached();
+ }
+ i_free_and_null(file->temp_path);
+ file->seek_to_beginning = TRUE;
+ /* allow opening the file after writing to it */
+ file->open_mode = FS_OPEN_MODE_READONLY;
+ return 0;
+}
+
+static int fs_posix_write(struct fs_file *_file, const void *data, size_t size)
+{
+ struct posix_fs_file *file =
+ container_of(_file, struct posix_fs_file, file);
+ ssize_t ret;
+
+ if (file->fd == -1) {
+ if (fs_posix_open(file) < 0)
+ return -1;
+ i_assert(file->fd != -1);
+ }
+
+ if (file->open_mode != FS_OPEN_MODE_APPEND) {
+ if (write_full(file->fd, data, size) < 0) {
+ fs_set_error_errno(_file->event, "write(%s) failed: %m",
+ file->full_path);
+ return -1;
+ }
+ return fs_posix_write_finish(file);
+ }
+
+ /* atomic append - it should either succeed or fail */
+ ret = write(file->fd, data, size);
+ if (ret < 0) {
+ fs_set_error_errno(_file->event, "write(%s) failed: %m",
+ file->full_path);
+ return -1;
+ }
+ if ((size_t)ret != size) {
+ fs_set_error(_file->event, ENOSPC,
+ "write(%s) returned %zu/%zu",
+ file->full_path, (size_t)ret, size);
+ return -1;
+ }
+ return 0;
+}
+
+static void fs_posix_write_stream(struct fs_file *_file)
+{
+ struct posix_fs_file *file =
+ container_of(_file, struct posix_fs_file, file);
+
+ i_assert(_file->output == NULL);
+
+ if (file->open_mode == FS_OPEN_MODE_APPEND) {
+ file->write_buf = buffer_create_dynamic(default_pool, 1024*32);
+ _file->output = o_stream_create_buffer(file->write_buf);
+ } else if (file->fd == -1 && fs_posix_open(file) < 0) {
+ _file->output = o_stream_create_error_str(errno, "%s",
+ fs_file_last_error(_file));
+ } else {
+ i_assert(file->fd != -1);
+ _file->output = o_stream_create_fd_file(file->fd,
+ UOFF_T_MAX, FALSE);
+ }
+ o_stream_set_name(_file->output, file->full_path);
+}
+
+static int fs_posix_write_stream_finish(struct fs_file *_file, bool success)
+{
+ struct posix_fs_file *file =
+ container_of(_file, struct posix_fs_file, file);
+ int ret = success ? 0 : -1;
+
+ o_stream_destroy(&_file->output);
+
+ switch (file->open_mode) {
+ case FS_OPEN_MODE_APPEND:
+ if (ret == 0) {
+ ret = fs_posix_write(_file, file->write_buf->data,
+ file->write_buf->used);
+ }
+ buffer_free(&file->write_buf);
+ break;
+ case FS_OPEN_MODE_CREATE:
+ case FS_OPEN_MODE_CREATE_UNIQUE_128:
+ case FS_OPEN_MODE_REPLACE:
+ if (ret == 0)
+ ret = fs_posix_write_finish(file);
+ break;
+ case FS_OPEN_MODE_READONLY:
+ i_unreached();
+ }
+ return ret < 0 ? -1 : 1;
+}
+
+static int
+fs_posix_lock(struct fs_file *_file, unsigned int secs, struct fs_lock **lock_r)
+{
+ struct posix_fs_file *file =
+ container_of(_file, struct posix_fs_file, file);
+ struct posix_fs *fs = container_of(_file->fs, struct posix_fs, fs);
+ struct dotlock_settings dotlock_set;
+ struct posix_fs_lock fs_lock, *ret_lock;
+ const char *error;
+ int ret = -1;
+
+ i_zero(&fs_lock);
+ fs_lock.lock.file = _file;
+
+ struct file_lock_settings lock_set = {
+ .lock_method = FILE_LOCK_METHOD_FLOCK,
+ };
+ switch (fs->lock_method) {
+ case FS_POSIX_LOCK_METHOD_FLOCK:
+#ifndef HAVE_FLOCK
+ fs_set_error(_file->event, ENOTSUP,
+ "flock() not supported by OS (for file %s)",
+ file->full_path);
+#else
+ if (secs == 0) {
+ ret = file_try_lock(file->fd, file->full_path, F_WRLCK,
+ &lock_set, &fs_lock.file_lock,
+ &error);
+ } else {
+ ret = file_wait_lock(file->fd, file->full_path, F_WRLCK,
+ &lock_set, secs,
+ &fs_lock.file_lock, &error);
+ }
+ if (ret < 0) {
+ fs_set_error_errno(_file->event, "flock(%s) failed: %s",
+ file->full_path, error);
+ }
+#endif
+ break;
+ case FS_POSIX_LOCK_METHOD_DOTLOCK:
+ i_zero(&dotlock_set);
+ dotlock_set.stale_timeout = FS_POSIX_DOTLOCK_STALE_TIMEOUT_SECS;
+ dotlock_set.use_excl_lock = TRUE;
+ dotlock_set.timeout = secs;
+
+ ret = file_dotlock_create(&dotlock_set, file->full_path,
+ secs == 0 ? 0 :
+ DOTLOCK_CREATE_FLAG_NONBLOCK,
+ &fs_lock.dotlock);
+ if (ret < 0) {
+ fs_set_error_errno(_file->event,
+ "file_dotlock_create(%s) failed: %m",
+ file->full_path);
+ }
+ break;
+ }
+ if (ret <= 0)
+ return ret;
+
+ ret_lock = i_new(struct posix_fs_lock, 1);
+ *ret_lock = fs_lock;
+ *lock_r = &ret_lock->lock;
+ return 1;
+}
+
+static void fs_posix_unlock(struct fs_lock *_lock)
+{
+ struct posix_fs_lock *lock =
+ container_of(_lock, struct posix_fs_lock, lock);
+
+ if (lock->file_lock != NULL)
+ file_unlock(&lock->file_lock);
+ if (lock->dotlock != NULL)
+ file_dotlock_delete(&lock->dotlock);
+ i_free(lock);
+}
+
+static int fs_posix_exists(struct fs_file *_file)
+{
+ struct posix_fs_file *file =
+ container_of(_file, struct posix_fs_file, file);
+ struct stat st;
+
+ if (stat(file->full_path, &st) < 0) {
+ if (errno != ENOENT) {
+ fs_set_error_errno(_file->event, "stat(%s) failed: %m",
+ file->full_path);
+ return -1;
+ }
+ return 0;
+ }
+ return 1;
+}
+
+static int fs_posix_stat(struct fs_file *_file, struct stat *st_r)
+{
+ struct posix_fs_file *file =
+ container_of(_file, struct posix_fs_file, file);
+
+ /* in case output != NULL it means that we're still writing to the file
+ and fs_stat() shouldn't stat the unfinished file. this is done by
+ fs-sis after fs_copy(). */
+ if (file->fd != -1 && _file->output == NULL) {
+ if (fstat(file->fd, st_r) < 0) {
+ fs_set_error_errno(_file->event, "fstat(%s) failed: %m",
+ file->full_path);
+ return -1;
+ }
+ } else {
+ if (stat(file->full_path, st_r) < 0) {
+ fs_set_error_errno(_file->event, "stat(%s) failed: %m",
+ file->full_path);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int fs_posix_copy(struct fs_file *_src, struct fs_file *_dest)
+{
+ struct posix_fs_file *src =
+ container_of(_src, struct posix_fs_file, file);
+ struct posix_fs_file *dest =
+ container_of(_dest, struct posix_fs_file, file);
+ unsigned int try_count = 0;
+ int ret;
+
+ fs_posix_write_rename_if_needed(dest);
+ ret = link(src->full_path, dest->full_path);
+ if (errno == EEXIST && dest->open_mode == FS_OPEN_MODE_REPLACE) {
+ /* destination file already exists - replace it */
+ i_unlink_if_exists(dest->full_path);
+ ret = link(src->full_path, dest->full_path);
+ }
+ while (ret < 0 && errno == ENOENT &&
+ try_count <= MAX_MKDIR_RETRY_COUNT) {
+ if (fs_posix_mkdir_parents(dest, dest->full_path) < 0)
+ return -1;
+ ret = link(src->full_path, dest->full_path);
+ try_count++;
+ }
+ if (ret < 0) {
+ fs_set_error_errno(_src->event, "link(%s, %s) failed: %m",
+ src->full_path, dest->full_path);
+ return -1;
+ }
+ return 0;
+}
+
+static int fs_posix_rename(struct fs_file *_src, struct fs_file *_dest)
+{
+ struct posix_fs_file *src =
+ container_of(_src, struct posix_fs_file, file);
+ struct posix_fs_file *dest =
+ container_of(_dest, struct posix_fs_file, file);
+ unsigned int try_count = 0;
+ int ret;
+
+ ret = rename(src->full_path, dest->full_path);
+ while (ret < 0 && errno == ENOENT &&
+ try_count <= MAX_MKDIR_RETRY_COUNT) {
+ if (fs_posix_mkdir_parents(dest, dest->full_path) < 0)
+ return -1;
+ ret = rename(src->full_path, dest->full_path);
+ try_count++;
+ }
+ if (ret < 0) {
+ fs_set_error_errno(_src->event, "rename(%s, %s) failed: %m",
+ src->full_path, dest->full_path);
+ return -1;
+ }
+ return 0;
+}
+
+static int fs_posix_delete(struct fs_file *_file)
+{
+ struct posix_fs_file *file =
+ container_of(_file, struct posix_fs_file, file);
+
+ if (unlink(file->full_path) < 0) {
+ if (!UNLINK_EISDIR(errno)) {
+ fs_set_error_errno(_file->event, "unlink(%s) failed: %m",
+ file->full_path);
+ return -1;
+ }
+ /* attempting to delete a directory. convert it to rmdir()
+ automatically. */
+ if (rmdir(file->full_path) < 0) {
+ fs_set_error_errno(_file->event, "rmdir(%s) failed: %m",
+ file->full_path);
+ return -1;
+ }
+ }
+ (void)fs_posix_rmdir_parents(file, file->full_path);
+ return 0;
+}
+
+static struct fs_iter *fs_posix_iter_alloc(void)
+{
+ struct posix_fs_iter *iter = i_new(struct posix_fs_iter, 1);
+ return &iter->iter;
+}
+
+static void
+fs_posix_iter_init(struct fs_iter *_iter, const char *path,
+ enum fs_iter_flags flags ATTR_UNUSED)
+{
+ struct posix_fs_iter *iter =
+ container_of(_iter, struct posix_fs_iter, iter);
+ struct posix_fs *fs = container_of(_iter->fs, struct posix_fs, fs);
+
+ iter->path = fs->path_prefix == NULL ? i_strdup(path) :
+ i_strconcat(fs->path_prefix, path, NULL);
+ if (iter->path[0] == '\0') {
+ i_free(iter->path);
+ iter->path = i_strdup(".");
+ }
+ iter->dir = opendir(iter->path);
+ if (iter->dir == NULL && errno != ENOENT) {
+ iter->err = errno;
+ fs_set_error_errno(_iter->event,
+ "opendir(%s) failed: %m", iter->path);
+ }
+}
+
+static bool fs_posix_iter_want(struct posix_fs_iter *iter, const char *fname)
+{
+ bool ret;
+
+ T_BEGIN {
+ const char *path = t_strdup_printf("%s/%s", iter->path, fname);
+ struct stat st;
+
+ if (stat(path, &st) < 0 &&
+ lstat(path, &st) < 0)
+ ret = FALSE;
+ else if (!S_ISDIR(st.st_mode))
+ ret = (iter->iter.flags & FS_ITER_FLAG_DIRS) == 0;
+ else
+ ret = (iter->iter.flags & FS_ITER_FLAG_DIRS) != 0;
+ } T_END;
+ return ret;
+}
+
+static const char *fs_posix_iter_next(struct fs_iter *_iter)
+{
+ struct posix_fs_iter *iter =
+ container_of(_iter, struct posix_fs_iter, iter);
+ struct posix_fs *fs = container_of(_iter->fs, struct posix_fs, fs);
+ struct dirent *d;
+
+ if (iter->dir == NULL)
+ return NULL;
+
+ errno = 0;
+ for (; (d = readdir(iter->dir)) != NULL; errno = 0) {
+ if (strcmp(d->d_name, ".") == 0 ||
+ strcmp(d->d_name, "..") == 0)
+ continue;
+ if (strncmp(d->d_name, fs->temp_file_prefix,
+ fs->temp_file_prefix_len) == 0)
+ continue;
+#ifdef HAVE_DIRENT_D_TYPE
+ switch (d->d_type) {
+ case DT_UNKNOWN:
+ if (fs_posix_iter_want(iter, d->d_name))
+ return d->d_name;
+ break;
+ case DT_DIR:
+ if ((iter->iter.flags & FS_ITER_FLAG_DIRS) != 0)
+ return d->d_name;
+ break;
+ default:
+ if ((iter->iter.flags & FS_ITER_FLAG_DIRS) == 0)
+ return d->d_name;
+ break;
+ }
+#else
+ if (fs_posix_iter_want(iter, d->d_name))
+ return d->d_name;
+#endif
+ }
+ if (errno != 0) {
+ iter->err = errno;
+ fs_set_error_errno(_iter->event,
+ "readdir(%s) failed: %m", iter->path);
+ }
+ return NULL;
+}
+
+static int fs_posix_iter_deinit(struct fs_iter *_iter)
+{
+ struct posix_fs_iter *iter =
+ container_of(_iter, struct posix_fs_iter, iter);
+ int ret = 0;
+
+ if (iter->dir != NULL && closedir(iter->dir) < 0 && iter->err == 0) {
+ iter->err = errno;
+ fs_set_error_errno(_iter->event,
+ "closedir(%s) failed: %m", iter->path);
+ }
+ if (iter->err != 0) {
+ errno = iter->err;
+ ret = -1;
+ }
+ i_free(iter->path);
+ return ret;
+}
+
+const struct fs fs_class_posix = {
+ .name = "posix",
+ .v = {
+ fs_posix_alloc,
+ fs_posix_init,
+ NULL,
+ fs_posix_free,
+ fs_posix_get_properties,
+ fs_posix_file_alloc,
+ fs_posix_file_init,
+ fs_posix_file_deinit,
+ fs_posix_file_close,
+ NULL,
+ NULL, NULL,
+ fs_default_set_metadata,
+ NULL,
+ fs_posix_prefetch,
+ fs_posix_read,
+ fs_posix_read_stream,
+ fs_posix_write,
+ fs_posix_write_stream,
+ fs_posix_write_stream_finish,
+ fs_posix_lock,
+ fs_posix_unlock,
+ fs_posix_exists,
+ fs_posix_stat,
+ fs_posix_copy,
+ fs_posix_rename,
+ fs_posix_delete,
+ fs_posix_iter_alloc,
+ fs_posix_iter_init,
+ fs_posix_iter_next,
+ fs_posix_iter_deinit,
+ NULL,
+ NULL,
+ }
+};