diff options
Diffstat (limited to 'src/lib-dict-extra/dict-fs.c')
-rw-r--r-- | src/lib-dict-extra/dict-fs.c | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/src/lib-dict-extra/dict-fs.c b/src/lib-dict-extra/dict-fs.c new file mode 100644 index 0000000..4c173e3 --- /dev/null +++ b/src/lib-dict-extra/dict-fs.c @@ -0,0 +1,330 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "fs-api.h" +#include "istream.h" +#include "str.h" +#include "dict-transaction-memory.h" +#include "dict-private.h" + +struct fs_dict { + struct dict dict; + struct fs *fs; +}; + +struct fs_dict_iterate_context { + struct dict_iterate_context ctx; + char *path; + enum dict_iterate_flags flags; + pool_t value_pool; + struct fs_iter *fs_iter; + const char *values[2]; + char *error; +}; + +static int +fs_dict_init(struct dict *driver, const char *uri, + const struct dict_settings *set, + struct dict **dict_r, const char **error_r) +{ + struct fs_settings fs_set; + struct fs *fs; + struct fs_dict *dict; + const char *p, *fs_driver, *fs_args; + + p = strchr(uri, ':'); + if (p == NULL) { + fs_driver = uri; + fs_args = ""; + } else { + fs_driver = t_strdup_until(uri, p); + fs_args = p+1; + } + + i_zero(&fs_set); + fs_set.base_dir = set->base_dir; + if (fs_init(fs_driver, fs_args, &fs_set, &fs, error_r) < 0) + return -1; + + dict = i_new(struct fs_dict, 1); + dict->dict = *driver; + dict->fs = fs; + + *dict_r = &dict->dict; + return 0; +} + +static void fs_dict_deinit(struct dict *_dict) +{ + struct fs_dict *dict = (struct fs_dict *)_dict; + + fs_deinit(&dict->fs); + i_free(dict); +} + +/* Remove unsafe paths */ +static const char *fs_dict_escape_key(const char *key) +{ + const char *ptr; + string_t *new_key = NULL; + /* we take the slow path always if we see potential + need for escaping */ + while ((ptr = strstr(key, "/.")) != NULL) { + /* move to the first dot */ + const char *ptr2 = ptr + 1; + /* find position of non-dot */ + while (*ptr2 == '.') ptr2++; + if (new_key == NULL) + new_key = t_str_new(strlen(key)); + str_append_data(new_key, key, ptr - key); + /* if ptr2 is / or end of string, escape */ + if (*ptr2 == '/' || *ptr2 == '\0') + str_append(new_key, "/..."); + else + str_append(new_key, "/."); + key = ptr + 2; + } + if (new_key == NULL) + return key; + str_append(new_key, key); + return str_c(new_key); +} + +static const char *fs_dict_get_full_key(const char *username, const char *key) +{ + key = fs_dict_escape_key(key); + if (str_begins(key, DICT_PATH_SHARED)) + return key + strlen(DICT_PATH_SHARED); + else if (str_begins(key, DICT_PATH_PRIVATE)) { + return t_strdup_printf("%s/%s", username, + key + strlen(DICT_PATH_PRIVATE)); + } else { + i_unreached(); + } +} + +static int fs_dict_lookup(struct dict *_dict, const struct dict_op_settings *set, + pool_t pool, const char *key, + const char **value_r, const char **error_r) +{ + struct fs_dict *dict = (struct fs_dict *)_dict; + struct fs_file *file; + struct istream *input; + const unsigned char *data; + size_t size; + const char *path; + string_t *str; + int ret; + + path = fs_dict_get_full_key(set->username, key); + file = fs_file_init(dict->fs, path, FS_OPEN_MODE_READONLY); + input = fs_read_stream(file, IO_BLOCK_SIZE); + (void)i_stream_read(input); + + str = str_new(pool, i_stream_get_data_size(input)+1); + while ((ret = i_stream_read_more(input, &data, &size)) > 0) { + str_append_data(str, data, size); + i_stream_skip(input, size); + } + i_assert(ret == -1); + + if (input->stream_errno == 0) { + *value_r = str_c(str); + ret = 1; + } else { + *value_r = NULL; + if (input->stream_errno == ENOENT) + ret = 0; + else { + *error_r = t_strdup_printf("read(%s) failed: %s", + path, i_stream_get_error(input)); + } + } + + i_stream_unref(&input); + fs_file_deinit(&file); + return ret; +} + +static struct dict_iterate_context * +fs_dict_iterate_init(struct dict *_dict, const struct dict_op_settings *set, + const char *path, enum dict_iterate_flags flags) +{ + struct fs_dict *dict = (struct fs_dict *)_dict; + struct fs_dict_iterate_context *iter; + + /* these flags are not supported for now */ + i_assert((flags & DICT_ITERATE_FLAG_RECURSE) == 0); + i_assert((flags & DICT_ITERATE_FLAG_EXACT_KEY) == 0); + i_assert((flags & (DICT_ITERATE_FLAG_SORT_BY_KEY | + DICT_ITERATE_FLAG_SORT_BY_VALUE)) == 0); + + iter = i_new(struct fs_dict_iterate_context, 1); + iter->ctx.dict = _dict; + iter->path = i_strdup(path); + iter->flags = flags; + iter->value_pool = pool_alloconly_create("iterate value pool", 128); + iter->fs_iter = fs_iter_init(dict->fs, + fs_dict_get_full_key(set->username, path), 0); + return &iter->ctx; +} + +static bool fs_dict_iterate(struct dict_iterate_context *ctx, + const char **key_r, const char *const **values_r) +{ + struct fs_dict_iterate_context *iter = + (struct fs_dict_iterate_context *)ctx; + struct fs_dict *dict = (struct fs_dict *)ctx->dict; + const char *path, *error; + int ret; + + if (iter->error != NULL) + return FALSE; + + *key_r = fs_iter_next(iter->fs_iter); + if (*key_r == NULL) { + if (fs_iter_deinit(&iter->fs_iter, &error) < 0) { + iter->error = i_strdup(error); + return FALSE; + } + if (iter->path == NULL) + return FALSE; + path = fs_dict_get_full_key(ctx->set.username, iter->path); + iter->fs_iter = fs_iter_init(dict->fs, path, 0); + return fs_dict_iterate(ctx, key_r, values_r); + } + path = t_strconcat(iter->path, *key_r, NULL); + if ((iter->flags & DICT_ITERATE_FLAG_NO_VALUE) != 0) { + iter->values[0] = NULL; + *key_r = path; + return TRUE; + } + p_clear(iter->value_pool); + struct dict_op_settings set = { + .username = ctx->set.username, + }; + ret = fs_dict_lookup(ctx->dict, &set, iter->value_pool, path, + &iter->values[0], &error); + if (ret < 0) { + /* I/O error */ + iter->error = i_strdup(error); + return FALSE; + } else if (ret == 0) { + /* file was just deleted, just skip to next one */ + return fs_dict_iterate(ctx, key_r, values_r); + } + *key_r = path; + *values_r = iter->values; + return TRUE; +} + +static int fs_dict_iterate_deinit(struct dict_iterate_context *ctx, + const char **error_r) +{ + struct fs_dict_iterate_context *iter = + (struct fs_dict_iterate_context *)ctx; + const char *error; + int ret; + + if (fs_iter_deinit(&iter->fs_iter, &error) < 0 && iter->error == NULL) + iter->error = i_strdup(error); + + ret = iter->error != NULL ? -1 : 0; + *error_r = t_strdup(iter->error); + + pool_unref(&iter->value_pool); + i_free(iter->path); + i_free(iter->error); + i_free(iter); + return ret; +} + +static struct dict_transaction_context * +fs_dict_transaction_init(struct dict *_dict) +{ + struct dict_transaction_memory_context *ctx; + pool_t pool; + + pool = pool_alloconly_create("file dict transaction", 2048); + ctx = p_new(pool, struct dict_transaction_memory_context, 1); + dict_transaction_memory_init(ctx, _dict, pool); + return &ctx->ctx; +} + +static int fs_dict_write_changes(struct dict_transaction_memory_context *ctx, + const char **error_r) +{ + struct fs_dict *dict = (struct fs_dict *)ctx->ctx.dict; + struct fs_file *file; + const struct dict_transaction_memory_change *change; + const char *key; + int ret = 0; + + array_foreach(&ctx->changes, change) { + key = fs_dict_get_full_key(ctx->ctx.set.username, change->key); + switch (change->type) { + case DICT_CHANGE_TYPE_SET: + file = fs_file_init(dict->fs, key, + FS_OPEN_MODE_REPLACE); + if (fs_write(file, change->value.str, strlen(change->value.str)) < 0) { + *error_r = t_strdup_printf( + "fs_write(%s) failed: %s", key, + fs_file_last_error(file)); + ret = -1; + } + fs_file_deinit(&file); + break; + case DICT_CHANGE_TYPE_UNSET: + file = fs_file_init(dict->fs, key, FS_OPEN_MODE_READONLY); + if (fs_delete(file) < 0) { + *error_r = t_strdup_printf( + "fs_delete(%s) failed: %s", key, + fs_file_last_error(file)); + ret = -1; + } + fs_file_deinit(&file); + break; + case DICT_CHANGE_TYPE_INC: + i_unreached(); + } + if (ret < 0) + return -1; + } + return 0; +} + +static void +fs_dict_transaction_commit(struct dict_transaction_context *_ctx, + bool async ATTR_UNUSED, + dict_transaction_commit_callback_t *callback, + void *context) +{ + struct dict_transaction_memory_context *ctx = + (struct dict_transaction_memory_context *)_ctx; + struct dict_commit_result result = { .ret = 1 }; + + + if (fs_dict_write_changes(ctx, &result.error) < 0) + result.ret = -1; + pool_unref(&ctx->pool); + + callback(&result, context); +} + +struct dict dict_driver_fs = { + .name = "fs", + { + .init = fs_dict_init, + .deinit = fs_dict_deinit, + .lookup = fs_dict_lookup, + .iterate_init = fs_dict_iterate_init, + .iterate = fs_dict_iterate, + .iterate_deinit = fs_dict_iterate_deinit, + .transaction_init = fs_dict_transaction_init, + .transaction_commit = fs_dict_transaction_commit, + .transaction_rollback = dict_transaction_memory_rollback, + .set = dict_transaction_memory_set, + .unset = dict_transaction_memory_unset, + } +}; |