settings-parser.c revision 822ae9061773d9ac1246c35d3c47cc0f49cee3ab
2454dfa32c93c20a8522c6ed42fe057baaac9f9aStephan Bosch/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen#include "lib.h"
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen#include "array.h"
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen#include "hash.h"
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen#include "network.h"
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen#include "istream.h"
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen#include "str.h"
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen#include "strescape.h"
d186a2391d98a3efa3fe1078879ef2798e169c29Timo Sirainen#include "var-expand.h"
d186a2391d98a3efa3fe1078879ef2798e169c29Timo Sirainen#include "settings-parser.h"
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen
d186a2391d98a3efa3fe1078879ef2798e169c29Timo Sirainen#include <stdio.h>
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen#include <stdlib.h>
d186a2391d98a3efa3fe1078879ef2798e169c29Timo Sirainen#include <unistd.h>
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen#include <ctype.h>
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen#include <fcntl.h>
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen#include <sys/stat.h>
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen#include <sys/wait.h>
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen
d186a2391d98a3efa3fe1078879ef2798e169c29Timo Sirainen#define IS_WHITE(c) ((c) == ' ' || (c) == '\t')
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainenstruct setting_link {
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen struct setting_link *parent;
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen const struct setting_parser_info *info;
d186a2391d98a3efa3fe1078879ef2798e169c29Timo Sirainen
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen const char *full_key;
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen /* Points to array inside parent->set_struct.
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen SET_DEFLIST : array of set_structs
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen SET_STRLIST : array of const_strings */
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen ARRAY_TYPE(void_array) *array;
e0aff4c7e3336ec4b5edbcfc3a72e1e118603ee2Timo Sirainen /* Pointer to structure containing the values */
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen void *set_struct;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen /* Pointer to structure containing non-zero values for settings that
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen have been changed. */
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen void *change_struct;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen /* SET_DEFLIST: array of change_structs */
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen ARRAY_TYPE(void_array) *change_array;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen};
7ff6268cc35102675d73d44d680bed13d0709f7bTimo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainenstruct setting_parser_context {
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen pool_t set_pool, parser_pool;
29543188462c9348f365ec29115d777ffe4769d3Timo Sirainen enum settings_parser_flags flags;
7ff6268cc35102675d73d44d680bed13d0709f7bTimo Sirainen bool str_vars_are_expanded;
7ff6268cc35102675d73d44d680bed13d0709f7bTimo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen struct setting_link *roots;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen unsigned int root_count;
7ff6268cc35102675d73d44d680bed13d0709f7bTimo Sirainen struct hash_table *links;
7ff6268cc35102675d73d44d680bed13d0709f7bTimo Sirainen
0f97c2b6ec76e7f600e983cb952cf265a6189114Timo Sirainen unsigned int linenum;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen const char *error;
d186a2391d98a3efa3fe1078879ef2798e169c29Timo Sirainen const struct setting_parser_info *prev_info;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen};
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainenstatic const struct setting_parser_info strlist_info = {
34b724d1d7e50b1ab24267a3b6fc089b1147c1abAki Tuomi .module_name = NULL,
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen .defines = NULL,
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen .defaults = NULL,
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
7ff6268cc35102675d73d44d680bed13d0709f7bTimo Sirainen .type_offset = (size_t)-1,
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen .struct_size = 0,
1da5113b93f5dd0543a155040daa7ae3f3718b8bTimo Sirainen
1da5113b93f5dd0543a155040daa7ae3f3718b8bTimo Sirainen .parent_offset = (size_t)-1
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen};
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainenstatic void
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainensetting_parser_copy_defaults(struct setting_parser_context *ctx,
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen const struct setting_parser_info *info,
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen struct setting_link *link);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainenstatic int
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainensettings_apply(struct setting_link *dest_link,
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen const struct setting_link *src_link,
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen pool_t pool, const char **conflict_key_r);
0f97c2b6ec76e7f600e983cb952cf265a6189114Timo Sirainen
0f97c2b6ec76e7f600e983cb952cf265a6189114Timo Sirainenstruct setting_parser_context *
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainensettings_parser_init(pool_t set_pool, const struct setting_parser_info *root,
2848ed04730e9f2ed91829d41312ebc3132b5613Timo Sirainen enum settings_parser_flags flags)
2848ed04730e9f2ed91829d41312ebc3132b5613Timo Sirainen{
2848ed04730e9f2ed91829d41312ebc3132b5613Timo Sirainen return settings_parser_init_list(set_pool, &root, 1, flags);
2848ed04730e9f2ed91829d41312ebc3132b5613Timo Sirainen}
cf05592015b99607095f970bf914f5d069bf0666Timo Sirainen
cf05592015b99607095f970bf914f5d069bf0666Timo Sirainenstatic void
cf05592015b99607095f970bf914f5d069bf0666Timo Sirainencopy_unique_defaults(struct setting_parser_context *ctx,
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen const struct setting_define *def,
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen struct setting_link *link)
402e999a878e0cc41a0afb830fea0a93afc75f0dTimo Sirainen{
402e999a878e0cc41a0afb830fea0a93afc75f0dTimo Sirainen ARRAY_TYPE(void_array) *arr =
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen STRUCT_MEMBER_P(link->set_struct, def->offset);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen ARRAY_TYPE(void_array) *carr = NULL;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen struct setting_link *new_link;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen struct setting_parser_info info;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen const char *const *keyp, *key, *prefix;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen void *const *children;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen void *new_set, *new_changes = NULL;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen char *full_key;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen unsigned int i, count;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen if (!array_is_created(arr))
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen return;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
402e999a878e0cc41a0afb830fea0a93afc75f0dTimo Sirainen children = array_get(arr, &count);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen if (link->change_struct != NULL) {
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen carr = STRUCT_MEMBER_P(link->change_struct, def->offset);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen i_assert(!array_is_created(carr));
85c9cf2c39903ecb102d701e8b19a7cf364dce83Timo Sirainen p_array_init(carr, ctx->set_pool, count + 4);
85c9cf2c39903ecb102d701e8b19a7cf364dce83Timo Sirainen }
85c9cf2c39903ecb102d701e8b19a7cf364dce83Timo Sirainen p_array_init(arr, ctx->set_pool, count + 4);
85c9cf2c39903ecb102d701e8b19a7cf364dce83Timo Sirainen
85c9cf2c39903ecb102d701e8b19a7cf364dce83Timo Sirainen memset(&info, 0, sizeof(info));
85c9cf2c39903ecb102d701e8b19a7cf364dce83Timo Sirainen info = *def->list_info;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen for (i = 0; i < count; i++) {
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen new_set = p_malloc(ctx->set_pool, info.struct_size);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen array_append(arr, &new_set, 1);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen if (link->change_struct != NULL) {
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen new_changes = p_malloc(ctx->set_pool, info.struct_size);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen array_append(carr, &new_changes, 1);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen }
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen keyp = CONST_PTR_OFFSET(children[i], info.type_offset);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen key = settings_section_escape(*keyp);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen new_link = p_new(ctx->set_pool, struct setting_link, 1);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen prefix = link->full_key == NULL ?
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen t_strconcat(def->key, SETTINGS_SEPARATOR_S, NULL) :
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen t_strconcat(link->full_key, SETTINGS_SEPARATOR_S,
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen def->key, SETTINGS_SEPARATOR_S,NULL);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen full_key = p_strconcat(ctx->set_pool, prefix, key, NULL);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen new_link->full_key = full_key;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen new_link->parent = link;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen new_link->info = def->list_info;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen new_link->array = arr;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen new_link->change_array = carr;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen new_link->set_struct = new_set;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen new_link->change_struct = new_changes;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen hash_table_insert(ctx->links, full_key, new_link);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen info.defaults = children[i];
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen setting_parser_copy_defaults(ctx, &info, new_link);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen }
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen}
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainenstatic void
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainensetting_parser_copy_defaults(struct setting_parser_context *ctx,
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen const struct setting_parser_info *info,
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen struct setting_link *link)
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen{
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen const struct setting_define *def;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen const char *p, **strp;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen if (info->defaults == NULL)
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen return;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen memcpy(link->set_struct, info->defaults, info->struct_size);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen for (def = info->defines; def->key != NULL; def++) {
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen switch (def->type) {
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen case SET_ENUM: {
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen /* fix enums by dropping everything after the
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen first ':' */
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen strp = STRUCT_MEMBER_P(link->set_struct, def->offset);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen p = strchr(*strp, ':');
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen if (p != NULL)
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen *strp = p_strdup_until(ctx->set_pool, *strp, p);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen break;
b936882755cc7819463e503edca4cb5f1782ae4bAki Tuomi }
b936882755cc7819463e503edca4cb5f1782ae4bAki Tuomi case SET_STR_VARS: {
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen /* insert the unexpanded-character */
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen strp = STRUCT_MEMBER_P(link->set_struct, def->offset);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen if (*strp != NULL) {
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen *strp = p_strconcat(ctx->set_pool,
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen SETTING_STRVAR_UNEXPANDED,
402e999a878e0cc41a0afb830fea0a93afc75f0dTimo Sirainen *strp, NULL);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen }
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen break;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen }
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen case SET_DEFLIST_UNIQUE:
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen copy_unique_defaults(ctx, def, link);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen break;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen default:
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen break;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen }
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen }
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen}
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainenstruct setting_parser_context *
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainensettings_parser_init_list(pool_t set_pool,
34b724d1d7e50b1ab24267a3b6fc089b1147c1abAki Tuomi const struct setting_parser_info *const *roots,
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen unsigned int count, enum settings_parser_flags flags)
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen{
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen struct setting_parser_context *ctx;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen unsigned int i;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen pool_t parser_pool;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen i_assert(count > 0);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen parser_pool = pool_alloconly_create("settings parser", 16384);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen ctx = p_new(parser_pool, struct setting_parser_context, 1);
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen ctx->set_pool = set_pool;
1da5113b93f5dd0543a155040daa7ae3f3718b8bTimo Sirainen ctx->parser_pool = parser_pool;
1da5113b93f5dd0543a155040daa7ae3f3718b8bTimo Sirainen ctx->flags = flags;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen ctx->links = hash_table_create(default_pool, ctx->parser_pool, 0,
0f97c2b6ec76e7f600e983cb952cf265a6189114Timo Sirainen str_hash, (hash_cmp_callback_t *)strcmp);
7ff6268cc35102675d73d44d680bed13d0709f7bTimo Sirainen
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen ctx->root_count = count;
66e1cf5014bec1cf1a8339be6fccc9be5ad3c793Timo Sirainen 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(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 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_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;
}
int settings_get_time(const char *str, unsigned int *secs_r,
const char **error_r)
{
unsigned int num, multiply = 1;
char *p;
num = strtoull(str, &p, 10);
while (*p == ' ') p++;
switch (i_toupper(*p)) {
case 'S':
multiply = 1;
if (strncasecmp(p, "secs", strlen(p)) == 0)
p = "";
break;
case 'M':
multiply = 60;
if (strncasecmp(p, "mins", strlen(p)) == 0)
p = "";
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 > -1U / multiply) {
*error_r = t_strconcat("Time interval is too large: ",
str, NULL);
return -1;
}
*secs_r = num * multiply;
return 0;
}
int settings_get_size(const char *str, uoff_t *bytes_r,
const char **error_r)
{
unsigned long long num, multiply = 1;
char *p;
num = strtoull(str, &p, 10);
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 > -1ULL / 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 int 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;
hash_table_insert(ctx->links, key, link);
return 0;
}
static int
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);
memset(&new_link, 0, sizeof(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
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, *change_ptr;
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) {
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_append(link->change_array,
&link->change_struct, 1);
}
setting_parser_copy_defaults(ctx, link->info, link);
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;
}
}
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_TIME:
if (settings_get_time(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_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 = STRUCT_MEMBER_P(link->info->defaults, def->offset);
if (get_enum(ctx, value, (char **)ptr,
*(const char **)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(struct setting_parser_context *ctx, const char *key,
const struct setting_define **def_r,
struct setting_link **link_r)
{
const struct setting_define *def;
struct setting_link *link;
const char *end;
unsigned int i;
/* 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) {
*def_r = def;
*link_r = &ctx->roots[i];
return TRUE;
}
}
/* try to find from links */
end = strrchr(key, SETTINGS_SEPARATOR);
if (end == NULL)
return FALSE;
link = hash_table_lookup(ctx->links, t_strdup_until(key, end));
if (link == NULL)
return FALSE;
*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 int settings_parse_keyvalue(struct setting_parser_context *ctx,
const char *key, const char *value)
{
const struct setting_define *def;
struct setting_link *link;
if (settings_find_key(ctx, key, &def, &link)) {
if (link->info == &strlist_info) {
void *vkey, *vvalue;
vkey = p_strdup(ctx->set_pool,
strrchr(key, SETTINGS_SEPARATOR) + 1);
vvalue = p_strdup(ctx->set_pool, value);
array_append(link->array, &vkey, 1);
array_append(link->array, &vvalue, 1);
return 1;
}
if (settings_parse(ctx, link, def, key, value) < 0)
return -1;
return 1;
} else {
ctx->error = p_strconcat(ctx->parser_pool,
"Unknown setting: ", key, NULL);
return 0;
}
}
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);
}
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;
}
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 && strncmp(line, "ERROR ", 6) == 0) {
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;
while ((ret = i_stream_read(input)) > 0) {
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;
}
static int environ_cmp(char *const *s1, char *const *s2)
{
return -strcmp(*s1, *s2);
}
int settings_parse_environ(struct setting_parser_context *ctx)
{
extern char **environ;
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_append(&sorted_envs_arr, &environ[i], 1);
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");
(void)close(fd[0]);
(void)close(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;
(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;
}
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;
if (info->check_func != NULL) {
if (!info->check_func(set, pool, error_r))
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 TRUE;
}
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_expandeded(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 (link->info == &strlist_info) {
/* parent is strlist, no expansion needed */
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_expandeded(struct setting_parser_context *ctx,
pool_t pool, const char *const *keys)
{
for (; *keys != NULL; keys++)
settings_parse_set_key_expandeded(ctx, pool, *keys);
}
void settings_parse_var_skip(struct setting_parser_context *ctx)
{
unsigned int i;
for (i = 0; i < ctx->root_count; i++) {
settings_var_expand(ctx->roots[i].info,
ctx->roots[i].set_struct, NULL, NULL);
}
}
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_BOOL:
case SET_UINT:
case SET_TIME:
case SET_SIZE:
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);
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:
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++) {
settings_var_expand_info(def->list_info,
pool, children[i],
table, str);
}
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_BOOL:
case SET_UINT:
case SET_TIME:
case SET_SIZE:
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_t)-1)
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)
{
switch (type) {
case SET_BOOL: {
const bool *src_bool = src;
bool *dest_bool = dest;
*dest_bool = *src_bool;
break;
}
case SET_UINT:
case SET_TIME: {
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_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:
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, *dup;
unsigned int i, count;
if (!array_is_created(src_arr))
break;
strings = array_get(src_arr, &count);
if (!array_is_created(dest_arr))
p_array_init(dest_arr, pool, count);
for (i = 0; i < count; i++) {
dup = p_strdup(pool, strings[i]);
array_append(dest_arr, &dup, 1);
}
break;
}
case SET_ALIAS:
break;
}
return TRUE;
}
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;
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)) {
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(def->list_info,
children[i], pool);
array_append(dest_arr, &child_set, 1);
settings_set_parent(def->list_info, child_set,
dest_set);
}
}
}
return dest_set;
}
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_TIME:
case SET_SIZE:
case SET_STR_VARS:
case SET_STR:
case SET_ENUM:
case SET_STRLIST:
*((char *)dest) = *((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_append(dest_arr, &child_set, 1);
}
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_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)
{
if (parsers[0].name != NULL) T_BEGIN {
info_update_real(pool, parent, parsers);
} T_END;
}
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,
struct hash_table *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);
}
hash_table_insert(links, old_link, new_link);
return new_link;
}
struct setting_parser_context *
settings_parser_dup(struct setting_parser_context *old_ctx, pool_t new_pool)
{
struct setting_parser_context *new_ctx;
struct hash_iterate_context *iter;
struct setting_link *new_link;
struct hash_table *links;
void *key, *value;
unsigned int i;
pool_t parser_pool;
pool_ref(new_pool);
parser_pool = pool_alloconly_create("dup settings parser", 8192);
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;
links = hash_table_create(default_pool, new_ctx->parser_pool,
0, NULL, NULL);
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(old_ctx->roots[i].info,
old_ctx->roots[i].set_struct,
new_ctx->set_pool);
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]);
}
new_ctx->links =
hash_table_create(default_pool, new_ctx->parser_pool, 0,
str_hash, (hash_cmp_callback_t *)strcmp);
iter = hash_table_iterate_init(old_ctx->links);
while (hash_table_iterate(iter, &key, &value)) {
new_link = settings_link_get_new(new_ctx, links, value);
hash_table_insert(new_ctx->links,
p_strdup(new_ctx->parser_pool, 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_append(dest_arr, &set, 1);
}
}
}
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_append(dest_arr, &child_set, 1);
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_append(dest_arr, &child_set, 1);
}
}
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_t)-1);
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;
memset(&child_dest_link, 0, sizeof(child_dest_link));
memset(&child_src_link, 0, sizeof(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) {
/* 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_append(dest_arr, &child_set, 1);
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_append(dest_carr, &child_set, 1);
}
}
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)) {
/* 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) == '\\')
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_n(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;
default:
str_append_c(str, name[i]);
break;
}
}
return str_c(str);
}