#define CONFMGT_PRIVATE
#include "orconfig.h"
#include "lib/confmgt/confmgt.h"
#include "lib/confmgt/structvar.h"
#include "lib/confmgt/unitparse.h"
#include "lib/container/bitarray.h"
#include "lib/container/smartlist.h"
#include "lib/encoding/confline.h"
#include "lib/log/escape.h"
#include "lib/log/log.h"
#include "lib/log/util_bug.h"
#include "lib/string/compat_ctype.h"
#include "lib/string/printf.h"
#include "lib/string/util_string.h"
#include "ext/siphash.h"
typedef struct managed_var_t {
const config_var_t *cvar;
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);
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 {
smartlist_t *configs;
};
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;
}
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 {
const config_format_t *toplevel;
smartlist_t *subconfigs;
smartlist_t *all_vars;
smartlist_t *all_abbrevs;
smartlist_t *all_deprecations;
bool frozen;
struct_magic_decl_t toplevel_magic;
};
#define IDX_TOPLEVEL (-1)
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;
}
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->has_config_suite,
"Tried to register a toplevel format in a non-toplevel position");
}
if (fmt->config_suite_offset) {
tor_assertf(fmt->has_config_suite,
"config_suite_offset was set, but has_config_suite was not.");
}
tor_assertf(fmt != mgr->toplevel &&
! smartlist_contains(mgr->subconfigs, fmt),
"Tried to register an already-registered format.");
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);
}
if (fmt->abbrevs) {
for (i = 0; fmt->abbrevs[i].abbreviated; ++i) {
smartlist_add(mgr->all_abbrevs, (void*)&fmt->abbrevs[i]);
}
}
if (fmt->deprecations) {
const config_deprecation_t *d;
for (d = fmt->deprecations; d->name; ++d) {
smartlist_add(mgr->all_deprecations, (void*)d);
}
}
}
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;
}
static inline config_suite_t **
config_mgr_get_suite_ptr(const config_mgr_t *mgr, void *toplevel)
{
if (! mgr->toplevel->has_config_suite)
return NULL;
return STRUCT_VAR_P(toplevel, mgr->toplevel->config_suite_offset);
}
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);
}
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);
}
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);
}
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;
}
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);
}
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;
}
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;
}
void
config_check_toplevel_magic(const config_mgr_t *mgr,
const void *object)
{
struct_check_magic(object, &mgr->toplevel_magic);
}
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);
}
#define CONFIG_CHECK(mgr, cfg) STMT_BEGIN \
config_mgr_assert_magic_ok((mgr), (cfg)); \
STMT_END
void *
config_new(const config_mgr_t *mgr)
{
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;
}
const char *
config_expand_abbrev(const config_mgr_t *mgr, const char *option,
int command_line, int warn_obsolete)
{
SMARTLIST_FOREACH_BEGIN(mgr->all_abbrevs, const config_abbrev_t *, abbrev) {
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.",
abbrev->abbreviated,
abbrev->full);
}
option = abbrev->full;
}
} SMARTLIST_FOREACH_END(abbrev);
return option;
}
const char *
config_find_deprecation(const config_mgr_t *mgr, const char *key)
{
if (BUG(mgr == NULL) || BUG(key == NULL))
return NULL;
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;
}
static const managed_var_t *
config_mgr_find_var(const config_mgr_t *mgr,
const char *key,
bool allow_truncated, int *idx_out)
{
const size_t keylen = strlen(key);
if (idx_out)
*idx_out = -1;
if (!keylen)
return NULL;
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;
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, mv->cvar->member.name);
if (idx_out)
*idx_out = mv_sl_idx;
return mv;
}
} SMARTLIST_FOREACH_END(mv);
return NULL;
}
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;
}
static int
config_count_options(const config_mgr_t *mgr)
{
return smartlist_len(mgr->all_vars);
}
static bool
config_var_has_flag(const config_var_t *var, uint32_t flag)
{
uint32_t have_flags = var->flags | struct_var_get_flags(&var->member);
return (have_flags & flag) != 0;
}
static bool
config_var_is_replaced_on_set(const config_var_t *var)
{
return ! config_var_has_flag(var, CFLG_NOREPLACE);
}
bool
config_var_is_settable(const config_var_t *var)
{
return ! config_var_has_flag(var, CFLG_NOSET);
}
static bool
config_var_is_gettable(const config_var_t *var)
{
(void)var;
return true;
}
static bool
config_var_should_list_changes(const config_var_t *var)
{
return ! config_var_has_flag(var, CFLG_NOCMP);
}
static bool
config_var_needs_copy(const config_var_t *var)
{
return ! config_var_has_flag(var, CFLG_NOCOPY);
}
bool
config_var_is_listable(const config_var_t *var)
{
return ! config_var_has_flag(var, CFLG_NOLIST);
}
static bool
config_var_is_dumpable(const config_var_t *var)
{
return ! config_var_has_flag(var, CFLG_NODUMP);
}
static int
config_assign_value(const config_mgr_t *mgr, void *options,
config_line_t *c, char **msg)
{
const managed_var_t *var;
CONFIG_CHECK(mgr, options);
var = config_mgr_find_var(mgr, c->key, true, NULL);
tor_assert(var);
tor_assert(!strcmp(c->key, var->cvar->member.name));
void *object = config_mgr_get_obj_mutable(mgr, options, var->object_idx);
if (config_var_has_flag(var->cvar, CFLG_WARN_OBSOLETE)) {
log_warn(LD_GENERAL, "Skipping obsolete configuration option \"%s\".",
var->cvar->member.name);
} else if (config_var_has_flag(var->cvar, CFLG_WARN_DISABLED)) {
log_warn(LD_GENERAL, "This copy of Tor was built without support for "
"the option \"%s\". Skipping.", var->cvar->member.name);
}
return struct_var_kvassign(object, c, msg, &var->cvar->member);
}
static void
config_mark_lists_fragile(const config_mgr_t *mgr, void *options)
{
tor_assert(mgr);
tor_assert(options);
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
warn_deprecated_option(const char *what, const char *why)
{
const char *space = (why && strlen(why)) ? " " : "";
log_warn(LD_CONFIG, "The %s option is deprecated, and will most likely "
"be removed in a future version of Tor.%s%s (If you think this is "
"a mistake, please let us know!)",
what, space, why);
}
static int
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 managed_var_t *mvar;
CONFIG_CHECK(mgr, options);
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,
"Found unrecognized option '%s'; saving it.", c->key);
config_line_append((config_line_t**)lvalue, c->key, c->value);
return 0;
} else {
tor_asprintf(msg,
"Unknown option '%s'. Failing.", c->key);
return -1;
}
}
const config_var_t *cvar = mvar->cvar;
tor_assert(cvar);
if (strcmp(cvar->member.name, c->key)) {
tor_free(c->key);
c->key = tor_strdup(cvar->member.name);
}
const char *deprecation_msg;
if (warn_deprecations &&
(deprecation_msg = config_find_deprecation(mgr, cvar->member.name))) {
warn_deprecated_option(cvar->member.name, deprecation_msg);
}
if (!strlen(c->value)) {
if (!clear_first) {
if (! config_var_is_replaced_on_set(cvar) &&
c->command != CONFIG_LINE_CLEAR) {
log_warn(LD_CONFIG,
"Linelist option '%s' has no value. Skipping.", c->key);
} else {
config_reset(mgr, options, mvar, use_defaults);
}
}
return 0;
} else if (c->command == CONFIG_LINE_CLEAR && !clear_first) {
tor_assert_nonfatal_unreached();
config_reset(mgr, options, mvar, use_defaults);
}
if (options_seen && config_var_is_replaced_on_set(cvar)) {
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.", cvar->member.name);
}
bitarray_set(options_seen, var_index);
}
if (config_assign_value(mgr, options, c, msg) < 0)
return -2;
return 0;
}
STATIC void
config_reset_line(const config_mgr_t *mgr, void *options,
const char *key, int use_defaults)
{
const managed_var_t *var;
CONFIG_CHECK(mgr, options);
var = config_mgr_find_var(mgr, key, true, NULL);
if (!var)
return;
config_reset(mgr, options, var, use_defaults);
}
static int
config_value_needs_escape(const char *value)
{
if (*value == '\"')
return 1;
while (*value) {
switch (*value)
{
case '\r':
case '\n':
case '#':
return 1;
default:
if (!TOR_ISPRINT(*value))
return 1;
}
++value;
}
return 0;
}
config_line_t *
config_get_assigned_option(const config_mgr_t *mgr, const void *options,
const char *key, int escape_val)
{
const managed_var_t *var;
config_line_t *result;
tor_assert(options && key);
CONFIG_CHECK(mgr, options);
var = config_mgr_find_var(mgr, key, true, NULL);
if (!var) {
log_warn(LD_CONFIG, "Unknown option '%s'. Failing.", key);
return NULL;
}
if (! config_var_is_gettable(var->cvar)) {
log_warn(LD_CONFIG, "Option '%s' is obsolete or unfetchable. Failing.",
key);
return NULL;
}
const void *object = config_mgr_get_obj(mgr, options, var->object_idx);
result = struct_var_kvencode(object, &var->cvar->member);
if (escape_val) {
config_line_t *line;
for (line = result; line; line = line->next) {
if (line->value && config_value_needs_escape(line->value)) {
char *newval = esc_for_log(line->value);
tor_free(line->value);
line->value = newval;
}
}
}
return result;
}
int
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(mgr);
const unsigned clear_first = config_assign_flags & CAL_CLEAR_FIRST;
const unsigned use_defaults = config_assign_flags & CAL_USE_DEFAULTS;
CONFIG_CHECK(mgr, options);
for (p = list; p; p = p->next) {
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);
}
}
if (clear_first) {
for (p = list; p; p = p->next)
config_reset_line(mgr, options, p->key, use_defaults);
}
options_seen = bitarray_init_zero(n_options);
while (list) {
int r;
if ((r=config_assign_line(mgr, options, list, config_assign_flags,
options_seen, msg))) {
bitarray_free(options_seen);
return r;
}
list = list->next;
}
bitarray_free(options_seen);
config_mark_lists_fragile(mgr, options);
return 0;
}
static void
config_clear(const config_mgr_t *mgr, void *options, const managed_var_t *var)
{
void *object = config_mgr_get_obj_mutable(mgr, options, var->object_idx);
struct_var_free(object, &var->cvar->member);
}
static void
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(mgr, options);
config_clear(mgr, options, var);
if (!use_defaults)
return;
if (var->cvar->initvalue) {
c = tor_malloc_zero(sizeof(config_line_t));
c->key = tor_strdup(var->cvar->member.name);
c->value = tor_strdup(var->cvar->initvalue);
if (config_assign_value(mgr, options, c, &msg) < 0) {
log_warn(LD_BUG, "Failed to assign default: %s", msg);
tor_free(msg);
}
config_free_lines(c);
}
}
void
config_free_(const config_mgr_t *mgr, void *options)
{
if (!options)
return;
tor_assert(mgr);
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);
}
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);
}
int
config_is_same(const config_mgr_t *mgr,
const void *o1, const void *o2,
const char *name)
{
CONFIG_CHECK(mgr, o1);
CONFIG_CHECK(mgr, o2);
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(obj1, obj2, &var->cvar->member);
}
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_should_list_changes(mv->cvar)) {
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;
}
void *
config_dup(const config_mgr_t *mgr, const void *old)
{
void *newopts;
newopts = config_new(mgr);
SMARTLIST_FOREACH_BEGIN(mgr->all_vars, managed_var_t *, mv) {
if (! config_var_needs_copy(mv->cvar)) {
continue;
}
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) {
log_err(LD_BUG, "Unable to copy value for %s.",
mv->cvar->member.name);
tor_assert_unreached();
}
} SMARTLIST_FOREACH_END(mv);
return newopts;
}
void
config_init(const config_mgr_t *mgr, void *options)
{
CONFIG_CHECK(mgr, options);
SMARTLIST_FOREACH_BEGIN(mgr->all_vars, const managed_var_t *, mv) {
if (!mv->cvar->initvalue)
continue;
config_reset(mgr, options, mv, 1);
} SMARTLIST_FOREACH_END(mv);
}
static int
config_check_immutable_flags(const config_format_t *fmt,
const void *old_options,
const void *new_options,
char **msg_out)
{
tor_assert(fmt);
tor_assert(new_options);
if (BUG(! old_options))
return 0;
unsigned i;
for (i = 0; fmt->vars[i].member.name; ++i) {
const config_var_t *v = &fmt->vars[i];
if (! config_var_has_flag(v, CFLG_IMMUTABLE))
continue;
if (! struct_var_eq(old_options, new_options, &v->member)) {
tor_asprintf(msg_out,
"While Tor is running, changing %s is not allowed",
v->member.name);
return -1;
}
}
return 0;
}
static validation_status_t
config_validate_single(const config_format_t *fmt,
const void *old_options, void *options,
char **msg_out)
{
tor_assert(fmt);
tor_assert(options);
if (fmt->pre_normalize_fn) {
if (fmt->pre_normalize_fn(options, msg_out) < 0) {
return VSTAT_PRE_NORMALIZE_ERR;
}
}
if (fmt->legacy_validate_fn) {
if (fmt->legacy_validate_fn(old_options, options, msg_out) < 0) {
return VSTAT_LEGACY_ERR;
}
}
if (fmt->validate_fn) {
if (fmt->validate_fn(options, msg_out) < 0) {
return VSTAT_VALIDATE_ERR;
}
}
if (old_options) {
if (config_check_immutable_flags(fmt, old_options, options, msg_out) < 0) {
return VSTAT_TRANSITION_ERR;
}
if (fmt->check_transition_fn) {
if (fmt->check_transition_fn(old_options, options, msg_out) < 0) {
return VSTAT_TRANSITION_ERR;
}
}
}
if (fmt->post_normalize_fn) {
if (fmt->post_normalize_fn(options, msg_out) < 0) {
return VSTAT_POST_NORMALIZE_ERR;
}
}
return VSTAT_OK;
}
validation_status_t
config_validate(const config_mgr_t *mgr,
const void *old_options, void *options,
char **msg_out)
{
validation_status_t rv;
CONFIG_CHECK(mgr, options);
if (old_options) {
CONFIG_CHECK(mgr, old_options);
}
config_suite_t **suitep_new = config_mgr_get_suite_ptr(mgr, options);
config_suite_t **suitep_old = NULL;
if (old_options)
suitep_old = config_mgr_get_suite_ptr(mgr, (void*) old_options);
if (suitep_new) {
SMARTLIST_FOREACH_BEGIN(mgr->subconfigs, const config_format_t *, fmt) {
void *obj = smartlist_get((*suitep_new)->configs, fmt_sl_idx);
const void *obj_old=NULL;
if (suitep_old)
obj_old = smartlist_get((*suitep_old)->configs, fmt_sl_idx);
rv = config_validate_single(fmt, obj_old, obj, msg_out);
if (rv < 0)
return rv;
} SMARTLIST_FOREACH_END(fmt);
}
rv = config_validate_single(mgr->toplevel, old_options, options, msg_out);
if (rv < 0)
return rv;
return VSTAT_OK;
}
char *
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;
char *msg = NULL;
if (defaults == NULL) {
defaults = defaults_tmp = config_new(mgr);
config_init(mgr, defaults_tmp);
}
if (default_options == NULL) {
if (config_validate(mgr, NULL, defaults_tmp, &msg) < 0) {
log_err(LD_BUG, "Failed to validate default config: %s", msg);
tor_free(msg);
tor_assert(0);
}
}
elements = smartlist_new();
SMARTLIST_FOREACH_BEGIN(mgr->all_vars, managed_var_t *, mv) {
int comment_option = 0;
if (! config_var_is_dumpable(mv->cvar))
continue;
const char *name = mv->cvar->member.name;
if (minimal && config_is_same(mgr, options, defaults, name))
continue;
else if (comment_defaults &&
config_is_same(mgr, options, defaults, name))
comment_option = 1;
line = assigned =
config_get_assigned_option(mgr, options, name, 1);
for (; line; line = line->next) {
if (!strcmpstart(line->key, "__")) {
continue;
}
int value_exists = line->value && *(line->value);
smartlist_add_asprintf(elements, "%s%s%s%s\n",
comment_option ? "# " : "",
line->key, value_exists ? " " : "", line->value);
}
config_free_lines(assigned);
} SMARTLIST_FOREACH_END(mv);
if (fmt->extra) {
line = *(config_line_t**)STRUCT_VAR_P(options, fmt->extra->offset);
for (; line; line = line->next) {
int value_exists = line->value && *(line->value);
smartlist_add_asprintf(elements, "%s%s%s\n",
line->key, value_exists ? " " : "", line->value);
}
}
result = smartlist_join_strings(elements, "", 0, NULL);
SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp));
smartlist_free(elements);
config_free(mgr, defaults_tmp);
return result;
}
bool
config_check_ok(const config_mgr_t *mgr, const void *options, int severity)
{
bool all_ok = true;
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",
mv->cvar->member.name);
all_ok = false;
}
} SMARTLIST_FOREACH_END(mv);
return all_ok;
}