summaryrefslogtreecommitdiffstats
path: root/src/lib-fs/fs-dict.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-fs/fs-dict.c')
-rw-r--r--src/lib-fs/fs-dict.c372
1 files changed, 372 insertions, 0 deletions
diff --git a/src/lib-fs/fs-dict.c b/src/lib-fs/fs-dict.c
new file mode 100644
index 0000000..936e9b6
--- /dev/null
+++ b/src/lib-fs/fs-dict.c
@@ -0,0 +1,372 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "str.h"
+#include "guid.h"
+#include "hex-binary.h"
+#include "base64.h"
+#include "istream.h"
+#include "ostream.h"
+#include "dict.h"
+#include "fs-api-private.h"
+
+enum fs_dict_value_encoding {
+ FS_DICT_VALUE_ENCODING_RAW,
+ FS_DICT_VALUE_ENCODING_HEX,
+ FS_DICT_VALUE_ENCODING_BASE64
+};
+
+struct dict_fs {
+ struct fs fs;
+ struct dict *dict;
+ char *path_prefix;
+ enum fs_dict_value_encoding encoding;
+};
+
+struct dict_fs_file {
+ struct fs_file file;
+ pool_t pool;
+ const char *key, *value;
+ buffer_t *write_buffer;
+};
+
+struct dict_fs_iter {
+ struct fs_iter iter;
+ struct dict_iterate_context *dict_iter;
+};
+
+static struct fs *fs_dict_alloc(void)
+{
+ struct dict_fs *fs;
+
+ fs = i_new(struct dict_fs, 1);
+ fs->fs = fs_class_dict;
+ return &fs->fs;
+}
+
+static int
+fs_dict_init(struct fs *_fs, const char *args, const struct fs_settings *set,
+ const char **error_r)
+{
+ struct dict_fs *fs = (struct dict_fs *)_fs;
+ struct dict_settings dict_set;
+ const char *p, *encoding_str, *error;
+
+ p = strchr(args, ':');
+ if (p == NULL) {
+ *error_r = "':' missing in args";
+ return -1;
+ }
+ encoding_str = t_strdup_until(args, p++);
+ if (strcmp(encoding_str, "raw") == 0)
+ fs->encoding = FS_DICT_VALUE_ENCODING_RAW;
+ else if (strcmp(encoding_str, "hex") == 0)
+ fs->encoding = FS_DICT_VALUE_ENCODING_HEX;
+ else if (strcmp(encoding_str, "base64") == 0)
+ fs->encoding = FS_DICT_VALUE_ENCODING_BASE64;
+ else {
+ *error_r = t_strdup_printf("Unknown value encoding '%s'",
+ encoding_str);
+ return -1;
+ }
+
+ i_zero(&dict_set);
+ dict_set.base_dir = set->base_dir;
+ dict_set.event_parent = set->event_parent;
+
+ if (dict_init(p, &dict_set, &fs->dict, &error) < 0) {
+ *error_r = t_strdup_printf("dict_init(%s) failed: %s",
+ args, error);
+ return -1;
+ }
+ return 0;
+}
+
+static void fs_dict_free(struct fs *_fs)
+{
+ struct dict_fs *fs = (struct dict_fs *)_fs;
+
+ if (fs->dict != NULL) dict_deinit(&fs->dict);
+ i_free(fs);
+}
+
+static enum fs_properties fs_dict_get_properties(struct fs *fs ATTR_UNUSED)
+{
+ return FS_PROPERTY_ITER | FS_PROPERTY_RELIABLEITER;
+}
+
+static struct fs_file *fs_dict_file_alloc(void)
+{
+ struct dict_fs_file *file;
+ pool_t pool;
+
+ pool = pool_alloconly_create("fs dict file", 128);
+ file = p_new(pool, struct dict_fs_file, 1);
+ file->pool = pool;
+ return &file->file;
+}
+
+static void
+fs_dict_file_init(struct fs_file *_file, const char *path,
+ enum fs_open_mode mode, enum fs_open_flags flags ATTR_UNUSED)
+{
+ struct dict_fs_file *file = (struct dict_fs_file *)_file;
+ struct dict_fs *fs = (struct dict_fs *)_file->fs;
+ guid_128_t guid;
+
+ i_assert(mode != FS_OPEN_MODE_APPEND); /* not supported */
+ i_assert(mode != FS_OPEN_MODE_CREATE); /* not supported */
+
+ if (mode != FS_OPEN_MODE_CREATE_UNIQUE_128)
+ file->file.path = p_strdup(file->pool, path);
+ else {
+ guid_128_generate(guid);
+ file->file.path = p_strdup_printf(file->pool, "%s/%s", path,
+ guid_128_to_string(guid));
+ }
+ file->key = fs->path_prefix == NULL ?
+ p_strdup(file->pool, file->file.path) :
+ p_strconcat(file->pool, fs->path_prefix, file->file.path, NULL);
+}
+
+static void fs_dict_file_deinit(struct fs_file *_file)
+{
+ struct dict_fs_file *file = (struct dict_fs_file *)_file;
+
+ i_assert(_file->output == NULL);
+
+ fs_file_free(_file);
+ pool_unref(&file->pool);
+}
+
+static bool fs_dict_prefetch(struct fs_file *_file ATTR_UNUSED,
+ uoff_t length ATTR_UNUSED)
+{
+ /* once async dict_lookup() is implemented, we want to start it here */
+ return TRUE;
+}
+
+static int fs_dict_lookup(struct dict_fs_file *file)
+{
+ struct dict_fs *fs = (struct dict_fs *)file->file.fs;
+ const char *error;
+ int ret;
+
+ if (file->value != NULL)
+ return 0;
+
+ struct dict_op_settings set = {
+ .username = file->file.fs->username,
+ };
+ ret = dict_lookup(fs->dict, &set, file->pool, file->key, &file->value, &error);
+ if (ret > 0)
+ return 0;
+ else if (ret < 0) {
+ fs_set_error(file->file.event, EIO,
+ "dict_lookup(%s) failed: %s", file->key, error);
+ return -1;
+ } else {
+ fs_set_error(file->file.event, ENOENT,
+ "Dict key %s doesn't exist", file->key);
+ return -1;
+ }
+}
+
+static struct istream *
+fs_dict_read_stream(struct fs_file *_file, size_t max_buffer_size ATTR_UNUSED)
+{
+ struct dict_fs_file *file = (struct dict_fs_file *)_file;
+ struct istream *input;
+
+ if (fs_dict_lookup(file) < 0)
+ input = i_stream_create_error_str(errno, "%s", fs_file_last_error(_file));
+ else
+ input = i_stream_create_from_data(file->value, strlen(file->value));
+ i_stream_set_name(input, file->key);
+ return input;
+}
+
+static void fs_dict_write_stream(struct fs_file *_file)
+{
+ struct dict_fs_file *file = (struct dict_fs_file *)_file;
+
+ i_assert(_file->output == NULL);
+
+ file->write_buffer = buffer_create_dynamic(file->pool, 128);
+ _file->output = o_stream_create_buffer(file->write_buffer);
+ o_stream_set_name(_file->output, file->key);
+}
+
+static void fs_dict_write_rename_if_needed(struct dict_fs_file *file)
+{
+ struct dict_fs *fs = (struct dict_fs *)file->file.fs;
+ const char *new_fname;
+
+ new_fname = fs_metadata_find(&file->file.metadata, FS_METADATA_WRITE_FNAME);
+ if (new_fname == NULL)
+ return;
+
+ file->file.path = p_strdup(file->pool, new_fname);
+ file->key = fs->path_prefix == NULL ? p_strdup(file->pool, new_fname) :
+ p_strconcat(file->pool, fs->path_prefix, new_fname, NULL);
+}
+
+static int fs_dict_write_stream_finish(struct fs_file *_file, bool success)
+{
+ struct dict_fs_file *file = (struct dict_fs_file *)_file;
+ struct dict_fs *fs = (struct dict_fs *)_file->fs;
+ struct dict_transaction_context *trans;
+ const char *error;
+
+ o_stream_destroy(&_file->output);
+ if (!success)
+ return -1;
+
+ struct dict_op_settings set = {
+ .username = _file->fs->username,
+ };
+ fs_dict_write_rename_if_needed(file);
+ trans = dict_transaction_begin(fs->dict, &set);
+ switch (fs->encoding) {
+ case FS_DICT_VALUE_ENCODING_RAW:
+ dict_set(trans, file->key, str_c(file->write_buffer));
+ break;
+ case FS_DICT_VALUE_ENCODING_HEX: {
+ string_t *hex = t_str_new(file->write_buffer->used * 2 + 1);
+ binary_to_hex_append(hex, file->write_buffer->data,
+ file->write_buffer->used);
+ dict_set(trans, file->key, str_c(hex));
+ break;
+ }
+ case FS_DICT_VALUE_ENCODING_BASE64: {
+ const size_t base64_size =
+ MAX_BASE64_ENCODED_SIZE(file->write_buffer->used);
+ string_t *base64 = t_str_new(base64_size);
+ base64_encode(file->write_buffer->data,
+ file->write_buffer->used, base64);
+ dict_set(trans, file->key, str_c(base64));
+ }
+ }
+ if (dict_transaction_commit(&trans, &error) < 0) {
+ fs_set_error(_file->event, EIO,
+ "Dict transaction commit failed: %s", error);
+ return -1;
+ }
+ return 1;
+}
+
+static int fs_dict_stat(struct fs_file *_file, struct stat *st_r)
+{
+ struct dict_fs_file *file = (struct dict_fs_file *)_file;
+
+ i_zero(st_r);
+
+ if (fs_dict_lookup(file) < 0)
+ return -1;
+ st_r->st_size = strlen(file->value);
+ return 0;
+}
+
+static int fs_dict_delete(struct fs_file *_file)
+{
+ struct dict_fs_file *file = (struct dict_fs_file *)_file;
+ struct dict_fs *fs = (struct dict_fs *)_file->fs;
+ struct dict_transaction_context *trans;
+ const char *error;
+
+ struct dict_op_settings set = {
+ .username = fs->fs.username,
+ };
+ trans = dict_transaction_begin(fs->dict, &set);
+ dict_unset(trans, file->key);
+ if (dict_transaction_commit(&trans, &error) < 0) {
+ fs_set_error(_file->event, EIO,
+ "Dict transaction commit failed: %s", error);
+ return -1;
+ }
+ return 0;
+}
+
+static struct fs_iter *fs_dict_iter_alloc(void)
+{
+ struct dict_fs_iter *iter = i_new(struct dict_fs_iter, 1);
+ return &iter->iter;
+}
+
+static void
+fs_dict_iter_init(struct fs_iter *_iter, const char *path,
+ enum fs_iter_flags flags ATTR_UNUSED)
+{
+ struct dict_fs_iter *iter = (struct dict_fs_iter *)_iter;
+ struct dict_fs *fs = (struct dict_fs *)_iter->fs;
+
+ if (fs->path_prefix != NULL)
+ path = t_strconcat(fs->path_prefix, path, NULL);
+
+ struct dict_op_settings set = {
+ .username = iter->iter.fs->username,
+ };
+ iter->dict_iter = dict_iterate_init(fs->dict, &set, path, 0);
+}
+
+static const char *fs_dict_iter_next(struct fs_iter *_iter)
+{
+ struct dict_fs_iter *iter = (struct dict_fs_iter *)_iter;
+ const char *key, *value;
+
+ if (!dict_iterate(iter->dict_iter, &key, &value))
+ return NULL;
+ return key;
+}
+
+static int fs_dict_iter_deinit(struct fs_iter *_iter)
+{
+ struct dict_fs_iter *iter = (struct dict_fs_iter *)_iter;
+ const char *error;
+ int ret;
+
+ ret = dict_iterate_deinit(&iter->dict_iter, &error);
+ if (ret < 0)
+ fs_set_error(_iter->event, EIO,
+ "Dict iteration failed: %s", error);
+ return ret;
+}
+
+const struct fs fs_class_dict = {
+ .name = "dict",
+ .v = {
+ fs_dict_alloc,
+ fs_dict_init,
+ NULL,
+ fs_dict_free,
+ fs_dict_get_properties,
+ fs_dict_file_alloc,
+ fs_dict_file_init,
+ fs_dict_file_deinit,
+ NULL,
+ NULL,
+ NULL, NULL,
+ fs_default_set_metadata,
+ NULL,
+ fs_dict_prefetch,
+ NULL,
+ fs_dict_read_stream,
+ NULL,
+ fs_dict_write_stream,
+ fs_dict_write_stream_finish,
+ NULL,
+ NULL,
+ NULL,
+ fs_dict_stat,
+ fs_default_copy,
+ NULL,
+ fs_dict_delete,
+ fs_dict_iter_alloc,
+ fs_dict_iter_init,
+ fs_dict_iter_next,
+ fs_dict_iter_deinit,
+ NULL,
+ NULL
+ }
+};