diff options
author | Nick Mathewson <nickm@torproject.org> | 2019-08-28 09:46:59 -0400 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2019-08-28 09:46:59 -0400 |
commit | b1d7ddfb02cdbd23fce0c30d6ab4897ede18e7fe (patch) | |
tree | 123e890d0e61f38d5a4ff091d7244c1a233a0ccc /src/app/config/confparse.c | |
parent | 35e978da61efa04af9a5ab2399dff863bc6fb20a (diff) | |
parent | a3e99c5f1e2eb9dd9ead51bcbed61dc2b99cf256 (diff) | |
download | tor-b1d7ddfb02cdbd23fce0c30d6ab4897ede18e7fe.tar.gz tor-b1d7ddfb02cdbd23fce0c30d6ab4897ede18e7fe.zip |
Merge branch 'ticket31240v2' into ticket31240v2_merged_2
Diffstat (limited to 'src/app/config/confparse.c')
-rw-r--r-- | src/app/config/confparse.c | 746 |
1 files changed, 589 insertions, 157 deletions
diff --git a/src/app/config/confparse.c b/src/app/config/confparse.c index 6e2624466a..f20a361ba3 100644 --- a/src/app/config/confparse.c +++ b/src/app/config/confparse.c @@ -37,16 +37,349 @@ #include "lib/string/printf.h" #include "lib/string/util_string.h" -static void config_reset(const config_format_t *fmt, void *options, - const config_var_t *var, int use_defaults); +#include "ext/siphash.h" + +/** + * A managed_var_t is an internal wrapper around a config_var_t in + * a config_format_t structure. It is used by config_mgr_t to + * keep track of which option goes with which structure. */ +typedef struct managed_var_t { + /** + * A pointer to the config_var_t for this option. + */ + const config_var_t *cvar; + /** + * The index of the object in which this option is stored. It is + * IDX_TOPLEVEL to indicate that the object is the top-level object. + **/ + int object_idx; +} managed_var_t; + +static void config_reset(const config_mgr_t *fmt, void *options, + const managed_var_t *var, int use_defaults); +static void config_mgr_register_fmt(config_mgr_t *mgr, + const config_format_t *fmt, + int object_idx); + +/** Release all storage held in a managed_var_t. */ +static void +managed_var_free_(managed_var_t *mv) +{ + if (!mv) + return; + tor_free(mv); +} +#define managed_var_free(mv) \ + FREE_AND_NULL(managed_var_t, managed_var_free_, (mv)) + +struct config_suite_t { + /** A list of configuration objects managed by a given configuration + * manager. They are stored in the same order as the config_format_t + * objects in the manager's list of subformats. */ + smartlist_t *configs; +}; + +/** + * Allocate a new empty config_suite_t. + **/ +static config_suite_t * +config_suite_new(void) +{ + config_suite_t *suite = tor_malloc_zero(sizeof(config_suite_t)); + suite->configs = smartlist_new(); + return suite; +} + +/** Release all storage held by a config_suite_t. (Does not free + * any configuration objects it holds; the caller must do that first.) */ +static void +config_suite_free_(config_suite_t *suite) +{ + if (!suite) + return; + smartlist_free(suite->configs); + tor_free(suite); +} + +#define config_suite_free(suite) \ + FREE_AND_NULL(config_suite_t, config_suite_free_, (suite)) + +struct config_mgr_t { + /** The 'top-level' configuration format. This one is used for legacy + * options that have not yet been assigned to different sub-modules. + * + * (NOTE: for now, this is the only config_format_t that a config_mgr_t + * contains. A subsequent commit will add more. XXXX) + */ + const config_format_t *toplevel; + /** + * List of second-level configuration format objects that this manager + * also knows about. + */ + smartlist_t *subconfigs; + /** A smartlist of managed_var_t objects for all configuration formats. */ + smartlist_t *all_vars; + /** A smartlist of config_abbrev_t objects for all configuration + * formats. These objects are used to track synonyms and abbreviations for + * different configuration options. */ + smartlist_t *all_abbrevs; + /** A smartlist of config_deprecation_t for all configuration formats. */ + smartlist_t *all_deprecations; + /** True if this manager has been frozen and cannot have any more formats + * added to it. A manager must be frozen before it can be used to construct + * or manipulate objects. */ + bool frozen; + /** A replacement for the magic number of the toplevel object. We override + * that number to make it unique for this particular config_mgr_t, so that + * an object constructed with one mgr can't be used with another, even if + * those managers' contents are equal. + */ + struct_magic_decl_t toplevel_magic; +}; + +#define IDX_TOPLEVEL (-1) + +/** Create a new config_mgr_t to manage a set of configuration objects to be + * wrapped under <b>toplevel_fmt</b>. */ +config_mgr_t * +config_mgr_new(const config_format_t *toplevel_fmt) +{ + config_mgr_t *mgr = tor_malloc_zero(sizeof(config_mgr_t)); + mgr->subconfigs = smartlist_new(); + mgr->all_vars = smartlist_new(); + mgr->all_abbrevs = smartlist_new(); + mgr->all_deprecations = smartlist_new(); + + config_mgr_register_fmt(mgr, toplevel_fmt, IDX_TOPLEVEL); + mgr->toplevel = toplevel_fmt; + + return mgr; +} + +/** Add a config_format_t to a manager, with a specified (unique) index. */ +static void +config_mgr_register_fmt(config_mgr_t *mgr, + const config_format_t *fmt, + int object_idx) +{ + int i; + + tor_assertf(!mgr->frozen, + "Tried to add a format to a configuration manager after " + "it had been frozen."); + + if (object_idx != IDX_TOPLEVEL) { + tor_assertf(fmt->config_suite_offset < 0, + "Tried to register a toplevel format in a non-toplevel position"); + } + tor_assertf(fmt != mgr->toplevel && + ! smartlist_contains(mgr->subconfigs, fmt), + "Tried to register an already-registered format."); + + /* register variables */ + for (i = 0; fmt->vars[i].member.name; ++i) { + managed_var_t *mv = tor_malloc_zero(sizeof(managed_var_t)); + mv->cvar = &fmt->vars[i]; + mv->object_idx = object_idx; + smartlist_add(mgr->all_vars, mv); + } + + /* register abbrevs */ + if (fmt->abbrevs) { + for (i = 0; fmt->abbrevs[i].abbreviated; ++i) { + smartlist_add(mgr->all_abbrevs, (void*)&fmt->abbrevs[i]); + } + } + + /* register deprecations. */ + if (fmt->deprecations) { + const config_deprecation_t *d; + for (d = fmt->deprecations; d->name; ++d) { + smartlist_add(mgr->all_deprecations, (void*)d); + } + } +} + +/** + * Add a new format to this configuration object. Asserts on failure. + * + **/ +int +config_mgr_add_format(config_mgr_t *mgr, + const config_format_t *fmt) +{ + tor_assert(mgr); + int idx = smartlist_len(mgr->subconfigs); + config_mgr_register_fmt(mgr, fmt, idx); + smartlist_add(mgr->subconfigs, (void *)fmt); + return idx; +} + +/** Return a pointer to the config_suite_t * pointer inside a + * configuration object; returns NULL if there is no such member. */ +static inline config_suite_t ** +config_mgr_get_suite_ptr(const config_mgr_t *mgr, void *toplevel) +{ + if (mgr->toplevel->config_suite_offset < 0) + return NULL; + return STRUCT_VAR_P(toplevel, mgr->toplevel->config_suite_offset); +} + +/** + * Return a pointer to the configuration object within <b>toplevel</b> whose + * index is <b>idx</b>. + * + * NOTE: XXXX Eventually, there will be multiple objects supported within the + * toplevel object. For example, the or_options_t will contain pointers + * to configuration objects for other modules. This function gets + * the sub-object for a particular module. + */ +STATIC void * +config_mgr_get_obj_mutable(const config_mgr_t *mgr, void *toplevel, int idx) +{ + tor_assert(mgr); + tor_assert(toplevel); + if (idx == IDX_TOPLEVEL) + return toplevel; + + tor_assertf(idx >= 0 && idx < smartlist_len(mgr->subconfigs), + "Index %d is out of range.", idx); + config_suite_t **suite = config_mgr_get_suite_ptr(mgr, toplevel); + tor_assert(suite); + tor_assert(smartlist_len(mgr->subconfigs) == + smartlist_len((*suite)->configs)); + + return smartlist_get((*suite)->configs, idx); +} + +/** As config_mgr_get_obj_mutable(), but return a const pointer. */ +STATIC const void * +config_mgr_get_obj(const config_mgr_t *mgr, const void *toplevel, int idx) +{ + return config_mgr_get_obj_mutable(mgr, (void*)toplevel, idx); +} + +/** Sorting helper for smartlist of managed_var_t */ +static int +managed_var_cmp(const void **a, const void **b) +{ + const managed_var_t *mv1 = *(const managed_var_t**)a; + const managed_var_t *mv2 = *(const managed_var_t**)b; + + return strcasecmp(mv1->cvar->member.name, mv2->cvar->member.name); +} + +/** + * Mark a configuration manager as "frozen", so that no more formats can be + * added, and so that it can be used for manipulating configuration objects. + **/ +void +config_mgr_freeze(config_mgr_t *mgr) +{ + static uint64_t mgr_count = 0; + + smartlist_sort(mgr->all_vars, managed_var_cmp); + memcpy(&mgr->toplevel_magic, &mgr->toplevel->magic, + sizeof(struct_magic_decl_t)); + uint64_t magic_input[3] = { mgr->toplevel_magic.magic_val, + (uint64_t) (uintptr_t) mgr, + ++mgr_count }; + mgr->toplevel_magic.magic_val = + (uint32_t)siphash24g(magic_input, sizeof(magic_input)); + mgr->frozen = true; +} + +/** Release all storage held in <b>mgr</b> */ +void +config_mgr_free_(config_mgr_t *mgr) +{ + if (!mgr) + return; + SMARTLIST_FOREACH(mgr->all_vars, managed_var_t *, mv, managed_var_free(mv)); + smartlist_free(mgr->all_vars); + smartlist_free(mgr->all_abbrevs); + smartlist_free(mgr->all_deprecations); + smartlist_free(mgr->subconfigs); + memset(mgr, 0, sizeof(*mgr)); + tor_free(mgr); +} + +/** Return a new smartlist_t containing a config_var_t for every variable that + * <b>mgr</b> knows about. The elements of this smartlist do not need + * to be freed; they have the same lifespan as <b>mgr</b>. */ +smartlist_t * +config_mgr_list_vars(const config_mgr_t *mgr) +{ + smartlist_t *result = smartlist_new(); + tor_assert(mgr); + SMARTLIST_FOREACH(mgr->all_vars, managed_var_t *, mv, + smartlist_add(result, (void*) mv->cvar)); + return result; +} + +/** Return a new smartlist_t containing the names of all deprecated variables. + * The elements of this smartlist do not need to be freed; they have the same + * lifespan as <b>mgr</b>. + */ +smartlist_t * +config_mgr_list_deprecated_vars(const config_mgr_t *mgr) +{ + smartlist_t *result = smartlist_new(); + tor_assert(mgr); + SMARTLIST_FOREACH(mgr->all_deprecations, config_deprecation_t *, d, + smartlist_add(result, (char*)d->name)); + return result; +} + +/** Assert that the magic fields in <b>options</b> and its subsidiary + * objects are all okay. */ +static void +config_mgr_assert_magic_ok(const config_mgr_t *mgr, + const void *options) +{ + tor_assert(mgr); + tor_assert(options); + tor_assert(mgr->frozen); + struct_check_magic(options, &mgr->toplevel_magic); + + config_suite_t **suitep = config_mgr_get_suite_ptr(mgr, (void*)options); + if (suitep == NULL) { + tor_assert(smartlist_len(mgr->subconfigs) == 0); + return; + } + + tor_assert(smartlist_len((*suitep)->configs) == + smartlist_len(mgr->subconfigs)); + SMARTLIST_FOREACH_BEGIN(mgr->subconfigs, const config_format_t *, fmt) { + void *obj = smartlist_get((*suitep)->configs, fmt_sl_idx); + tor_assert(obj); + struct_check_magic(obj, &fmt->magic); + } SMARTLIST_FOREACH_END(fmt); +} + +/** Macro: assert that <b>cfg</b> has the right magic field for + * <b>mgr</b>. */ +#define CONFIG_CHECK(mgr, cfg) STMT_BEGIN \ + config_mgr_assert_magic_ok((mgr), (cfg)); \ + STMT_END /** Allocate an empty configuration object of a given format type. */ void * -config_new(const config_format_t *fmt) +config_new(const config_mgr_t *mgr) { - void *opts = tor_malloc_zero(fmt->size); - struct_set_magic(opts, &fmt->magic); - CONFIG_CHECK(fmt, opts); + tor_assert(mgr->frozen); + void *opts = tor_malloc_zero(mgr->toplevel->size); + struct_set_magic(opts, &mgr->toplevel_magic); + config_suite_t **suitep = config_mgr_get_suite_ptr(mgr, opts); + if (suitep) { + *suitep = config_suite_new(); + SMARTLIST_FOREACH_BEGIN(mgr->subconfigs, const config_format_t *, fmt) { + void *obj = tor_malloc_zero(fmt->size); + struct_set_magic(obj, &fmt->magic); + smartlist_add((*suitep)->configs, obj); + } SMARTLIST_FOREACH_END(fmt); + } + CONFIG_CHECK(mgr, opts); return opts; } @@ -60,29 +393,26 @@ config_new(const config_format_t *fmt) * apply abbreviations that work for the config file and the command line. * If <b>warn_obsolete</b> is set, warn about deprecated names. */ const char * -config_expand_abbrev(const config_format_t *fmt, const char *option, +config_expand_abbrev(const config_mgr_t *mgr, const char *option, int command_line, int warn_obsolete) { - int i; - if (! fmt->abbrevs) - return option; - for (i=0; fmt->abbrevs[i].abbreviated; ++i) { + SMARTLIST_FOREACH_BEGIN(mgr->all_abbrevs, const config_abbrev_t *, abbrev) { /* Abbreviations are case insensitive. */ - if (!strcasecmp(option,fmt->abbrevs[i].abbreviated) && - (command_line || !fmt->abbrevs[i].commandline_only)) { - if (warn_obsolete && fmt->abbrevs[i].warn) { + if (!strcasecmp(option, abbrev->abbreviated) && + (command_line || !abbrev->commandline_only)) { + if (warn_obsolete && abbrev->warn) { log_warn(LD_CONFIG, "The configuration option '%s' is deprecated; " "use '%s' instead.", - fmt->abbrevs[i].abbreviated, - fmt->abbrevs[i].full); + abbrev->abbreviated, + abbrev->full); } /* Keep going through the list in case we want to rewrite it more. * (We could imagine recursing here, but I don't want to get the * user into an infinite loop if we craft our list wrong.) */ - option = fmt->abbrevs[i].full; + option = abbrev->full; } - } + } SMARTLIST_FOREACH_END(abbrev); return option; } @@ -90,61 +420,92 @@ config_expand_abbrev(const config_format_t *fmt, const char *option, * explaining why it is deprecated (which may be an empty string). Return NULL * if it is not deprecated. The <b>key</b> field must be fully expanded. */ const char * -config_find_deprecation(const config_format_t *fmt, const char *key) +config_find_deprecation(const config_mgr_t *mgr, const char *key) { - if (BUG(fmt == NULL) || BUG(key == NULL)) + if (BUG(mgr == NULL) || BUG(key == NULL)) return NULL; // LCOV_EXCL_LINE - if (fmt->deprecations == NULL) - return NULL; - const config_deprecation_t *d; - for (d = fmt->deprecations; d->name; ++d) { + SMARTLIST_FOREACH_BEGIN(mgr->all_deprecations, const config_deprecation_t *, + d) { if (!strcasecmp(d->name, key)) { return d->why_deprecated ? d->why_deprecated : ""; } - } + } SMARTLIST_FOREACH_END(d); return NULL; } -/** If <b>key</b> is a configuration option, return the corresponding const - * config_var_t. Otherwise, if <b>key</b> is a non-standard abbreviation, - * warn, and return the corresponding const config_var_t. Otherwise return - * NULL. +/** + * Find the managed_var_t object for a variable whose name is <b>name</b> + * according to <b>mgr</b>. Return that object, or NULL if none exists. + * + * If <b>allow_truncated</b> is true, then accept any variable whose + * name begins with <b>name</b>. + * + * If <b>idx_out</b> is not NULL, set *<b>idx_out</b> to the position of + * that variable within mgr->all_vars, or to -1 if the variable is + * not found. */ -const config_var_t * -config_find_option(const config_format_t *fmt, const char *key) +static const managed_var_t * +config_mgr_find_var(const config_mgr_t *mgr, + const char *key, + bool allow_truncated, int *idx_out) { - int i; - size_t keylen = strlen(key); + const size_t keylen = strlen(key); + if (idx_out) + *idx_out = -1; + if (!keylen) return NULL; /* if they say "--" on the command line, it's not an option */ + /* First, check for an exact (case-insensitive) match */ - for (i=0; fmt->vars[i].member.name; ++i) { - if (!strcasecmp(key, fmt->vars[i].member.name)) { - return &fmt->vars[i]; + SMARTLIST_FOREACH_BEGIN(mgr->all_vars, const managed_var_t *, mv) { + if (!strcasecmp(mv->cvar->member.name, key)) { + if (idx_out) + *idx_out = mv_sl_idx; + return mv; } - } + } SMARTLIST_FOREACH_END(mv); + + if (!allow_truncated) + return NULL; + /* If none, check for an abbreviated match */ - for (i=0; fmt->vars[i].member.name; ++i) { - if (!strncasecmp(key, fmt->vars[i].member.name, keylen)) { + SMARTLIST_FOREACH_BEGIN(mgr->all_vars, const managed_var_t *, mv) { + if (!strncasecmp(key, mv->cvar->member.name, keylen)) { log_warn(LD_CONFIG, "The abbreviation '%s' is deprecated. " "Please use '%s' instead", - key, fmt->vars[i].member.name); - return &fmt->vars[i]; + key, mv->cvar->member.name); + if (idx_out) + *idx_out = mv_sl_idx; + return mv; } - } + } SMARTLIST_FOREACH_END(mv); + /* Okay, unrecognized option */ return NULL; } +/** + * If <b>key</b> is a name or an abbreviation configuration option, return + * the corresponding canonical name for it. Warn if the abbreviation is + * non-standard. Return NULL if the option does not exist. + */ +const char * +config_find_option_name(const config_mgr_t *mgr, const char *key) +{ + key = config_expand_abbrev(mgr, key, 0, 0); + const managed_var_t *mv = config_mgr_find_var(mgr, key, true, NULL); + if (mv) + return mv->cvar->member.name; + else + return NULL; +} + /** Return the number of option entries in <b>fmt</b>. */ static int -config_count_options(const config_format_t *fmt) +config_count_options(const config_mgr_t *mgr) { - int i; - for (i=0; fmt->vars[i].member.name; ++i) - ; - return i; + return smartlist_len(mgr->all_vars); } bool @@ -185,33 +546,33 @@ config_var_is_dumpable(const config_var_t *var) * Called from config_assign_line() and option_reset(). */ static int -config_assign_value(const config_format_t *fmt, void *options, +config_assign_value(const config_mgr_t *mgr, void *options, config_line_t *c, char **msg) { - const config_var_t *var; + const managed_var_t *var; - CONFIG_CHECK(fmt, options); + CONFIG_CHECK(mgr, options); - var = config_find_option(fmt, c->key); + var = config_mgr_find_var(mgr, c->key, true, NULL); tor_assert(var); - tor_assert(!strcmp(c->key, var->member.name)); + tor_assert(!strcmp(c->key, var->cvar->member.name)); + void *object = config_mgr_get_obj_mutable(mgr, options, var->object_idx); - return struct_var_kvassign(options, c, msg, &var->member); + return struct_var_kvassign(object, c, msg, &var->cvar->member); } /** Mark every linelist in <b>options</b> "fragile", so that fresh assignments * to it will replace old ones. */ static void -config_mark_lists_fragile(const config_format_t *fmt, void *options) +config_mark_lists_fragile(const config_mgr_t *mgr, void *options) { - int i; - tor_assert(fmt); + tor_assert(mgr); tor_assert(options); - for (i = 0; fmt->vars[i].member.name; ++i) { - const config_var_t *var = &fmt->vars[i]; - struct_var_mark_fragile(options, &var->member); - } + SMARTLIST_FOREACH_BEGIN(mgr->all_vars, const managed_var_t *, mv) { + void *object = config_mgr_get_obj_mutable(mgr, options, mv->object_idx); + struct_var_mark_fragile(object, &mv->cvar->member); + } SMARTLIST_FOREACH_END(mv); } void @@ -234,19 +595,21 @@ warn_deprecated_option(const char *what, const char *why) * Called from config_assign(). */ static int -config_assign_line(const config_format_t *fmt, void *options, +config_assign_line(const config_mgr_t *mgr, void *options, config_line_t *c, unsigned flags, bitarray_t *options_seen, char **msg) { const unsigned use_defaults = flags & CAL_USE_DEFAULTS; const unsigned clear_first = flags & CAL_CLEAR_FIRST; const unsigned warn_deprecations = flags & CAL_WARN_DEPRECATIONS; - const config_var_t *var; + const managed_var_t *mvar; - CONFIG_CHECK(fmt, options); + CONFIG_CHECK(mgr, options); - var = config_find_option(fmt, c->key); - if (!var) { + int var_index = -1; + mvar = config_mgr_find_var(mgr, c->key, true, &var_index); + if (!mvar) { + const config_format_t *fmt = mgr->toplevel; if (fmt->extra) { void *lvalue = STRUCT_VAR_P(options, fmt->extra->offset); log_info(LD_CONFIG, @@ -260,49 +623,52 @@ config_assign_line(const config_format_t *fmt, void *options, } } + const config_var_t *cvar = mvar->cvar; + tor_assert(cvar); + /* Put keyword into canonical case. */ - if (strcmp(var->member.name, c->key)) { + if (strcmp(cvar->member.name, c->key)) { tor_free(c->key); - c->key = tor_strdup(var->member.name); + c->key = tor_strdup(cvar->member.name); } const char *deprecation_msg; if (warn_deprecations && - (deprecation_msg = config_find_deprecation(fmt, var->member.name))) { - warn_deprecated_option(var->member.name, deprecation_msg); + (deprecation_msg = config_find_deprecation(mgr, cvar->member.name))) { + warn_deprecated_option(cvar->member.name, deprecation_msg); } if (!strlen(c->value)) { /* reset or clear it, then return */ if (!clear_first) { - if (config_var_is_cumulative(var) && c->command != CONFIG_LINE_CLEAR) { + if (config_var_is_cumulative(cvar) && c->command != CONFIG_LINE_CLEAR) { /* We got an empty linelist from the torrc or command line. As a special case, call this an error. Warn and ignore. */ log_warn(LD_CONFIG, "Linelist option '%s' has no value. Skipping.", c->key); } else { /* not already cleared */ - config_reset(fmt, options, var, use_defaults); + config_reset(mgr, options, mvar, use_defaults); } } return 0; } else if (c->command == CONFIG_LINE_CLEAR && !clear_first) { // XXXX This is unreachable, since a CLEAR line always has an // XXXX empty value. - config_reset(fmt, options, var, use_defaults); // LCOV_EXCL_LINE + config_reset(mgr, options, mvar, use_defaults); // LCOV_EXCL_LINE } - if (options_seen && ! config_var_is_cumulative(var)) { + if (options_seen && ! config_var_is_cumulative(cvar)) { /* We're tracking which options we've seen, and this option is not * supposed to occur more than once. */ - int var_index = (int)(var - fmt->vars); + tor_assert(var_index >= 0); if (bitarray_is_set(options_seen, var_index)) { log_warn(LD_CONFIG, "Option '%s' used more than once; all but the last " - "value will be ignored.", var->member.name); + "value will be ignored.", cvar->member.name); } bitarray_set(options_seen, var_index); } - if (config_assign_value(fmt, options, c, msg) < 0) + if (config_assign_value(mgr, options, c, msg) < 0) return -2; return 0; } @@ -310,18 +676,18 @@ config_assign_line(const config_format_t *fmt, void *options, /** Restore the option named <b>key</b> in options to its default value. * Called from config_assign(). */ STATIC void -config_reset_line(const config_format_t *fmt, void *options, +config_reset_line(const config_mgr_t *mgr, void *options, const char *key, int use_defaults) { - const config_var_t *var; + const managed_var_t *var; - CONFIG_CHECK(fmt, options); + CONFIG_CHECK(mgr, options); - var = config_find_option(fmt, key); + var = config_mgr_find_var(mgr, key, true, NULL); if (!var) return; /* give error on next pass. */ - config_reset(fmt, options, var, use_defaults); + config_reset(mgr, options, var, use_defaults); } /** Return true iff value needs to be quoted and escaped to be used in @@ -355,22 +721,24 @@ config_value_needs_escape(const char *value) * value needs to be quoted before it's put in a config file, quote and * escape that value. Return NULL if no such key exists. */ config_line_t * -config_get_assigned_option(const config_format_t *fmt, const void *options, +config_get_assigned_option(const config_mgr_t *mgr, const void *options, const char *key, int escape_val) { - const config_var_t *var; + const managed_var_t *var; config_line_t *result; + tor_assert(options && key); - CONFIG_CHECK(fmt, options); + CONFIG_CHECK(mgr, options); - var = config_find_option(fmt, key); + var = config_mgr_find_var(mgr, key, true, NULL); if (!var) { log_warn(LD_CONFIG, "Unknown option '%s'. Failing.", key); return NULL; } + const void *object = config_mgr_get_obj(mgr, options, var->object_idx); - result = struct_var_kvencode(options, &var->member); + result = struct_var_kvencode(object, &var->cvar->member); if (escape_val) { config_line_t *line; @@ -442,20 +810,20 @@ options_trial_assign() calls config_assign(1, 1) returns. */ int -config_assign(const config_format_t *fmt, void *options, config_line_t *list, +config_assign(const config_mgr_t *mgr, void *options, config_line_t *list, unsigned config_assign_flags, char **msg) { config_line_t *p; bitarray_t *options_seen; - const int n_options = config_count_options(fmt); + const int n_options = config_count_options(mgr); const unsigned clear_first = config_assign_flags & CAL_CLEAR_FIRST; const unsigned use_defaults = config_assign_flags & CAL_USE_DEFAULTS; - CONFIG_CHECK(fmt, options); + CONFIG_CHECK(mgr, options); /* pass 1: normalize keys */ for (p = list; p; p = p->next) { - const char *full = config_expand_abbrev(fmt, p->key, 0, 1); + const char *full = config_expand_abbrev(mgr, p->key, 0, 1); if (strcmp(full,p->key)) { tor_free(p->key); p->key = tor_strdup(full); @@ -466,14 +834,14 @@ config_assign(const config_format_t *fmt, void *options, config_line_t *list, * mentioned config options, and maybe set to their defaults. */ if (clear_first) { for (p = list; p; p = p->next) - config_reset_line(fmt, options, p->key, use_defaults); + config_reset_line(mgr, options, p->key, use_defaults); } options_seen = bitarray_init_zero(n_options); /* pass 3: assign. */ while (list) { int r; - if ((r=config_assign_line(fmt, options, list, config_assign_flags, + if ((r=config_assign_line(mgr, options, list, config_assign_flags, options_seen, msg))) { bitarray_free(options_seen); return r; @@ -485,7 +853,7 @@ config_assign(const config_format_t *fmt, void *options, config_line_t *list, /** Now we're done assigning a group of options to the configuration. * Subsequent group assignments should _replace_ linelists, not extend * them. */ - config_mark_lists_fragile(fmt, options); + config_mark_lists_fragile(mgr, options); return 0; } @@ -493,33 +861,32 @@ config_assign(const config_format_t *fmt, void *options, config_line_t *list, /** Reset config option <b>var</b> to 0, 0.0, NULL, or the equivalent. * Called from config_reset() and config_free(). */ static void -config_clear(const config_format_t *fmt, void *options, - const config_var_t *var) +config_clear(const config_mgr_t *mgr, void *options, const managed_var_t *var) { - - (void)fmt; /* unused */ - - struct_var_free(options, &var->member); + void *object = config_mgr_get_obj_mutable(mgr, options, var->object_idx); + struct_var_free(object, &var->cvar->member); } /** Clear the option indexed by <b>var</b> in <b>options</b>. Then if * <b>use_defaults</b>, set it to its default value. * Called by config_init() and option_reset_line() and option_assign_line(). */ static void -config_reset(const config_format_t *fmt, void *options, - const config_var_t *var, int use_defaults) +config_reset(const config_mgr_t *mgr, void *options, + const managed_var_t *var, int use_defaults) { config_line_t *c; char *msg = NULL; - CONFIG_CHECK(fmt, options); - config_clear(fmt, options, var); /* clear it first */ + CONFIG_CHECK(mgr, options); + config_clear(mgr, options, var); /* clear it first */ + if (!use_defaults) return; /* all done */ - if (var->initvalue) { + + if (var->cvar->initvalue) { c = tor_malloc_zero(sizeof(config_line_t)); - c->key = tor_strdup(var->member.name); - c->value = tor_strdup(var->initvalue); - if (config_assign_value(fmt, options, c, &msg) < 0) { + c->key = tor_strdup(var->cvar->member.name); + c->value = tor_strdup(var->cvar->initvalue); + if (config_assign_value(mgr, options, c, &msg) < 0) { // LCOV_EXCL_START log_warn(LD_BUG, "Failed to assign default: %s", msg); tor_free(msg); /* if this happens it's a bug */ @@ -531,23 +898,44 @@ config_reset(const config_format_t *fmt, void *options, /** Release storage held by <b>options</b>. */ void -config_free_(const config_format_t *fmt, void *options) +config_free_(const config_mgr_t *mgr, void *options) { - int i; - if (!options) return; - tor_assert(fmt); + tor_assert(mgr); - for (i=0; fmt->vars[i].member.name; ++i) - config_clear(fmt, options, &(fmt->vars[i])); + if (mgr->toplevel->clear_fn) { + mgr->toplevel->clear_fn(mgr, options); + } + config_suite_t **suitep = config_mgr_get_suite_ptr(mgr, options); + if (suitep) { + tor_assert(smartlist_len((*suitep)->configs) == + smartlist_len(mgr->subconfigs)); + SMARTLIST_FOREACH_BEGIN(mgr->subconfigs, const config_format_t *, fmt) { + void *obj = smartlist_get((*suitep)->configs, fmt_sl_idx); + if (fmt->clear_fn) { + fmt->clear_fn(mgr, obj); + } + } SMARTLIST_FOREACH_END(fmt); + } - if (fmt->extra) { - config_line_t **linep = STRUCT_VAR_P(options, fmt->extra->offset); + SMARTLIST_FOREACH_BEGIN(mgr->all_vars, const managed_var_t *, mv) { + config_clear(mgr, options, mv); + } SMARTLIST_FOREACH_END(mv); + + if (mgr->toplevel->extra) { + config_line_t **linep = STRUCT_VAR_P(options, + mgr->toplevel->extra->offset); config_free_lines(*linep); *linep = NULL; } + + if (suitep) { + SMARTLIST_FOREACH((*suitep)->configs, void *, obj, tor_free(obj)); + config_suite_free(*suitep); + } + tor_free(options); } @@ -555,59 +943,103 @@ config_free_(const config_format_t *fmt, void *options) * and <b>o2</b>. Must not be called for LINELIST_S or OBSOLETE options. */ int -config_is_same(const config_format_t *fmt, +config_is_same(const config_mgr_t *mgr, const void *o1, const void *o2, const char *name) { - CONFIG_CHECK(fmt, o1); - CONFIG_CHECK(fmt, o2); + CONFIG_CHECK(mgr, o1); + CONFIG_CHECK(mgr, o2); - const config_var_t *var = config_find_option(fmt, name); + const managed_var_t *var = config_mgr_find_var(mgr, name, true, NULL); if (!var) { return true; } + const void *obj1 = config_mgr_get_obj(mgr, o1, var->object_idx); + const void *obj2 = config_mgr_get_obj(mgr, o2, var->object_idx); - return struct_var_eq(o1, o2, &var->member); + return struct_var_eq(obj1, obj2, &var->cvar->member); +} + +/** + * Return a list of the options which have changed between <b>options1</b> and + * <b>options2</b>. If an option has reverted to its default value, it has a + * value entry of NULL. + * + * <b>options1</b> and <b>options2</b> must be top-level configuration objects + * of the type managed by <b>mgr</b>. + **/ +config_line_t * +config_get_changes(const config_mgr_t *mgr, + const void *options1, const void *options2) +{ + config_line_t *result = NULL; + config_line_t **next = &result; + SMARTLIST_FOREACH_BEGIN(mgr->all_vars, managed_var_t *, mv) { + if (config_var_is_contained(mv->cvar)) { + /* something else will check this var, or it doesn't need checking */ + continue; + } + const void *obj1 = config_mgr_get_obj(mgr, options1, mv->object_idx); + const void *obj2 = config_mgr_get_obj(mgr, options2, mv->object_idx); + + if (struct_var_eq(obj1, obj2, &mv->cvar->member)) { + continue; + } + + const char *varname = mv->cvar->member.name; + config_line_t *line = + config_get_assigned_option(mgr, options2, varname, 1); + + if (line) { + *next = line; + } else { + *next = tor_malloc_zero(sizeof(config_line_t)); + (*next)->key = tor_strdup(varname); + } + while (*next) + next = &(*next)->next; + } SMARTLIST_FOREACH_END(mv); + + return result; } /** Copy storage held by <b>old</b> into a new or_options_t and return it. */ void * -config_dup(const config_format_t *fmt, const void *old) +config_dup(const config_mgr_t *mgr, const void *old) { void *newopts; - int i; - newopts = config_new(fmt); - for (i=0; fmt->vars[i].member.name; ++i) { - if (config_var_is_contained(&fmt->vars[i])) { + newopts = config_new(mgr); + SMARTLIST_FOREACH_BEGIN(mgr->all_vars, managed_var_t *, mv) { + if (config_var_is_contained(mv->cvar)) { // Something else will copy this option, or it doesn't need copying. continue; } - if (struct_var_copy(newopts, old, &fmt->vars[i].member) < 0) { + const void *oldobj = config_mgr_get_obj(mgr, old, mv->object_idx); + void *newobj = config_mgr_get_obj_mutable(mgr, newopts, mv->object_idx); + if (struct_var_copy(newobj, oldobj, &mv->cvar->member) < 0) { // LCOV_EXCL_START log_err(LD_BUG, "Unable to copy value for %s.", - fmt->vars[i].member.name); + mv->cvar->member.name); tor_assert_unreached(); // LCOV_EXCL_STOP } - } + } SMARTLIST_FOREACH_END(mv); + return newopts; } /** Set all vars in the configuration object <b>options</b> to their default * values. */ void -config_init(const config_format_t *fmt, void *options) +config_init(const config_mgr_t *mgr, void *options) { - int i; - const config_var_t *var; - CONFIG_CHECK(fmt, options); + CONFIG_CHECK(mgr, options); - for (i=0; fmt->vars[i].member.name; ++i) { - var = &fmt->vars[i]; - if (!var->initvalue) + SMARTLIST_FOREACH_BEGIN(mgr->all_vars, const managed_var_t *, mv) { + if (!mv->cvar->initvalue) continue; /* defaults to NULL or 0 */ - config_reset(fmt, options, var, 1); - } + config_reset(mgr, options, mv, 1); + } SMARTLIST_FOREACH_END(mv); } /** Allocate and return a new string holding the written-out values of the vars @@ -615,21 +1047,21 @@ config_init(const config_format_t *fmt, void *options) * Else, if comment_defaults, write default values as comments. */ char * -config_dump(const config_format_t *fmt, const void *default_options, +config_dump(const config_mgr_t *mgr, const void *default_options, const void *options, int minimal, int comment_defaults) { + const config_format_t *fmt = mgr->toplevel; smartlist_t *elements; const void *defaults = default_options; void *defaults_tmp = NULL; config_line_t *line, *assigned; char *result; - int i; char *msg = NULL; if (defaults == NULL) { - defaults = defaults_tmp = config_new(fmt); - config_init(fmt, defaults_tmp); + defaults = defaults_tmp = config_new(mgr); + config_init(mgr, defaults_tmp); } /* XXX use a 1 here so we don't add a new log line while dumping */ @@ -644,24 +1076,24 @@ config_dump(const config_format_t *fmt, const void *default_options, } elements = smartlist_new(); - for (i=0; fmt->vars[i].member.name; ++i) { + SMARTLIST_FOREACH_BEGIN(mgr->all_vars, managed_var_t *, mv) { int comment_option = 0; - if (config_var_is_contained(&fmt->vars[i])) { + if (config_var_is_contained(mv->cvar)) { // Something else will dump this option, or it doesn't need dumping. continue; } /* Don't save 'hidden' control variables. */ - if (! config_var_is_dumpable(&fmt->vars[i])) + if (! config_var_is_dumpable(mv->cvar)) continue; - if (minimal && config_is_same(fmt, options, defaults, - fmt->vars[i].member.name)) + const char *name = mv->cvar->member.name; + if (minimal && config_is_same(mgr, options, defaults, name)) continue; else if (comment_defaults && - config_is_same(fmt, options, defaults, fmt->vars[i].member.name)) + config_is_same(mgr, options, defaults, name)) comment_option = 1; line = assigned = - config_get_assigned_option(fmt, options, fmt->vars[i].member.name, 1); + config_get_assigned_option(mgr, options, name, 1); for (; line; line = line->next) { if (!strcmpstart(line->key, "__")) { @@ -674,7 +1106,7 @@ config_dump(const config_format_t *fmt, const void *default_options, line->key, line->value); } config_free_lines(assigned); - } + } SMARTLIST_FOREACH_END(mv); if (fmt->extra) { line = *(config_line_t**)STRUCT_VAR_P(options, fmt->extra->offset); @@ -686,9 +1118,7 @@ config_dump(const config_format_t *fmt, const void *default_options, result = smartlist_join_strings(elements, "", 0, NULL); SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp)); smartlist_free(elements); - if (defaults_tmp) { - fmt->free_fn(defaults_tmp); - } + config_free(mgr, defaults_tmp); return result; } @@ -697,15 +1127,17 @@ config_dump(const config_format_t *fmt, const void *default_options, * Return false otherwise. Log errors at level <b>severity</b>. */ bool -config_check_ok(const config_format_t *fmt, const void *options, int severity) +config_check_ok(const config_mgr_t *mgr, const void *options, int severity) { bool all_ok = true; - for (int i=0; fmt->vars[i].member.name; ++i) { - if (!struct_var_ok(options, &fmt->vars[i].member)) { + + SMARTLIST_FOREACH_BEGIN(mgr->all_vars, const managed_var_t *, mv) { + if (!struct_var_ok(options, &mv->cvar->member)) { log_fn(severity, LD_BUG, "Invalid value for %s", - fmt->vars[i].member.name); + mv->cvar->member.name); all_ok = false; } - } + } SMARTLIST_FOREACH_END(mv); + return all_ok; } |