summaryrefslogtreecommitdiffstats
path: root/src/lib-fs/fs-sis.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-fs/fs-sis.c')
-rw-r--r--src/lib-fs/fs-sis.c369
1 files changed, 369 insertions, 0 deletions
diff --git a/src/lib-fs/fs-sis.c b/src/lib-fs/fs-sis.c
new file mode 100644
index 0000000..6a020bc
--- /dev/null
+++ b/src/lib-fs/fs-sis.c
@@ -0,0 +1,369 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "ostream-cmp.h"
+#include "fs-sis-common.h"
+
+#define FS_SIS_REQUIRED_PROPS \
+ (FS_PROPERTY_FASTCOPY | FS_PROPERTY_STAT)
+
+struct sis_fs {
+ struct fs fs;
+};
+
+struct sis_fs_file {
+ struct fs_file file;
+ struct sis_fs *fs;
+ enum fs_open_mode open_mode;
+
+ struct fs_file *hash_file;
+ struct istream *hash_input;
+ struct ostream *fs_output;
+
+ char *hash, *hash_path;
+ bool opened;
+};
+
+#define SIS_FS(ptr) container_of((ptr), struct sis_fs, fs)
+#define SIS_FILE(ptr) container_of((ptr), struct sis_fs_file, file)
+
+static struct fs *fs_sis_alloc(void)
+{
+ struct sis_fs *fs;
+
+ fs = i_new(struct sis_fs, 1);
+ fs->fs = fs_class_sis;
+ return &fs->fs;
+}
+
+static int
+fs_sis_init(struct fs *_fs, const char *args, const struct fs_settings *set,
+ const char **error_r)
+{
+ enum fs_properties props;
+ const char *parent_name, *parent_args;
+
+ if (*args == '\0') {
+ *error_r = "Parent filesystem not given as parameter";
+ return -1;
+ }
+
+ parent_args = strchr(args, ':');
+ if (parent_args == NULL) {
+ parent_name = args;
+ parent_args = "";
+ } else {
+ parent_name = t_strdup_until(args, parent_args);
+ parent_args++;
+ }
+ if (fs_init(parent_name, parent_args, set, &_fs->parent, error_r) < 0)
+ return -1;
+ props = fs_get_properties(_fs->parent);
+ if ((props & FS_SIS_REQUIRED_PROPS) != FS_SIS_REQUIRED_PROPS) {
+ *error_r = t_strdup_printf("%s backend can't be used with SIS",
+ parent_name);
+ return -1;
+ }
+ return 0;
+}
+
+static void fs_sis_free(struct fs *_fs)
+{
+ struct sis_fs *fs = SIS_FS(_fs);
+
+ i_free(fs);
+}
+
+static struct fs_file *fs_sis_file_alloc(void)
+{
+ struct sis_fs_file *file = i_new(struct sis_fs_file, 1);
+ return &file->file;
+}
+
+static void
+fs_sis_file_init(struct fs_file *_file, const char *path,
+ enum fs_open_mode mode, enum fs_open_flags flags)
+{
+ struct sis_fs_file *file = SIS_FILE(_file);
+ struct sis_fs *fs = SIS_FS(_file->fs);
+ const char *dir, *hash;
+
+ file->file.path = i_strdup(path);
+ file->fs = fs;
+ file->open_mode = mode;
+ if (mode == FS_OPEN_MODE_APPEND) {
+ fs_set_error(_file->event, ENOTSUP, "APPEND mode not supported");
+ return;
+ }
+
+ if (fs_sis_path_parse(_file, path, &dir, &hash) < 0)
+ return;
+
+ /* if hashes/<hash> already exists, open it */
+ file->hash_path = i_strdup_printf("%s/"HASH_DIR_NAME"/%s", dir, hash);
+ file->hash_file = fs_file_init_parent(_file, file->hash_path,
+ FS_OPEN_MODE_READONLY, 0);
+
+ file->hash_input = fs_read_stream(file->hash_file, IO_BLOCK_SIZE);
+ if (i_stream_read(file->hash_input) == -1) {
+ /* doesn't exist */
+ if (errno != ENOENT) {
+ e_error(file->file.event, "Couldn't read hash file %s: %m",
+ file->hash_path);
+ }
+ i_stream_destroy(&file->hash_input);
+ }
+
+ file->file.parent = fs_file_init_parent(_file, path, mode, flags);
+}
+
+static void fs_sis_file_deinit(struct fs_file *_file)
+{
+ struct sis_fs_file *file = SIS_FILE(_file);
+
+ fs_file_deinit(&file->hash_file);
+ fs_file_free(_file);
+ i_free(file->hash);
+ i_free(file->hash_path);
+ i_free(file->file.path);
+ i_free(file);
+}
+
+static void fs_sis_file_close(struct fs_file *_file)
+{
+ struct sis_fs_file *file = SIS_FILE(_file);
+
+ i_stream_unref(&file->hash_input);
+ fs_file_close(file->hash_file);
+ fs_file_close(_file->parent);
+}
+
+static bool fs_sis_try_link(struct sis_fs_file *file)
+{
+ const struct stat *st;
+ struct stat st2;
+
+ if (i_stream_stat(file->hash_input, FALSE, &st) < 0)
+ return FALSE;
+
+ /* we can use the existing file */
+ if (fs_copy(file->hash_file, file->file.parent) < 0) {
+ if (errno != ENOENT && errno != EMLINK) {
+ e_error(file->file.event, "%s",
+ fs_file_last_error(file->hash_file));
+ }
+ /* failed to use link(), continue as if it hadn't been equal */
+ return FALSE;
+ }
+ if (fs_stat(file->file.parent, &st2) < 0) {
+ e_error(file->file.event, "%s",
+ fs_file_last_error(file->file.parent));
+ if (fs_delete(file->file.parent) < 0) {
+ e_error(file->file.event, "%s",
+ fs_file_last_error(file->file.parent));
+ }
+ return FALSE;
+ }
+ if (st->st_ino != st2.st_ino) {
+ /* the hashes/ file was already replaced with something else */
+ if (fs_delete(file->file.parent) < 0) {
+ e_error(file->file.event, "%s",
+ fs_file_last_error(file->file.parent));
+ }
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void fs_sis_replace_hash_file(struct sis_fs_file *file)
+{
+ struct fs *super_fs = file->file.parent->fs;
+ struct fs_file *temp_file;
+ const char *hash_fname;
+ string_t *temp_path;
+ int ret;
+
+ if (file->hash_input == NULL) {
+ /* hash file didn't exist previously. we should be able to
+ create it with link() */
+ if (fs_copy(file->file.parent, file->hash_file) < 0) {
+ if (errno == EEXIST) {
+ /* the file was just created. it's probably
+ a duplicate, but it's too much trouble
+ trying to deduplicate it anymore */
+ } else {
+ e_error(file->file.event, "%s",
+ fs_file_last_error(file->hash_file));
+ }
+ }
+ return;
+ }
+
+ temp_path = t_str_new(256);
+ hash_fname = strrchr(file->hash_path, '/');
+ if (hash_fname == NULL)
+ hash_fname = file->hash_path;
+ else {
+ str_append_data(temp_path, file->hash_path,
+ (hash_fname-file->hash_path) + 1);
+ hash_fname++;
+ }
+ str_printfa(temp_path, "%s%s.tmp",
+ super_fs->set.temp_file_prefix, hash_fname);
+
+ /* replace existing hash file atomically */
+ temp_file = fs_file_init_parent(&file->file, str_c(temp_path),
+ FS_OPEN_MODE_READONLY, 0);
+ ret = fs_copy(file->file.parent, temp_file);
+ if (ret < 0 && errno == EEXIST) {
+ /* either someone's racing us or it's a stale file.
+ try to continue. */
+ if (fs_delete(temp_file) < 0 &&
+ errno != ENOENT)
+ e_error(file->file.event, "%s", fs_file_last_error(temp_file));
+ ret = fs_copy(file->file.parent, temp_file);
+ }
+ if (ret < 0) {
+ e_error(file->file.event, "%s", fs_file_last_error(temp_file));
+ fs_file_deinit(&temp_file);
+ return;
+ }
+
+ if (fs_rename(temp_file, file->hash_file) < 0) {
+ if (errno == ENOENT) {
+ /* apparently someone else just renamed it. ignore. */
+ } else {
+ e_error(file->file.event, "%s",
+ fs_file_last_error(file->hash_file));
+ }
+ (void)fs_delete(temp_file);
+ }
+ fs_file_deinit(&temp_file);
+}
+
+static int fs_sis_write(struct fs_file *_file, const void *data, size_t size)
+{
+ struct sis_fs_file *file = SIS_FILE(_file);
+
+ if (_file->parent == NULL)
+ return -1;
+
+ if (file->hash_input != NULL &&
+ stream_cmp_block(file->hash_input, data, size) &&
+ i_stream_read_eof(file->hash_input)) {
+ /* try to use existing file */
+ if (fs_sis_try_link(file))
+ return 0;
+ }
+
+ if (fs_write(_file->parent, data, size) < 0)
+ return -1;
+ T_BEGIN {
+ fs_sis_replace_hash_file(file);
+ } T_END;
+ return 0;
+}
+
+static void fs_sis_write_stream(struct fs_file *_file)
+{
+ struct sis_fs_file *file = SIS_FILE(_file);
+
+ i_assert(_file->output == NULL);
+
+ if (_file->parent == NULL) {
+ _file->output = o_stream_create_error_str(EINVAL, "%s",
+ fs_file_last_error(_file));
+ } else {
+ file->fs_output = fs_write_stream(_file->parent);
+ if (file->hash_input == NULL) {
+ _file->output = file->fs_output;
+ o_stream_ref(_file->output);
+ } else {
+ /* compare if files are equal */
+ _file->output = o_stream_create_cmp(file->fs_output,
+ file->hash_input);
+ }
+ }
+ o_stream_set_name(_file->output, _file->path);
+}
+
+static int fs_sis_write_stream_finish(struct fs_file *_file, bool success)
+{
+ struct sis_fs_file *file = SIS_FILE(_file);
+
+ if (!success) {
+ if (_file->parent != NULL)
+ fs_write_stream_abort_parent(_file, &file->fs_output);
+ o_stream_unref(&_file->output);
+ return -1;
+ }
+
+ if (file->hash_input != NULL &&
+ o_stream_cmp_equals(_file->output) &&
+ i_stream_read_eof(file->hash_input)) {
+ o_stream_unref(&_file->output);
+ if (fs_sis_try_link(file)) {
+ fs_write_stream_abort_parent(_file, &file->fs_output);
+ return 1;
+ }
+ }
+ if (_file->output != NULL)
+ o_stream_unref(&_file->output);
+
+ if (fs_write_stream_finish(_file->parent, &file->fs_output) < 0)
+ return -1;
+ T_BEGIN {
+ fs_sis_replace_hash_file(file);
+ } T_END;
+ return 1;
+}
+
+static int fs_sis_delete(struct fs_file *_file)
+{
+ T_BEGIN {
+ fs_sis_try_unlink_hash_file(_file, _file->parent);
+ } T_END;
+ return fs_delete(_file->parent);
+}
+
+const struct fs fs_class_sis = {
+ .name = "sis",
+ .v = {
+ fs_sis_alloc,
+ fs_sis_init,
+ NULL,
+ fs_sis_free,
+ fs_wrapper_get_properties,
+ fs_sis_file_alloc,
+ fs_sis_file_init,
+ fs_sis_file_deinit,
+ fs_sis_file_close,
+ fs_wrapper_file_get_path,
+ fs_wrapper_set_async_callback,
+ fs_wrapper_wait_async,
+ fs_wrapper_set_metadata,
+ fs_wrapper_get_metadata,
+ fs_wrapper_prefetch,
+ fs_wrapper_read,
+ fs_wrapper_read_stream,
+ fs_sis_write,
+ fs_sis_write_stream,
+ fs_sis_write_stream_finish,
+ fs_wrapper_lock,
+ fs_wrapper_unlock,
+ fs_wrapper_exists,
+ fs_wrapper_stat,
+ fs_wrapper_copy,
+ fs_wrapper_rename,
+ fs_sis_delete,
+ fs_wrapper_iter_alloc,
+ fs_wrapper_iter_init,
+ NULL,
+ NULL,
+ NULL,
+ fs_wrapper_get_nlinks,
+ }
+};