Mercurial > dovecot > core-2.2
view src/lib-settings/settings-parser.c @ 9002:9d0037a997f4 HEAD
Initial commit for config rewrite.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Tue, 27 Jan 2009 18:21:53 -0500 |
parents | |
children | f5e35bc340bf |
line wrap: on
line source
/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "hash.h" #include "network.h" #include "istream.h" #include "str.h" #include "strescape.h" #include "var-expand.h" #include "settings-parser.h" #include <stdio.h> #include <unistd.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; ARRAY_TYPE(void_array) *array; void *set_struct; }; 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; struct hash_table *links; string_t *save_input_str; unsigned int linenum; const char *error; const struct setting_parser_info *prev_info; }; static const struct setting_parser_info strlist_info = { MEMBER(defines) NULL, MEMBER(defaults) NULL, MEMBER(parent) NULL, MEMBER(dynamic_parsers) NULL, MEMBER(parent_offset) (size_t)-1, MEMBER(type_offset) (size_t)-1, MEMBER(struct_size) 0 }; 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 setting_parser_copy_defaults(const struct setting_parser_info *info, pool_t pool, void *dest) { const struct setting_define *def; const char *p, **strp; if (info->defaults == NULL) return; memcpy(dest, 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(dest, def->offset); p = strchr(*strp, ':'); if (p != NULL) *strp = p_strdup_until(pool, *strp, p); break; } case SET_STR_VARS: { /* insert the unexpanded-character */ strp = STRUCT_MEMBER_P(dest, def->offset); if (*strp != NULL) { *strp = p_strconcat(pool, SETTING_STRVAR_UNEXPANDED, *strp, NULL); } 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("settings parser", 8192); ctx = p_new(parser_pool, struct setting_parser_context, 1); ctx->set_pool = set_pool; ctx->parser_pool = parser_pool; ctx->flags = flags; ctx->root_count = count; ctx->roots = p_new(ctx->set_pool, struct setting_link, count); for (i = 0; i < count; i++) { ctx->roots[i].info = roots[i]; ctx->roots[i].set_struct = p_malloc(ctx->set_pool, roots[i]->struct_size); setting_parser_copy_defaults(roots[i], ctx->set_pool, ctx->roots[i].set_struct); } ctx->links = hash_table_create(default_pool, ctx->parser_pool, 0, str_hash, (hash_cmp_callback_t *)strcmp); 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(struct setting_parser_context *ctx) { unsigned int i; void **sets; sets = t_new(void *, ctx->root_count); for (i = 0; i < ctx->root_count; i++) sets[i] = ctx->roots[i].set_struct; return sets; } 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 && list->type != SET_INTERNAL) 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_strconcat(ctx->parser_pool, "Invalid boolean: ", value, NULL); return -1; } return 0; } static int get_uint(struct setting_parser_context *ctx, const char *value, unsigned int *result_r) { int num; /* use %i so we can handle eg. 0600 as octal value with umasks */ if (!sscanf(value, "%i", &num) || num < 0) { ctx->error = p_strconcat(ctx->parser_pool, "Invalid number: ", value, NULL); return -1; } *result_r = num; 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 int get_deflist(struct setting_parser_context *ctx, struct setting_link *parent, const struct setting_parser_info *info, const char *key, const char *value, ARRAY_TYPE(void_array) *result) { struct setting_link *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); 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 (hash_table_lookup(ctx->links, full_key) != NULL) { ctx->error = p_strconcat(ctx->parser_pool, full_key, " already exists", NULL); return -1; } link = p_new(ctx->parser_pool, struct setting_link, 1); link->parent = parent; link->info = info; link->array = result; hash_table_insert(ctx->links, full_key, link); } 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, *ptr2; ctx->prev_info = link->info; if (link->set_struct == NULL) { link->set_struct = p_malloc(ctx->set_pool, link->info->struct_size); setting_parser_copy_defaults(link->info, ctx->set_pool, link->set_struct); array_append(link->array, &link->set_struct, 1); if (link->info->parent_offset != (size_t)-1 && link->parent != NULL) { ptr = STRUCT_MEMBER_P(link->set_struct, link->info->parent_offset); *((void **)ptr) = link->parent->set_struct; } } ptr = STRUCT_MEMBER_P(link->set_struct, def->offset); switch (def->type) { case SET_INTERNAL: i_unreached(); case SET_BOOL: return get_bool(ctx, value, (bool *)ptr); case SET_UINT: return get_uint(ctx, value, (unsigned int *)ptr); case SET_STR: *((char **)ptr) = p_strdup(ctx->set_pool, value); return 0; case SET_STR_VARS: *((char **)ptr) = p_strconcat(ctx->set_pool, ctx->str_vars_are_expanded ? SETTING_STRVAR_EXPANDED : SETTING_STRVAR_UNEXPANDED, value, NULL); return 0; case SET_ENUM: /* get the available values from default string */ i_assert(link->info->defaults != NULL); ptr2 = STRUCT_MEMBER_P(link->info->defaults, def->offset); return get_enum(ctx, value, (char **)ptr, *(const char **)ptr2); case SET_DEFLIST: ctx->prev_info = def->list_info; return get_deflist(ctx, link, def->list_info, key, value, (ARRAY_TYPE(void_array) *)ptr); case SET_STRLIST: { ctx->prev_info = &strlist_info; return get_deflist(ctx, link, &strlist_info, key, value, (ARRAY_TYPE(void_array) *)ptr); } } i_unreached(); return -1; } static int settings_parse_keyvalue(struct setting_parser_context *ctx, const char *key, const char *value) { const struct setting_define *def = NULL; unsigned int i; int ret = 1; /* try to find from roots */ for (i = 0; i < ctx->root_count; i++) { def = setting_define_find(ctx->roots[i].info, key); if (def != NULL) break; } if (def != NULL) { if (settings_parse(ctx, &ctx->roots[i], def, key, value) < 0) ret = -1; } else { /* try to find from links */ const char *end = strrchr(key, SETTINGS_SEPARATOR); struct setting_link *link; link = end == NULL ? NULL : hash_table_lookup(ctx->links, t_strdup_until(key, end)); if (link == NULL) def = NULL; else if (link->info == &strlist_info) { void *vkey, *vvalue; vkey = p_strdup(ctx->set_pool, end + 1); vvalue = p_strdup(ctx->set_pool, value); array_append(link->array, &vkey, 1); array_append(link->array, &vvalue, 1); return 1; } else { def = setting_define_find(link->info, end + 1); } if (def != NULL) { if (settings_parse(ctx, link, def, key, value) < 0) ret = -1; } else { ctx->error = p_strconcat(ctx->parser_pool, "Unknown setting: ", key, NULL); ret = 0; } } return ret; } int settings_parse_line(struct setting_parser_context *ctx, const char *line) { const char *key, *value; int ret; ctx->error = NULL; ctx->prev_info = NULL; 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; } int settings_parse_stream(struct setting_parser_context *ctx, struct istream *input) { const char *line; string_t *full_line; size_t len; int ret = 1; full_line = str_new(default_pool, 512); while ((line = i_stream_next_line(input)) != NULL) { if (*line == '\0') { /* empty line finishes it */ ret = 0; break; } ctx->linenum++; while (IS_WHITE(*line == ' ')) line++; if (*line == '\0' || *line == '#') continue; len = strlen(line); while (len > 0 && IS_WHITE(line[len-1])) len--; if (line[len] == '\\') { /* line continues */ str_append_n(full_line, line, len - 1); } else { /* full line */ if (str_len(full_line) > 0) { str_append_n(full_line, line, len); line = str_c(full_line); } ret = settings_parse_line(ctx, line); if (ret == 0 && (ctx->flags & SETTINGS_PARSER_FLAG_IGNORE_UNKNOWN_KEYS) == 0) ret = -1; if (ret < 0) { ctx->error = p_strdup_printf(ctx->parser_pool, "Line %u: %s", ctx->linenum, ctx->error); ret = -1; break; } str_truncate(full_line, 0); } } str_free(&full_line); return ret; } int settings_parse_stream_read(struct setting_parser_context *ctx, struct istream *input) { const unsigned char *data; size_t size; int ret; while ((ret = i_stream_read(input)) > 0) { if (ctx->save_input_str != NULL) { data = i_stream_get_data(input, &size); str_append_n(ctx->save_input_str, data, size); } if ((ret = settings_parse_stream(ctx, input)) < 0) return -1; if (ret == 0) { /* empty line read */ return 0; } } switch (ret) { case -1: if (input->stream_errno != 0) { ctx->error = p_strdup_printf(ctx->parser_pool, "read() failed: %m"); } else { ctx->error = "input is missing end-of-settings line"; } 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(fd, max_line_length, TRUE); ret = settings_parse_stream_read(ctx, input); i_stream_unref(&input); return ret; } int settings_parse_environ(struct setting_parser_context *ctx) { extern char **environ; const char *key, *value; unsigned int i; int ret = 0; for (i = 0; environ[i] != NULL && ret == 0; i++) { value = strchr(environ[i], '='); if (value != NULL) T_BEGIN { key = t_strdup_until(environ[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; } 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"); (void)close(fd[0]); (void)close(fd[1]); return -1; } if (pid == 0) { /* child */ static const char *argv[] = { NULL, "-c", NULL, "-s", NULL, NULL }; argv[0] = bin_path; argv[2] = config_path; argv[4] = service; (void)close(fd[0]); if (dup2(fd[1], STDOUT_FILENO) < 0) i_fatal("dup2() failed: %m"); execv(argv[0], (void *)argv); i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m", bin_path); return -1; } (void)close(fd[1]); input = i_stream_create_fd(fd[0], (size_t)-1, TRUE); 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; } void settings_parse_set_expanded(struct setting_parser_context *ctx, bool is_expanded) { ctx->str_vars_are_expanded = is_expanded; } static void settings_var_expand_info(const struct setting_parser_info *info, pool_t pool, void *set, const struct var_expand_table *table, string_t *str) { const struct setting_define *def; void *value, *const *children; unsigned int i, count; for (def = info->defines; def->key != NULL; def++) { value = PTR_OFFSET(set, def->offset); switch (def->type) { case SET_STR_VARS: { const char **val = value; if (*val == NULL) break; if (**val == SETTING_STRVAR_UNEXPANDED[0]) { str_truncate(str, 0); var_expand(str, *val + 1, table); *val = p_strdup(pool, str_c(str)); } else { i_assert(**val == SETTING_STRVAR_EXPANDED[0]); *val += 1; } break; } case SET_DEFLIST: { const ARRAY_TYPE(void_array) *val = value; if (!array_is_created(val)) break; children = array_get(val, &count); for (i = 0; i < count; i++) { settings_var_expand_info(def->list_info, pool, children[i], table, str); } break; } default: break; } } } void settings_var_expand(const struct setting_parser_info *info, void *set, pool_t pool, const struct var_expand_table *table) { string_t *str; T_BEGIN { str = t_str_new(256); settings_var_expand_info(info, pool, set, table, str); } T_END; } 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_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: { 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; } default: break; } } return FALSE; } void *settings_dup(const struct setting_parser_info *info, const void *set, pool_t pool) { const struct setting_define *def; const void *src; void *dest_set, *dest, *const *children; unsigned int i, count; dest_set = p_malloc(pool, info->struct_size); memcpy(dest_set, set, 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); switch (def->type) { case SET_INTERNAL: case SET_BOOL: case SET_UINT: break; case SET_STR_VARS: case SET_STR: case SET_ENUM: { const char *const *src_str = src; const char **dest_str = dest; *dest_str = p_strdup(pool, *src_str); break; } case SET_DEFLIST: { 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_dup(def->list_info, children[i], pool); array_append(dest_arr, &child_set, 1); } break; } case SET_STRLIST: { const ARRAY_TYPE(const_string) *src_arr = src; ARRAY_TYPE(const_string) *dest_arr = dest; const char *const *strings, *dup; if (!array_is_created(src_arr)) break; strings = array_get(src_arr, &count); p_array_init(dest_arr, pool, count); for (i = 0; i < count; i += 2) dup = p_strdup(pool, strings[i]); break; } } } return dest_set; } void settings_parse_save_input(struct setting_parser_context *ctx, string_t *dest) { ctx->save_input_str = dest; } static void info_update_real(pool_t pool, struct setting_parser_info *parent, const struct dynamic_settings_parser *parsers) { /* @UNSAFE */ ARRAY_DEFINE(defines, struct setting_define); 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_append(&defines, &parent->defines[j], 1); new_struct_size = 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_append(&defines, &new_define, 1); } 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_idx(&defines, 0), 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 = 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_append(&dynamic_parsers, &parent->dynamic_parsers[i], 1); } } offset = 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_append(&dynamic_parsers, &new_parser, 1); 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_idx(&dynamic_parsers, 0), 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) { T_BEGIN { info_update_real(pool, parent, parsers); } T_END; } const void *settings_find_dynamic(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; }