diff options
Diffstat (limited to 'src/lib-fs/fs-dict.c')
-rw-r--r-- | src/lib-fs/fs-dict.c | 372 |
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 + } +}; |