diff options
Diffstat (limited to 'src/lib-settings/settings-parser.c')
-rw-r--r-- | src/lib-settings/settings-parser.c | 2226 |
1 files changed, 2226 insertions, 0 deletions
diff --git a/src/lib-settings/settings-parser.c b/src/lib-settings/settings-parser.c new file mode 100644 index 0000000..7b63b30 --- /dev/null +++ b/src/lib-settings/settings-parser.c @@ -0,0 +1,2226 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hash.h" +#include "net.h" +#include "istream.h" +#include "env-util.h" +#include "execv-const.h" +#include "str.h" +#include "strescape.h" +#include "var-expand.h" +#include "settings-parser.h" + +#include <stdio.h> +#include <unistd.h> +#include <ctype.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#define IS_WHITE(c) ((c) == ' ' || (c) == '\t') + +struct setting_link { + struct setting_link *parent; + const struct setting_parser_info *info; + + const char *full_key; + + /* Points to array inside parent->set_struct. + SET_DEFLIST : array of set_structs + SET_STRLIST : array of const_strings */ + ARRAY_TYPE(void_array) *array; + /* Pointer to structure containing the values */ + void *set_struct; + /* Pointer to structure containing non-zero values for settings that + have been changed. */ + void *change_struct; + /* SET_DEFLIST: array of change_structs */ + ARRAY_TYPE(void_array) *change_array; +}; + +struct setting_parser_context { + pool_t set_pool, parser_pool; + enum settings_parser_flags flags; + bool str_vars_are_expanded; + + struct setting_link *roots; + unsigned int root_count; + HASH_TABLE(char *, struct setting_link *) links; + + unsigned int linenum; + const char *error; + const struct setting_parser_info *prev_info; +}; + +static const struct setting_parser_info strlist_info = { + .module_name = NULL, + .defines = NULL, + .defaults = NULL, + + .type_offset = SIZE_MAX, + .struct_size = 0, + + .parent_offset = SIZE_MAX +}; + +HASH_TABLE_DEFINE_TYPE(setting_link, struct setting_link *, + struct setting_link *); + +static void +setting_parser_copy_defaults(struct setting_parser_context *ctx, + const struct setting_parser_info *info, + struct setting_link *link); +static int +settings_apply(struct setting_link *dest_link, + const struct setting_link *src_link, + pool_t pool, const char **conflict_key_r); + +struct setting_parser_context * +settings_parser_init(pool_t set_pool, const struct setting_parser_info *root, + enum settings_parser_flags flags) +{ + return settings_parser_init_list(set_pool, &root, 1, flags); +} + +static void +copy_unique_defaults(struct setting_parser_context *ctx, + const struct setting_define *def, + struct setting_link *link) +{ + ARRAY_TYPE(void_array) *arr = + STRUCT_MEMBER_P(link->set_struct, def->offset); + ARRAY_TYPE(void_array) *carr = NULL; + struct setting_link *new_link; + struct setting_parser_info info; + const char *const *keyp, *key, *prefix; + void *const *children; + void *new_set, *new_changes = NULL; + char *full_key; + unsigned int i, count; + + if (!array_is_created(arr)) + return; + + children = array_get(arr, &count); + if (link->change_struct != NULL) { + carr = STRUCT_MEMBER_P(link->change_struct, def->offset); + i_assert(!array_is_created(carr)); + p_array_init(carr, ctx->set_pool, count + 4); + } + p_array_init(arr, ctx->set_pool, count + 4); + + i_zero(&info); + info = *def->list_info; + + for (i = 0; i < count; i++) T_BEGIN { + new_set = p_malloc(ctx->set_pool, info.struct_size); + array_push_back(arr, &new_set); + + if (link->change_struct != NULL) { + i_assert(carr != NULL); + new_changes = p_malloc(ctx->set_pool, info.struct_size); + array_push_back(carr, &new_changes); + } + + keyp = CONST_PTR_OFFSET(children[i], info.type_offset); + key = settings_section_escape(*keyp); + + new_link = p_new(ctx->set_pool, struct setting_link, 1); + prefix = link->full_key == NULL ? + t_strconcat(def->key, SETTINGS_SEPARATOR_S, NULL) : + t_strconcat(link->full_key, SETTINGS_SEPARATOR_S, + def->key, SETTINGS_SEPARATOR_S,NULL); + full_key = p_strconcat(ctx->set_pool, prefix, key, NULL); + new_link->full_key = full_key; + new_link->parent = link; + new_link->info = def->list_info; + new_link->array = arr; + new_link->change_array = carr; + new_link->set_struct = new_set; + new_link->change_struct = new_changes; + i_assert(hash_table_lookup(ctx->links, full_key) == NULL); + hash_table_insert(ctx->links, full_key, new_link); + + info.defaults = children[i]; + setting_parser_copy_defaults(ctx, &info, new_link); + } T_END; +} + +static void +setting_parser_copy_defaults(struct setting_parser_context *ctx, + const struct setting_parser_info *info, + struct setting_link *link) +{ + const struct setting_define *def; + const char *p, **strp; + + if (info->defaults == NULL) + return; + + memcpy(link->set_struct, info->defaults, info->struct_size); + for (def = info->defines; def->key != NULL; def++) { + switch (def->type) { + case SET_ENUM: { + /* fix enums by dropping everything after the + first ':' */ + strp = STRUCT_MEMBER_P(link->set_struct, def->offset); + p = strchr(*strp, ':'); + if (p != NULL) + *strp = p_strdup_until(ctx->set_pool, *strp, p); + break; + } + case SET_STR_VARS: { + /* insert the unexpanded-character */ + strp = STRUCT_MEMBER_P(link->set_struct, def->offset); + if (*strp != NULL) { + *strp = p_strconcat(ctx->set_pool, + SETTING_STRVAR_UNEXPANDED, + *strp, NULL); + } + break; + } + case SET_DEFLIST_UNIQUE: + copy_unique_defaults(ctx, def, link); + break; + default: + break; + } + } +} + +struct setting_parser_context * +settings_parser_init_list(pool_t set_pool, + const struct setting_parser_info *const *roots, + unsigned int count, enum settings_parser_flags flags) +{ + struct setting_parser_context *ctx; + unsigned int i; + pool_t parser_pool; + + i_assert(count > 0); + + parser_pool = pool_alloconly_create(MEMPOOL_GROWING"settings parser", + 1024); + ctx = p_new(parser_pool, struct setting_parser_context, 1); + ctx->set_pool = set_pool; + ctx->parser_pool = parser_pool; + ctx->flags = flags; + /* use case-insensitive comparisons. this is mainly because settings + may go through environment variables where their keys get + uppercased. of course the alternative would be to not uppercase + environment. probably doesn't make much difference which way is + chosen. */ + hash_table_create(&ctx->links, ctx->parser_pool, 0, + strcase_hash, strcasecmp); + + ctx->root_count = count; + ctx->roots = p_new(ctx->parser_pool, struct setting_link, count); + for (i = 0; i < count; i++) { + ctx->roots[i].info = roots[i]; + if (roots[i]->struct_size == 0) + continue; + + ctx->roots[i].set_struct = + p_malloc(ctx->set_pool, roots[i]->struct_size); + if ((flags & SETTINGS_PARSER_FLAG_TRACK_CHANGES) != 0) { + ctx->roots[i].change_struct = + p_malloc(ctx->set_pool, roots[i]->struct_size); + } + setting_parser_copy_defaults(ctx, roots[i], &ctx->roots[i]); + } + + pool_ref(ctx->set_pool); + return ctx; +} + +void settings_parser_deinit(struct setting_parser_context **_ctx) +{ + struct setting_parser_context *ctx = *_ctx; + + *_ctx = NULL; + hash_table_destroy(&ctx->links); + pool_unref(&ctx->set_pool); + pool_unref(&ctx->parser_pool); +} + +void *settings_parser_get(struct setting_parser_context *ctx) +{ + i_assert(ctx->root_count == 1); + + return ctx->roots[0].set_struct; +} + +void **settings_parser_get_list(const struct setting_parser_context *ctx) +{ + unsigned int i; + void **sets; + + sets = t_new(void *, ctx->root_count + 1); + for (i = 0; i < ctx->root_count; i++) + sets[i] = ctx->roots[i].set_struct; + return sets; +} + +void *settings_parser_get_changes(struct setting_parser_context *ctx) +{ + i_assert(ctx->root_count == 1); + + return ctx->roots[0].change_struct; +} + +const struct setting_parser_info *const * +settings_parser_get_roots(const struct setting_parser_context *ctx) +{ + const struct setting_parser_info **infos; + unsigned int i; + + infos = t_new(const struct setting_parser_info *, ctx->root_count + 1); + for (i = 0; i < ctx->root_count; i++) + infos[i] = ctx->roots[i].info; + return infos; +} + +const char *settings_parser_get_error(struct setting_parser_context *ctx) +{ + return ctx->error; +} + +static const struct setting_define * +setting_define_find(const struct setting_parser_info *info, const char *key) +{ + const struct setting_define *list; + + for (list = info->defines; list->key != NULL; list++) { + if (strcmp(list->key, key) == 0) + return list; + } + return NULL; +} + +static int +get_bool(struct setting_parser_context *ctx, const char *value, bool *result_r) +{ + /* FIXME: eventually we'd want to support only yes/no */ + if (strcasecmp(value, "yes") == 0 || + strcasecmp(value, "y") == 0 || strcmp(value, "1") == 0) + *result_r = TRUE; + else if (strcasecmp(value, "no") == 0) + *result_r = FALSE; + else { + ctx->error = p_strdup_printf(ctx->parser_pool, + "Invalid boolean value: %s (use yes or no)", value); + return -1; + } + + return 0; +} + +static int +get_uint(struct setting_parser_context *ctx, const char *value, + unsigned int *result_r) +{ + if (str_to_uint(value, result_r) < 0) { + ctx->error = p_strdup_printf(ctx->parser_pool, + "Invalid number %s: %s", value, + str_num_error(value)); + return -1; + } + return 0; +} + +static int +get_octal(struct setting_parser_context *ctx, const char *value, + unsigned int *result_r) +{ + unsigned long long octal; + + if (*value != '0') + return get_uint(ctx, value, result_r); + + if (str_to_ullong_oct(value, &octal) < 0) { + ctx->error = p_strconcat(ctx->parser_pool, "Invalid number: ", + value, NULL); + return -1; + } + *result_r = (unsigned int)octal; + return 0; +} + +static int settings_get_time_full(const char *str, unsigned int *interval_r, + bool milliseconds, const char **error_r) +{ + uintmax_t num, multiply = milliseconds ? 1000 : 1; + const char *p; + + if (str_parse_uintmax(str, &num, &p) < 0) { + *error_r = t_strconcat("Invalid time interval: ", str, NULL); + return -1; + } + while (*p == ' ') p++; + if (*p == '\0' && num != 0) { + *error_r = t_strdup_printf("Time interval '%s' is missing units " + "(add e.g. 's' for seconds)", str); + return -1; + } + switch (i_toupper(*p)) { + case 'S': + multiply *= 1; + if (strncasecmp(p, "secs", strlen(p)) == 0 || + strncasecmp(p, "seconds", strlen(p)) == 0) + p = ""; + break; + case 'M': + multiply *= 60; + if (strncasecmp(p, "mins", strlen(p)) == 0 || + strncasecmp(p, "minutes", strlen(p)) == 0) + p = ""; + else if (strncasecmp(p, "msecs", strlen(p)) == 0 || + strncasecmp(p, "mseconds", strlen(p)) == 0 || + strncasecmp(p, "millisecs", strlen(p)) == 0 || + strncasecmp(p, "milliseconds", strlen(p)) == 0) { + if (milliseconds || (num % 1000) == 0) { + if (!milliseconds) { + /* allow ms also for seconds, as long + as it's divisible by seconds */ + num /= 1000; + } + multiply = 1; + p = ""; + break; + } + *error_r = t_strdup_printf( + "Milliseconds not supported for this setting: %s", str); + return -1; + } + break; + case 'H': + multiply *= 60*60; + if (strncasecmp(p, "hours", strlen(p)) == 0) + p = ""; + break; + case 'D': + multiply *= 60*60*24; + if (strncasecmp(p, "days", strlen(p)) == 0) + p = ""; + break; + case 'W': + multiply *= 60*60*24*7; + if (strncasecmp(p, "weeks", strlen(p)) == 0) + p = ""; + break; + } + + if (*p != '\0') { + *error_r = t_strconcat("Invalid time interval: ", str, NULL); + return -1; + } + if (num > UINT_MAX / multiply) { + *error_r = t_strconcat("Time interval is too large: ", + str, NULL); + return -1; + } + *interval_r = num * multiply; + return 0; +} + +int settings_get_time(const char *str, unsigned int *secs_r, + const char **error_r) +{ + return settings_get_time_full(str, secs_r, FALSE, error_r); +} + +int settings_get_time_msecs(const char *str, unsigned int *msecs_r, + const char **error_r) +{ + return settings_get_time_full(str, msecs_r, TRUE, error_r); +} + +int settings_get_size(const char *str, uoff_t *bytes_r, + const char **error_r) +{ + uintmax_t num, multiply = 1; + const char *p; + + if (str_parse_uintmax(str, &num, &p) < 0) { + *error_r = t_strconcat("Invalid size: ", str, NULL); + return -1; + } + while (*p == ' ') p++; + switch (i_toupper(*p)) { + case 'B': + multiply = 1; + p += 1; + break; + case 'K': + multiply = 1024; + p += 1; + break; + case 'M': + multiply = 1024*1024; + p += 1; + break; + case 'G': + multiply = 1024*1024*1024; + p += 1; + break; + case 'T': + multiply = 1024ULL*1024*1024*1024; + p += 1; + break; + } + + if (multiply > 1) { + /* Allow: k, ki, kiB */ + if (i_toupper(*p) == 'I') + p++; + if (i_toupper(*p) == 'B') + p++; + } + if (*p != '\0') { + *error_r = t_strconcat("Invalid size: ", str, NULL); + return -1; + } + if (num > (UOFF_T_MAX) / multiply) { + *error_r = t_strconcat("Size is too large: ", str, NULL); + return -1; + } + *bytes_r = num * multiply; + return 0; +} + +static int get_enum(struct setting_parser_context *ctx, const char *value, + char **result_r, const char *allowed_values) +{ + const char *p; + + while (allowed_values != NULL) { + p = strchr(allowed_values, ':'); + if (p == NULL) { + if (strcmp(allowed_values, value) == 0) + break; + + ctx->error = p_strconcat(ctx->parser_pool, + "Invalid value: ", + value, NULL); + return -1; + } + + if (strncmp(allowed_values, value, p - allowed_values) == 0 && + value[p - allowed_values] == '\0') + break; + + allowed_values = p + 1; + } + + *result_r = p_strdup(ctx->set_pool, value); + return 0; +} + +static void +setting_link_init_set_struct(struct setting_parser_context *ctx, + struct setting_link *link) +{ + void *ptr; + + link->set_struct = p_malloc(ctx->set_pool, link->info->struct_size); + if ((ctx->flags & SETTINGS_PARSER_FLAG_TRACK_CHANGES) != 0) { + link->change_struct = + p_malloc(ctx->set_pool, link->info->struct_size); + array_push_back(link->change_array, &link->change_struct); + } + + setting_parser_copy_defaults(ctx, link->info, link); + array_push_back(link->array, &link->set_struct); + + if (link->info->parent_offset != SIZE_MAX && link->parent != NULL) { + ptr = STRUCT_MEMBER_P(link->set_struct, + link->info->parent_offset); + *((void **)ptr) = link->parent->set_struct; + } +} + +static int ATTR_NULL(2) +setting_link_add(struct setting_parser_context *ctx, + const struct setting_define *def, + const struct setting_link *link_copy, char *key) +{ + struct setting_link *link; + + link = hash_table_lookup(ctx->links, key); + if (link != NULL) { + if (link->parent == link_copy->parent && + link->info == link_copy->info && + (def == NULL || def->type == SET_DEFLIST_UNIQUE)) + return 0; + ctx->error = p_strconcat(ctx->parser_pool, key, + " already exists", NULL); + return -1; + } + + link = p_new(ctx->parser_pool, struct setting_link, 1); + *link = *link_copy; + link->full_key = key; + i_assert(hash_table_lookup(ctx->links, key) == NULL); + hash_table_insert(ctx->links, key, link); + + if (link->info->struct_size != 0) + setting_link_init_set_struct(ctx, link); + return 0; +} + +static int ATTR_NULL(3, 8) +get_deflist(struct setting_parser_context *ctx, struct setting_link *parent, + const struct setting_define *def, + const struct setting_parser_info *info, + const char *key, const char *value, ARRAY_TYPE(void_array) *result, + ARRAY_TYPE(void_array) *change_result) +{ + struct setting_link new_link; + const char *const *list; + char *full_key; + + i_assert(info->defines != NULL || info == &strlist_info); + + if (!array_is_created(result)) + p_array_init(result, ctx->set_pool, 5); + if (change_result != NULL && !array_is_created(change_result)) + p_array_init(change_result, ctx->set_pool, 5); + + i_zero(&new_link); + new_link.parent = parent; + new_link.info = info; + new_link.array = result; + new_link.change_array = change_result; + + if (info == &strlist_info) { + /* there are no sections below strlist, so allow referencing it + without the key (e.g. plugin/foo instead of plugin/0/foo) */ + full_key = p_strdup(ctx->parser_pool, key); + if (setting_link_add(ctx, def, &new_link, full_key) < 0) + return -1; + } + + list = t_strsplit(value, ",\t "); + for (; *list != NULL; list++) { + if (**list == '\0') + continue; + + full_key = p_strconcat(ctx->parser_pool, key, + SETTINGS_SEPARATOR_S, *list, NULL); + if (setting_link_add(ctx, def, &new_link, full_key) < 0) + return -1; + } + return 0; +} + +static int +get_in_port_zero(struct setting_parser_context *ctx, const char *value, + in_port_t *result_r) +{ + if (net_str2port_zero(value, result_r) < 0) { + ctx->error = p_strdup_printf(ctx->parser_pool, + "Invalid port number %s", value); + return -1; + } + return 0; +} + +static int +settings_parse(struct setting_parser_context *ctx, struct setting_link *link, + const struct setting_define *def, + const char *key, const char *value) +{ + void *ptr, *change_ptr; + const void *ptr2; + const char *error; + + while (def->type == SET_ALIAS) { + i_assert(def != link->info->defines); + def--; + } + + ctx->prev_info = link->info; + + if (link->set_struct == NULL) + setting_link_init_set_struct(ctx, link); + + change_ptr = link->change_struct == NULL ? NULL : + STRUCT_MEMBER_P(link->change_struct, def->offset); + + ptr = STRUCT_MEMBER_P(link->set_struct, def->offset); + switch (def->type) { + case SET_BOOL: + if (get_bool(ctx, value, (bool *)ptr) < 0) + return -1; + break; + case SET_UINT: + if (get_uint(ctx, value, (unsigned int *)ptr) < 0) + return -1; + break; + case SET_UINT_OCT: + if (get_octal(ctx, value, (unsigned int *)ptr) < 0) + return -1; + break; + case SET_TIME: + if (settings_get_time(value, (unsigned int *)ptr, &error) < 0) { + ctx->error = p_strdup(ctx->parser_pool, error); + return -1; + } + break; + case SET_TIME_MSECS: + if (settings_get_time_msecs(value, (unsigned int *)ptr, &error) < 0) { + ctx->error = p_strdup(ctx->parser_pool, error); + return -1; + } + break; + case SET_SIZE: + if (settings_get_size(value, (uoff_t *)ptr, &error) < 0) { + ctx->error = p_strdup(ctx->parser_pool, error); + return -1; + } + break; + case SET_IN_PORT: + if (get_in_port_zero(ctx, value, (in_port_t *)ptr) < 0) + return -1; + break; + case SET_STR: + *((char **)ptr) = p_strdup(ctx->set_pool, value); + break; + case SET_STR_VARS: + *((char **)ptr) = p_strconcat(ctx->set_pool, + ctx->str_vars_are_expanded ? + SETTING_STRVAR_EXPANDED : + SETTING_STRVAR_UNEXPANDED, + value, NULL); + break; + case SET_ENUM: + /* get the available values from default string */ + i_assert(link->info->defaults != NULL); + ptr2 = CONST_STRUCT_MEMBER_P(link->info->defaults, def->offset); + if (get_enum(ctx, value, (char **)ptr, + *(const char *const *)ptr2) < 0) + return -1; + break; + case SET_DEFLIST: + case SET_DEFLIST_UNIQUE: + ctx->prev_info = def->list_info; + return get_deflist(ctx, link, def, def->list_info, + key, value, (ARRAY_TYPE(void_array) *)ptr, + (ARRAY_TYPE(void_array) *)change_ptr); + case SET_STRLIST: { + ctx->prev_info = &strlist_info; + if (get_deflist(ctx, link, NULL, &strlist_info, key, value, + (ARRAY_TYPE(void_array) *)ptr, NULL) < 0) + return -1; + break; + } + case SET_ALIAS: + i_unreached(); + break; + } + + if (change_ptr != NULL) + *((char *)change_ptr) = 1; + return 0; +} + +static bool +settings_find_key_nth(struct setting_parser_context *ctx, const char *key, + unsigned int *n, const struct setting_define **def_r, + struct setting_link **link_r) +{ + const struct setting_define *def; + struct setting_link *link; + const char *end, *parent_key; + unsigned int i; + + /* try to find from roots */ + for (i = *n; i < ctx->root_count; i++) { + def = setting_define_find(ctx->roots[i].info, key); + if (def != NULL) { + *n = i + 1; + *def_r = def; + *link_r = &ctx->roots[i]; + return TRUE; + } + } + if (*n > ctx->root_count) + return FALSE; + *n += 1; + + /* try to find from links */ + end = strrchr(key, SETTINGS_SEPARATOR); + if (end == NULL) + return FALSE; + + parent_key = t_strdup_until(key, end); + link = hash_table_lookup(ctx->links, parent_key); + if (link == NULL) { + /* maybe this is the first strlist value */ + unsigned int parent_n = 0; + const struct setting_define *parent_def; + struct setting_link *parent_link; + + if (!settings_find_key_nth(ctx, parent_key, &parent_n, + &parent_def, &parent_link)) + return FALSE; + if (parent_def == NULL) { + /* we'll get here with e.g. "plugin/a/b=val". + not sure if we should ever do anything here.. */ + if (parent_link->full_key == NULL || + strcmp(parent_link->full_key, parent_key) != 0) + return FALSE; + } else { + if (parent_def->type != SET_STRLIST) + return FALSE; + } + + /* setting parent_key=0 adds it to links list */ + if (settings_parse_keyvalue(ctx, parent_key, "0") <= 0) + return FALSE; + + link = hash_table_lookup(ctx->links, parent_key); + i_assert(link != NULL); + } + + *link_r = link; + if (link->info == &strlist_info) { + *def_r = NULL; + return TRUE; + } else { + *def_r = setting_define_find(link->info, end + 1); + return *def_r != NULL; + } +} + +static bool +settings_find_key(struct setting_parser_context *ctx, const char *key, + const struct setting_define **def_r, + struct setting_link **link_r) +{ + unsigned int n = 0; + + return settings_find_key_nth(ctx, key, &n, def_r, link_r); +} + +static void +settings_parse_strlist(struct setting_parser_context *ctx, + struct setting_link *link, + const char *key, const char *value) +{ + void *const *items; + void *vkey, *vvalue; + unsigned int i, count; + + key = strrchr(key, SETTINGS_SEPARATOR) + 1; + vvalue = p_strdup(ctx->set_pool, value); + + /* replace if it already exists */ + items = array_get(link->array, &count); + for (i = 0; i < count; i += 2) { + if (strcmp(items[i], key) == 0) { + array_idx_set(link->array, i + 1, &vvalue); + return; + } + } + + vkey = p_strdup(ctx->set_pool, key); + array_push_back(link->array, &vkey); + array_push_back(link->array, &vvalue); +} + +int settings_parse_keyvalue(struct setting_parser_context *ctx, + const char *key, const char *value) +{ + const struct setting_define *def; + struct setting_link *link; + unsigned int n = 0; + + ctx->error = NULL; + ctx->prev_info = NULL; + + if (!settings_find_key_nth(ctx, key, &n, &def, &link)) { + ctx->error = p_strconcat(ctx->parser_pool, + "Unknown setting: ", key, NULL); + return 0; + } + + do { + if (def == NULL) { + i_assert(link->info == &strlist_info); + settings_parse_strlist(ctx, link, key, value); + return 1; + } + + if (settings_parse(ctx, link, def, key, value) < 0) + return -1; + /* there may be more instances of the setting */ + } while (settings_find_key_nth(ctx, key, &n, &def, &link)); + return 1; +} + +bool settings_parse_is_valid_key(struct setting_parser_context *ctx, + const char *key) +{ + const struct setting_define *def; + struct setting_link *link; + + return settings_find_key(ctx, key, &def, &link); +} + +const char *settings_parse_unalias(struct setting_parser_context *ctx, + const char *key) +{ + const struct setting_define *def; + struct setting_link *link; + + if (!settings_find_key(ctx, key, &def, &link)) + return NULL; + if (def == NULL) { + /* strlist */ + i_assert(link->info == &strlist_info); + return key; + } + + while (def->type == SET_ALIAS) { + i_assert(def != link->info->defines); + def--; + } + return def->key; +} + +const void * +settings_parse_get_value(struct setting_parser_context *ctx, + const char *key, enum setting_type *type_r) +{ + const struct setting_define *def; + struct setting_link *link; + + if (!settings_find_key(ctx, key, &def, &link)) + return NULL; + if (link->set_struct == NULL || def == NULL) + return NULL; + + *type_r = def->type; + return STRUCT_MEMBER_P(link->set_struct, def->offset); +} + +bool settings_parse_is_changed(struct setting_parser_context *ctx, + const char *key) +{ + const struct setting_define *def; + struct setting_link *link; + const unsigned char *p; + + if (!settings_find_key(ctx, key, &def, &link)) + return FALSE; + if (link->change_struct == NULL || def == NULL) + return FALSE; + + p = STRUCT_MEMBER_P(link->change_struct, def->offset); + return *p != 0; +} + +int settings_parse_line(struct setting_parser_context *ctx, const char *line) +{ + const char *key, *value; + int ret; + + key = line; + value = strchr(line, '='); + if (value == NULL) { + ctx->error = "Missing '='"; + return -1; + } + + if (key == value) { + ctx->error = "Missing key name ('=' at the beginning of line)"; + return -1; + } + + T_BEGIN { + key = t_strdup_until(key, value); + ret = settings_parse_keyvalue(ctx, key, value + 1); + } T_END; + return ret; +} + +const struct setting_parser_info * +settings_parse_get_prev_info(struct setting_parser_context *ctx) +{ + return ctx->prev_info; +} + +static const char *settings_translate_lf(const char *value) +{ + char *dest, *p; + + if (strchr(value, SETTING_STREAM_LF_CHAR[0]) == NULL) + return value; + + dest = t_strdup_noconst(value); + for (p = dest; *p != '\0'; p++) { + if (*p == SETTING_STREAM_LF_CHAR[0]) + *p = '\n'; + } + return dest; +} + +int settings_parse_stream(struct setting_parser_context *ctx, + struct istream *input) +{ + bool ignore_unknown_keys = + (ctx->flags & SETTINGS_PARSER_FLAG_IGNORE_UNKNOWN_KEYS) != 0; + const char *line; + int ret; + + while ((line = i_stream_next_line(input)) != NULL) { + if (*line == '\0') { + /* empty line finishes it */ + return 0; + } + ctx->linenum++; + if (ctx->linenum == 1 && str_begins(line, "ERROR ")) { + ctx->error = p_strdup(ctx->parser_pool, line + 6); + return -1; + } + + T_BEGIN { + line = settings_translate_lf(line); + ret = settings_parse_line(ctx, line); + } T_END; + + if (ret < 0 || (ret == 0 && !ignore_unknown_keys)) { + ctx->error = p_strdup_printf(ctx->parser_pool, + "Line %u: %s", ctx->linenum, ctx->error); + return -1; + } + } + return 1; +} + +int settings_parse_stream_read(struct setting_parser_context *ctx, + struct istream *input) +{ + int ret; + + do { + if ((ret = settings_parse_stream(ctx, input)) < 0) + return -1; + if (ret == 0) { + /* empty line read */ + return 0; + } + } while ((ret = i_stream_read(input)) > 0); + + switch (ret) { + case -1: + if (ctx->error != NULL) + break; + if (input->stream_errno != 0) { + ctx->error = p_strdup_printf(ctx->parser_pool, + "read(%s) failed: %s", i_stream_get_name(input), + i_stream_get_error(input)); + } else if (input->v_offset == 0) { + ctx->error = p_strdup_printf(ctx->parser_pool, + "read(%s) disconnected before receiving any data", + i_stream_get_name(input)); + } else { + ctx->error = p_strdup_printf(ctx->parser_pool, + "read(%s) disconnected before receiving " + "end-of-settings line", + i_stream_get_name(input)); + } + break; + case -2: + ctx->error = p_strdup_printf(ctx->parser_pool, + "Line %u: line too long", + ctx->linenum); + break; + case 0: + /* blocks */ + return 1; + default: + i_unreached(); + } + return -1; +} + +int settings_parse_file(struct setting_parser_context *ctx, + const char *path, size_t max_line_length) +{ + struct istream *input; + int fd, ret; + + fd = open(path, O_RDONLY); + if (fd < 0) { + ctx->error = p_strdup_printf(ctx->parser_pool, + "open(%s) failed: %m", path); + return -1; + } + + input = i_stream_create_fd_autoclose(&fd, max_line_length); + i_stream_set_name(input, path); + ret = settings_parse_stream_read(ctx, input); + i_stream_unref(&input); + + return ret; +} + +static int environ_cmp(char *const *s1, char *const *s2) +{ + return -strcmp(*s1, *s2); +} + +int settings_parse_environ(struct setting_parser_context *ctx) +{ + char **environ = *env_get_environ_p(); + ARRAY_TYPE(string) sorted_envs_arr; + const char *key, *value; + char *const *sorted_envs; + unsigned int i, count; + int ret = 0; + + if (environ == NULL) + return 0; + + /* sort the settings first. this is necessary for putenv() + implementations (e.g. valgrind) which change the order of strings + in environ[] */ + i_array_init(&sorted_envs_arr, 128); + for (i = 0; environ[i] != NULL; i++) + array_push_back(&sorted_envs_arr, &environ[i]); + array_sort(&sorted_envs_arr, environ_cmp); + sorted_envs = array_get(&sorted_envs_arr, &count); + + for (i = 0; i < count && ret == 0; i++) { + value = strchr(sorted_envs[i], '='); + if (value != NULL) T_BEGIN { + key = t_strdup_until(sorted_envs[i], value++); + key = t_str_lcase(key); + if (settings_parse_keyvalue(ctx, key, value) < 0) { + ctx->error = p_strdup_printf(ctx->parser_pool, + "Invalid setting %s: %s", + key, ctx->error); + ret = -1; + } + } T_END; + } + array_free(&sorted_envs_arr); + return ret; +} + +int settings_parse_exec(struct setting_parser_context *ctx, + const char *bin_path, const char *config_path, + const char *service) +{ + struct istream *input; + pid_t pid; + int ret, fd[2], status; + + if (pipe(fd) < 0) { + i_error("pipe() failed: %m"); + return -1; + } + + pid = fork(); + if (pid == (pid_t)-1) { + i_error("fork() failed: %m"); + i_close_fd(&fd[0]); + i_close_fd(&fd[1]); + return -1; + } + if (pid == 0) { + /* child */ + static const char *argv[] = { + NULL, + "-c", NULL, + "-p", NULL, + NULL + }; + argv[0] = bin_path; + argv[2] = config_path; + argv[4] = service; + i_close_fd(&fd[0]); + if (dup2(fd[1], STDOUT_FILENO) < 0) + i_fatal("dup2() failed: %m"); + + execv_const(argv[0], argv); + } + i_close_fd(&fd[1]); + + input = i_stream_create_fd_autoclose(&fd[0], SIZE_MAX); + i_stream_set_name(input, bin_path); + ret = settings_parse_stream_read(ctx, input); + i_stream_destroy(&input); + + if (waitpid(pid, &status, 0) < 0) { + i_error("waitpid() failed: %m"); + ret = -1; + } else if (status != 0) { + i_error("%s returned failure: %d", bin_path, status); + ret = -1; + } + return ret; +} + +static bool +settings_check_dynamic(const struct setting_parser_info *info, pool_t pool, + void *set, const char **error_r) +{ + unsigned int i; + + if (info->dynamic_parsers == NULL) + return TRUE; + + for (i = 0; info->dynamic_parsers[i].name != NULL; i++) { + struct dynamic_settings_parser *dyn = &info->dynamic_parsers[i]; + + if (!settings_check(dyn->info, pool, + PTR_OFFSET(set, dyn->struct_offset), + error_r)) + return FALSE; + } + return TRUE; +} + +bool settings_check(const struct setting_parser_info *info, pool_t pool, + void *set, const char **error_r) +{ + const struct setting_define *def; + const ARRAY_TYPE(void_array) *val; + void *const *children; + unsigned int i, count; + bool valid; + + if (info->check_func != NULL) { + T_BEGIN { + valid = info->check_func(set, pool, error_r); + } T_END_PASS_STR_IF(!valid, error_r); + if (!valid) + return FALSE; + } + + for (def = info->defines; def->key != NULL; def++) { + if (!SETTING_TYPE_IS_DEFLIST(def->type)) + continue; + + val = CONST_PTR_OFFSET(set, def->offset); + if (!array_is_created(val)) + continue; + + children = array_get(val, &count); + for (i = 0; i < count; i++) { + if (!settings_check(def->list_info, pool, + children[i], error_r)) + return FALSE; + } + } + return settings_check_dynamic(info, pool, set, error_r); +} + +bool settings_parser_check(struct setting_parser_context *ctx, pool_t pool, + const char **error_r) +{ + unsigned int i; + + for (i = 0; i < ctx->root_count; i++) { + if (!settings_check(ctx->roots[i].info, pool, + ctx->roots[i].set_struct, error_r)) + return FALSE; + } + return TRUE; +} + +void settings_parse_set_expanded(struct setting_parser_context *ctx, + bool is_expanded) +{ + ctx->str_vars_are_expanded = is_expanded; +} + +void settings_parse_set_key_expanded(struct setting_parser_context *ctx, + pool_t pool, const char *key) +{ + const struct setting_define *def; + struct setting_link *link; + const char **val; + + if (!settings_find_key(ctx, key, &def, &link)) + return; + if (def == NULL) { + /* parent is strlist, no expansion needed */ + i_assert(link->info == &strlist_info); + return; + } + + val = PTR_OFFSET(link->set_struct, def->offset); + if (def->type == SET_STR_VARS && *val != NULL) { + i_assert(**val == SETTING_STRVAR_UNEXPANDED[0] || + **val == SETTING_STRVAR_EXPANDED[0]); + *val = p_strconcat(pool, SETTING_STRVAR_EXPANDED, + *val + 1, NULL); + } +} + +void settings_parse_set_keys_expanded(struct setting_parser_context *ctx, + pool_t pool, const char *const *keys) +{ + for (; *keys != NULL; keys++) + settings_parse_set_key_expanded(ctx, pool, *keys); +} + +static int ATTR_NULL(3, 4, 5) +settings_var_expand_info(const struct setting_parser_info *info, void *set, + pool_t pool, + const struct var_expand_table *table, + const struct var_expand_func_table *func_table, + void *func_context, string_t *str, + const char **error_r) +{ + const struct setting_define *def; + void *value, *const *children; + const char *error; + unsigned int i, count; + int ret, final_ret = 1; + + for (def = info->defines; def->key != NULL; def++) { + value = PTR_OFFSET(set, def->offset); + switch (def->type) { + case SET_BOOL: + case SET_UINT: + case SET_UINT_OCT: + case SET_TIME: + case SET_TIME_MSECS: + case SET_SIZE: + case SET_IN_PORT: + case SET_STR: + case SET_ENUM: + case SET_STRLIST: + case SET_ALIAS: + break; + case SET_STR_VARS: { + const char **val = value; + + if (*val == NULL) + break; + + if (table == NULL) { + i_assert(**val == SETTING_STRVAR_EXPANDED[0] || + **val == SETTING_STRVAR_UNEXPANDED[0]); + *val += 1; + } else if (**val == SETTING_STRVAR_UNEXPANDED[0]) { + str_truncate(str, 0); + ret = var_expand_with_funcs(str, *val + 1, table, + func_table, func_context, + &error); + if (final_ret > ret) { + final_ret = ret; + *error_r = t_strdup_printf( + "%s: %s", def->key, error); + } + *val = p_strdup(pool, str_c(str)); + } else { + i_assert(**val == SETTING_STRVAR_EXPANDED[0]); + *val += 1; + } + break; + } + case SET_DEFLIST: + case SET_DEFLIST_UNIQUE: { + const ARRAY_TYPE(void_array) *val = value; + + if (!array_is_created(val)) + break; + + children = array_get(val, &count); + for (i = 0; i < count; i++) { + ret = settings_var_expand_info(def->list_info, + children[i], pool, table, func_table, + func_context, str, &error); + if (final_ret > ret) { + final_ret = ret; + *error_r = error; + } + } + break; + } + } + } + + if (final_ret <= 0) + return final_ret; + + if (info->expand_check_func != NULL) { + if (!info->expand_check_func(set, pool, error_r)) + return -1; + } + if (info->dynamic_parsers != NULL) { + for (i = 0; info->dynamic_parsers[i].name != NULL; i++) { + struct dynamic_settings_parser *dyn = &info->dynamic_parsers[i]; + const struct setting_parser_info *dinfo = dyn->info; + void *dset = PTR_OFFSET(set, dyn->struct_offset); + + if (dinfo->expand_check_func != NULL) { + if (!dinfo->expand_check_func(dset, pool, error_r)) + return -1; + } + } + } + + return final_ret; +} + +int settings_var_expand(const struct setting_parser_info *info, + void *set, pool_t pool, + const struct var_expand_table *table, + const char **error_r) +{ + return settings_var_expand_with_funcs(info, set, pool, table, + NULL, NULL, error_r); +} + +int settings_var_expand_with_funcs(const struct setting_parser_info *info, + void *set, pool_t pool, + const struct var_expand_table *table, + const struct var_expand_func_table *func_table, + void *func_context, const char **error_r) +{ + int ret; + + T_BEGIN { + string_t *str = t_str_new(256); + + ret = settings_var_expand_info(info, set, pool, table, + func_table, func_context, str, + error_r); + } T_END_PASS_STR_IF(ret <= 0, error_r); + return ret; +} + +void settings_parse_var_skip(struct setting_parser_context *ctx) +{ + unsigned int i; + const char *error; + + for (i = 0; i < ctx->root_count; i++) { + (void)settings_var_expand_info(ctx->roots[i].info, + ctx->roots[i].set_struct, + NULL, NULL, NULL, NULL, NULL, + &error); + } +} + +bool settings_vars_have_key(const struct setting_parser_info *info, void *set, + char var_key, const char *long_var_key, + const char **key_r, const char **value_r) +{ + const struct setting_define *def; + const void *value; + void *const *children; + unsigned int i, count; + + for (def = info->defines; def->key != NULL; def++) { + value = CONST_PTR_OFFSET(set, def->offset); + switch (def->type) { + case SET_BOOL: + case SET_UINT: + case SET_UINT_OCT: + case SET_TIME: + case SET_TIME_MSECS: + case SET_SIZE: + case SET_IN_PORT: + case SET_STR: + case SET_ENUM: + case SET_STRLIST: + case SET_ALIAS: + break; + case SET_STR_VARS: { + const char *const *val = value; + + if (*val == NULL) + break; + + if (**val == SETTING_STRVAR_UNEXPANDED[0]) { + if (var_has_key(*val + 1, var_key, + long_var_key)) { + *key_r = def->key; + *value_r = *val + 1; + return TRUE; + } + } else { + i_assert(**val == SETTING_STRVAR_EXPANDED[0]); + } + break; + } + case SET_DEFLIST: + case SET_DEFLIST_UNIQUE: { + const ARRAY_TYPE(void_array) *val = value; + + if (!array_is_created(val)) + break; + + children = array_get(val, &count); + for (i = 0; i < count; i++) { + if (settings_vars_have_key(def->list_info, + children[i], var_key, + long_var_key, + key_r, value_r)) + return TRUE; + } + break; + } + } + } + return FALSE; +} + +static void settings_set_parent(const struct setting_parser_info *info, + void *child, void *parent) +{ + void **ptr; + + if (info->parent_offset == SIZE_MAX) + return; + + ptr = PTR_OFFSET(child, info->parent_offset); + *ptr = parent; +} + +static bool +setting_copy(enum setting_type type, const void *src, void *dest, pool_t pool, + bool keep_values) +{ + switch (type) { + case SET_BOOL: { + const bool *src_bool = src; + bool *dest_bool = dest; + + *dest_bool = *src_bool; + break; + } + case SET_UINT: + case SET_UINT_OCT: + case SET_TIME: + case SET_TIME_MSECS: { + const unsigned int *src_uint = src; + unsigned int *dest_uint = dest; + + *dest_uint = *src_uint; + break; + } + case SET_SIZE: { + const uoff_t *src_size = src; + uoff_t *dest_size = dest; + + *dest_size = *src_size; + break; + } + case SET_IN_PORT: { + const in_port_t *src_size = src; + in_port_t *dest_size = dest; + + *dest_size = *src_size; + break; + } + case SET_STR_VARS: + case SET_STR: + case SET_ENUM: { + const char *const *src_str = src; + const char **dest_str = dest; + + if (keep_values) + *dest_str = *src_str; + else + *dest_str = p_strdup(pool, *src_str); + break; + } + case SET_DEFLIST: + case SET_DEFLIST_UNIQUE: + return FALSE; + case SET_STRLIST: { + const ARRAY_TYPE(const_string) *src_arr = src; + ARRAY_TYPE(const_string) *dest_arr = dest; + const char *const *strings, *const *dest_strings, *dup; + unsigned int i, j, count, dest_count; + + if (!array_is_created(src_arr)) + break; + + strings = array_get(src_arr, &count); + i_assert(count % 2 == 0); + if (!array_is_created(dest_arr)) + p_array_init(dest_arr, pool, count); + dest_count = array_count(dest_arr); + i_assert(dest_count % 2 == 0); + for (i = 0; i < count; i += 2) { + if (dest_count > 0) { + dest_strings = array_front(dest_arr); + for (j = 0; j < dest_count; j += 2) { + if (strcmp(strings[i], dest_strings[j]) == 0) + break; + } + if (j < dest_count) + continue; + } + dup = keep_values ? strings[i] : p_strdup(pool, strings[i]); + array_push_back(dest_arr, &dup); + dup = keep_values ? strings[i+1] : p_strdup(pool, strings[i+1]); + array_push_back(dest_arr, &dup); + } + break; + } + case SET_ALIAS: + break; + } + return TRUE; +} + +static void *settings_dup_full(const struct setting_parser_info *info, + const void *set, pool_t pool, bool keep_values) +{ + const struct setting_define *def; + const void *src; + void *dest_set, *dest, *const *children; + unsigned int i, count; + + if (info->struct_size == 0) + return NULL; + + /* don't just copy everything from set to dest_set. it may contain + some non-setting fields allocated from the original pool. */ + dest_set = p_malloc(pool, info->struct_size); + for (def = info->defines; def->key != NULL; def++) { + src = CONST_PTR_OFFSET(set, def->offset); + dest = PTR_OFFSET(dest_set, def->offset); + + if (!setting_copy(def->type, src, dest, pool, keep_values)) { + const ARRAY_TYPE(void_array) *src_arr = src; + ARRAY_TYPE(void_array) *dest_arr = dest; + void *child_set; + + if (!array_is_created(src_arr)) + continue; + + children = array_get(src_arr, &count); + p_array_init(dest_arr, pool, count); + for (i = 0; i < count; i++) { + child_set = settings_dup_full(def->list_info, + children[i], pool, + keep_values); + array_push_back(dest_arr, &child_set); + settings_set_parent(def->list_info, child_set, + dest_set); + } + } + } + return dest_set; +} + +void *settings_dup(const struct setting_parser_info *info, + const void *set, pool_t pool) +{ + return settings_dup_full(info, set, pool, FALSE); +} + +void *settings_dup_with_pointers(const struct setting_parser_info *info, + const void *set, pool_t pool) +{ + return settings_dup_full(info, set, pool, TRUE); +} + +static void * +settings_changes_dup(const struct setting_parser_info *info, + const void *change_set, pool_t pool) +{ + const struct setting_define *def; + const void *src; + void *dest_set, *dest, *const *children; + unsigned int i, count; + + if (change_set == NULL || info->struct_size == 0) + return NULL; + + dest_set = p_malloc(pool, info->struct_size); + for (def = info->defines; def->key != NULL; def++) { + src = CONST_PTR_OFFSET(change_set, def->offset); + dest = PTR_OFFSET(dest_set, def->offset); + + switch (def->type) { + case SET_BOOL: + case SET_UINT: + case SET_UINT_OCT: + case SET_TIME: + case SET_TIME_MSECS: + case SET_SIZE: + case SET_IN_PORT: + case SET_STR_VARS: + case SET_STR: + case SET_ENUM: + case SET_STRLIST: + *((char *)dest) = *((const char *)src); + break; + case SET_DEFLIST: + case SET_DEFLIST_UNIQUE: { + const ARRAY_TYPE(void_array) *src_arr = src; + ARRAY_TYPE(void_array) *dest_arr = dest; + void *child_set; + + if (!array_is_created(src_arr)) + break; + + children = array_get(src_arr, &count); + p_array_init(dest_arr, pool, count); + for (i = 0; i < count; i++) { + child_set = settings_changes_dup(def->list_info, + children[i], + pool); + array_push_back(dest_arr, &child_set); + } + break; + } + case SET_ALIAS: + break; + } + } + return dest_set; +} + +static void +info_update_real(pool_t pool, struct setting_parser_info *parent, + const struct dynamic_settings_parser *parsers) +{ + /* @UNSAFE */ + ARRAY(struct setting_define) defines; + ARRAY_TYPE(dynamic_settings_parser) dynamic_parsers; + struct dynamic_settings_parser new_parser; + const struct setting_define *cur_defines; + struct setting_define *new_defines, new_define; + void *parent_defaults; + unsigned int i, j; + size_t offset, new_struct_size; + + t_array_init(&defines, 128); + /* add existing defines */ + for (j = 0; parent->defines[j].key != NULL; j++) + array_push_back(&defines, &parent->defines[j]); + new_struct_size = MEM_ALIGN(parent->struct_size); + + /* add new dynamic defines */ + for (i = 0; parsers[i].name != NULL; i++) { + i_assert(parsers[i].info->parent == parent); + cur_defines = parsers[i].info->defines; + for (j = 0; cur_defines[j].key != NULL; j++) { + new_define = cur_defines[j]; + new_define.offset += new_struct_size; + array_push_back(&defines, &new_define); + } + new_struct_size += MEM_ALIGN(parsers[i].info->struct_size); + } + new_defines = p_new(pool, struct setting_define, + array_count(&defines) + 1); + memcpy(new_defines, array_front(&defines), + sizeof(*parent->defines) * array_count(&defines)); + parent->defines = new_defines; + + /* update defaults */ + parent_defaults = p_malloc(pool, new_struct_size); + memcpy(parent_defaults, parent->defaults, parent->struct_size); + offset = MEM_ALIGN(parent->struct_size); + for (i = 0; parsers[i].name != NULL; i++) { + memcpy(PTR_OFFSET(parent_defaults, offset), + parsers[i].info->defaults, parsers[i].info->struct_size); + offset += MEM_ALIGN(parsers[i].info->struct_size); + } + parent->defaults = parent_defaults; + + /* update dynamic parsers list */ + t_array_init(&dynamic_parsers, 32); + if (parent->dynamic_parsers != NULL) { + for (i = 0; parent->dynamic_parsers[i].name != NULL; i++) { + array_push_back(&dynamic_parsers, + &parent->dynamic_parsers[i]); + } + } + offset = MEM_ALIGN(parent->struct_size); + for (i = 0; parsers[i].name != NULL; i++) { + new_parser = parsers[i]; + new_parser.name = p_strdup(pool, new_parser.name); + new_parser.struct_offset = offset; + array_push_back(&dynamic_parsers, &new_parser); + offset += MEM_ALIGN(parsers[i].info->struct_size); + } + parent->dynamic_parsers = + p_new(pool, struct dynamic_settings_parser, + array_count(&dynamic_parsers) + 1); + memcpy(parent->dynamic_parsers, array_front(&dynamic_parsers), + sizeof(*parent->dynamic_parsers) * + array_count(&dynamic_parsers)); + parent->struct_size = new_struct_size; +} + +void settings_parser_info_update(pool_t pool, + struct setting_parser_info *parent, + const struct dynamic_settings_parser *parsers) +{ + if (parsers[0].name != NULL) T_BEGIN { + info_update_real(pool, parent, parsers); + } T_END; +} + +static void +settings_parser_update_children_parent(struct setting_parser_info *parent, + pool_t pool) +{ + struct setting_define *new_defs; + struct setting_parser_info *new_info; + unsigned int i, count; + + for (count = 0; parent->defines[count].key != NULL; count++) ; + + new_defs = p_new(pool, struct setting_define, count + 1); + memcpy(new_defs, parent->defines, sizeof(*new_defs) * count); + parent->defines = new_defs; + + for (i = 0; i < count; i++) { + if (new_defs[i].list_info == NULL || + new_defs[i].list_info->parent == NULL) + continue; + + new_info = p_new(pool, struct setting_parser_info, 1); + *new_info = *new_defs[i].list_info; + new_info->parent = parent; + new_defs[i].list_info = new_info; + } +} + +void settings_parser_dyn_update(pool_t pool, + const struct setting_parser_info *const **_roots, + const struct dynamic_settings_parser *dyn_parsers) +{ + const struct setting_parser_info *const *roots = *_roots; + const struct setting_parser_info *old_parent, **new_roots; + struct setting_parser_info *new_parent, *new_info; + struct dynamic_settings_parser *new_dyn_parsers; + unsigned int i, count; + + /* settings_parser_info_update() modifies the parent structure. + since we may be using the same structure later, we want it to be + in its original state, so we'll have to copy all structures. */ + old_parent = dyn_parsers[0].info->parent; + new_parent = p_new(pool, struct setting_parser_info, 1); + *new_parent = *old_parent; + settings_parser_update_children_parent(new_parent, pool); + + /* update root */ + for (count = 0; roots[count] != NULL; count++) ; + new_roots = p_new(pool, const struct setting_parser_info *, count + 1); + for (i = 0; i < count; i++) { + if (roots[i] == old_parent) + new_roots[i] = new_parent; + else + new_roots[i] = roots[i]; + } + *_roots = new_roots; + + /* update parent in dyn_parsers */ + for (count = 0; dyn_parsers[count].name != NULL; count++) ; + new_dyn_parsers = p_new(pool, struct dynamic_settings_parser, count + 1); + for (i = 0; i < count; i++) { + new_dyn_parsers[i] = dyn_parsers[i]; + + new_info = p_new(pool, struct setting_parser_info, 1); + *new_info = *dyn_parsers[i].info; + new_info->parent = new_parent; + new_dyn_parsers[i].info = new_info; + } + + settings_parser_info_update(pool, new_parent, new_dyn_parsers); +} + +const void *settings_find_dynamic(const struct setting_parser_info *info, + const void *base_set, const char *name) +{ + unsigned int i; + + if (info->dynamic_parsers == NULL) + return NULL; + + for (i = 0; info->dynamic_parsers[i].name != NULL; i++) { + if (strcmp(info->dynamic_parsers[i].name, name) == 0) { + return CONST_PTR_OFFSET(base_set, + info->dynamic_parsers[i].struct_offset); + } + } + return NULL; +} + +static struct setting_link * +settings_link_get_new(struct setting_parser_context *new_ctx, + HASH_TABLE_TYPE(setting_link) links, + struct setting_link *old_link) +{ + struct setting_link *new_link; + void *const *old_sets, **new_sets; + unsigned int i, count, count2; + size_t diff; + + new_link = hash_table_lookup(links, old_link); + if (new_link != NULL) + return new_link; + + i_assert(old_link->parent != NULL); + i_assert(old_link->array != NULL); + + new_link = p_new(new_ctx->parser_pool, struct setting_link, 1); + new_link->info = old_link->info; + new_link->parent = settings_link_get_new(new_ctx, links, + old_link->parent); + + /* find the array from parent struct */ + diff = (char *)old_link->array - (char *)old_link->parent->set_struct; + i_assert(diff + sizeof(*old_link->array) <= old_link->parent->info->struct_size); + new_link->array = PTR_OFFSET(new_link->parent->set_struct, diff); + + if (old_link->set_struct != NULL) { + /* find our struct from array */ + old_sets = array_get(old_link->array, &count); + new_sets = array_get_modifiable(new_link->array, &count2); + i_assert(count == count2); + for (i = 0; i < count; i++) { + if (old_sets[i] == old_link->set_struct) { + new_link->set_struct = new_sets[i]; + break; + } + } + i_assert(i < count); + } + i_assert(hash_table_lookup(links, old_link) == NULL); + hash_table_insert(links, old_link, new_link); + return new_link; +} + +struct setting_parser_context * +settings_parser_dup(const struct setting_parser_context *old_ctx, + pool_t new_pool) +{ + struct setting_parser_context *new_ctx; + struct hash_iterate_context *iter; + HASH_TABLE_TYPE(setting_link) links; + struct setting_link *new_link, *value; + char *key; + unsigned int i; + pool_t parser_pool; + bool keep_values; + + /* if source and destination pools are the same, there's no need to + duplicate values */ + keep_values = new_pool == old_ctx->set_pool; + + pool_ref(new_pool); + parser_pool = pool_alloconly_create(MEMPOOL_GROWING"dup settings parser", + 1024); + new_ctx = p_new(parser_pool, struct setting_parser_context, 1); + new_ctx->set_pool = new_pool; + new_ctx->parser_pool = parser_pool; + new_ctx->flags = old_ctx->flags; + new_ctx->str_vars_are_expanded = old_ctx->str_vars_are_expanded; + new_ctx->linenum = old_ctx->linenum; + new_ctx->error = p_strdup(new_ctx->parser_pool, old_ctx->error); + new_ctx->prev_info = old_ctx->prev_info; + + hash_table_create_direct(&links, new_ctx->parser_pool, 0); + + new_ctx->root_count = old_ctx->root_count; + new_ctx->roots = p_new(new_ctx->parser_pool, struct setting_link, + new_ctx->root_count); + for (i = 0; i < new_ctx->root_count; i++) { + i_assert(old_ctx->roots[i].parent == NULL); + i_assert(old_ctx->roots[i].array == NULL); + + new_ctx->roots[i].info = old_ctx->roots[i].info; + new_ctx->roots[i].set_struct = + settings_dup_full(old_ctx->roots[i].info, + old_ctx->roots[i].set_struct, + new_ctx->set_pool, keep_values); + new_ctx->roots[i].change_struct = + settings_changes_dup(old_ctx->roots[i].info, + old_ctx->roots[i].change_struct, + new_ctx->set_pool); + hash_table_insert(links, &old_ctx->roots[i], + &new_ctx->roots[i]); + } + + hash_table_create(&new_ctx->links, new_ctx->parser_pool, 0, + strcase_hash, strcasecmp); + + iter = hash_table_iterate_init(old_ctx->links); + while (hash_table_iterate(iter, old_ctx->links, &key, &value)) { + new_link = settings_link_get_new(new_ctx, links, value); + key = p_strdup(new_ctx->parser_pool, key); + hash_table_insert(new_ctx->links, key, new_link); + } + hash_table_iterate_deinit(&iter); + hash_table_destroy(&links); + return new_ctx; +} + +static void * +settings_changes_init(const struct setting_parser_info *info, + const void *change_set, pool_t pool) +{ + const struct setting_define *def; + const ARRAY_TYPE(void_array) *src_arr; + ARRAY_TYPE(void_array) *dest_arr; + void *dest_set, *set, *const *children; + unsigned int i, count; + + if (info->struct_size == 0) + return NULL; + + dest_set = p_malloc(pool, info->struct_size); + for (def = info->defines; def->key != NULL; def++) { + if (!SETTING_TYPE_IS_DEFLIST(def->type)) + continue; + + src_arr = CONST_PTR_OFFSET(change_set, def->offset); + dest_arr = PTR_OFFSET(dest_set, def->offset); + + if (array_is_created(src_arr)) { + children = array_get(src_arr, &count); + i_assert(!array_is_created(dest_arr)); + p_array_init(dest_arr, pool, count); + for (i = 0; i < count; i++) { + set = settings_changes_init(def->list_info, + children[i], pool); + array_push_back(dest_arr, &set); + } + } + } + return dest_set; +} + +static void settings_copy_deflist(const struct setting_define *def, + const struct setting_link *src_link, + struct setting_link *dest_link, + pool_t pool) +{ + const ARRAY_TYPE(void_array) *src_arr; + ARRAY_TYPE(void_array) *dest_arr; + void *const *children, *child_set; + unsigned int i, count; + + src_arr = CONST_PTR_OFFSET(src_link->set_struct, def->offset); + dest_arr = PTR_OFFSET(dest_link->set_struct, def->offset); + + if (!array_is_created(src_arr)) + return; + + children = array_get(src_arr, &count); + if (!array_is_created(dest_arr)) + p_array_init(dest_arr, pool, count); + for (i = 0; i < count; i++) { + child_set = settings_dup(def->list_info, children[i], pool); + array_push_back(dest_arr, &child_set); + settings_set_parent(def->list_info, child_set, + dest_link->set_struct); + } + + /* copy changes */ + dest_arr = PTR_OFFSET(dest_link->change_struct, def->offset); + if (!array_is_created(dest_arr)) + p_array_init(dest_arr, pool, count); + for (i = 0; i < count; i++) { + child_set = settings_changes_init(def->list_info, + children[i], pool); + array_push_back(dest_arr, &child_set); + } +} + +static int +settings_copy_deflist_unique(const struct setting_define *def, + const struct setting_link *src_link, + struct setting_link *dest_link, + pool_t pool, const char **conflict_key_r) +{ + struct setting_link child_dest_link, child_src_link; + const ARRAY_TYPE(void_array) *src_arr, *src_carr; + ARRAY_TYPE(void_array) *dest_arr, *dest_carr; + void *const *src_children, *const *src_cchildren; + void *const *dest_children, *const *dest_cchildren, *child_set; + const char *const *src_namep, *const *dest_namep; + unsigned int i, j, src_count, dest_count, ccount; + unsigned int type_offset; + + i_assert(def->list_info->type_offset != SIZE_MAX); + + src_arr = CONST_PTR_OFFSET(src_link->set_struct, def->offset); + src_carr = CONST_PTR_OFFSET(src_link->change_struct, def->offset); + dest_arr = PTR_OFFSET(dest_link->set_struct, def->offset); + dest_carr = PTR_OFFSET(dest_link->change_struct, def->offset); + + if (!array_is_created(src_arr)) + return 0; + type_offset = def->list_info->type_offset; + + i_zero(&child_dest_link); + i_zero(&child_src_link); + + child_dest_link.info = child_src_link.info = def->list_info; + + src_children = array_get(src_arr, &src_count); + src_cchildren = array_get(src_carr, &ccount); + i_assert(src_count == ccount); + if (!array_is_created(dest_arr)) { + p_array_init(dest_arr, pool, src_count); + p_array_init(dest_carr, pool, src_count); + } + for (i = 0; i < src_count; i++) { + src_namep = CONST_PTR_OFFSET(src_children[i], type_offset); + dest_children = array_get(dest_arr, &dest_count); + dest_cchildren = array_get(dest_carr, &ccount); + i_assert(dest_count == ccount); + for (j = 0; j < dest_count; j++) { + dest_namep = CONST_PTR_OFFSET(dest_children[j], + type_offset); + if (strcmp(*src_namep, *dest_namep) == 0) + break; + } + + if (j < dest_count && **src_namep != '\0') { + /* merge */ + child_src_link.set_struct = src_children[i]; + child_src_link.change_struct = src_cchildren[i]; + child_dest_link.set_struct = dest_children[j]; + child_dest_link.change_struct = dest_cchildren[j]; + if (settings_apply(&child_dest_link, &child_src_link, + pool, conflict_key_r) < 0) + return -1; + } else { + /* append */ + child_set = settings_dup(def->list_info, + src_children[i], pool); + array_push_back(dest_arr, &child_set); + settings_set_parent(def->list_info, child_set, + dest_link->set_struct); + + child_set = settings_changes_init(def->list_info, + src_cchildren[i], + pool); + array_push_back(dest_carr, &child_set); + } + } + return 0; +} + +static int +settings_apply(struct setting_link *dest_link, + const struct setting_link *src_link, + pool_t pool, const char **conflict_key_r) +{ + const struct setting_define *def; + const void *src, *csrc; + void *dest, *cdest; + + for (def = dest_link->info->defines; def->key != NULL; def++) { + csrc = CONST_PTR_OFFSET(src_link->change_struct, def->offset); + cdest = PTR_OFFSET(dest_link->change_struct, def->offset); + + if (def->type == SET_DEFLIST || def->type == SET_STRLIST) { + /* just add the new values */ + } else if (def->type == SET_DEFLIST_UNIQUE) { + /* merge sections */ + } else if (*((const char *)csrc) == 0) { + /* unchanged */ + continue; + } else if (def->type == SET_ALIAS) { + /* ignore aliases */ + continue; + } else if (*((const char *)cdest) != 0) { + /* conflict */ + if (conflict_key_r != NULL) { + *conflict_key_r = def->key; + return -1; + } + continue; + } else { + *((char *)cdest) = 1; + } + + /* found a changed setting */ + src = CONST_PTR_OFFSET(src_link->set_struct, def->offset); + dest = PTR_OFFSET(dest_link->set_struct, def->offset); + + if (setting_copy(def->type, src, dest, pool, FALSE)) { + /* non-list */ + } else if (def->type == SET_DEFLIST) { + settings_copy_deflist(def, src_link, dest_link, pool); + } else { + i_assert(def->type == SET_DEFLIST_UNIQUE); + if (settings_copy_deflist_unique(def, src_link, + dest_link, pool, + conflict_key_r) < 0) + return -1; + } + } + return 0; +} + +int settings_parser_apply_changes(struct setting_parser_context *dest, + const struct setting_parser_context *src, + pool_t pool, const char **conflict_key_r) +{ + unsigned int i; + + i_assert(src->root_count == dest->root_count); + for (i = 0; i < dest->root_count; i++) { + i_assert(src->roots[i].info == dest->roots[i].info); + if (settings_apply(&dest->roots[i], &src->roots[i], pool, + conflict_key_r) < 0) + return -1; + } + return 0; +} + +const char *settings_section_escape(const char *name) +{ +#define CHAR_NEED_ESCAPE(c) \ + ((c) == '=' || (c) == SETTINGS_SEPARATOR || (c) == '\\' || (c) == ' ' || (c) == ',') + string_t *str; + unsigned int i; + + for (i = 0; name[i] != '\0'; i++) { + if (CHAR_NEED_ESCAPE(name[i])) + break; + } + if (name[i] == '\0') + return name; + + str = t_str_new(i + strlen(name+i) + 8); + str_append_data(str, name, i); + for (; name[i] != '\0'; i++) { + switch (name[i]) { + case '=': + str_append(str, "\\e"); + break; + case SETTINGS_SEPARATOR: + str_append(str, "\\s"); + break; + case '\\': + str_append(str, "\\\\"); + break; + case ' ': + str_append(str, "\\_"); + break; + case ',': + str_append(str, "\\+"); + break; + default: + str_append_c(str, name[i]); + break; + } + } + return str_c(str); +} + |