From c60a85d22ab87fef5a7de2fee616ad112835177b Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Fri, 14 Jun 2019 08:42:24 -0400 Subject: [PATCH] Add a "typed_var" abstraction to implement lvalue access in C. Right now, this has been done at a high level by confparse.c, but it makes more sense to lower it. This API is radically un-typesafe as it stands; we'll be wrapping it in a safer API as we do #30914 and lower the struct manipulation code as well. Closes ticket 30864. --- Makefile.am | 4 +- changes/ticket30864 | 3 + scripts/maint/add_c_file.py | 6 +- src/app/config/confparse.c | 349 ++------------ src/lib/conf/conftypes.h | 16 + src/lib/confmgt/.may_include | 2 + src/lib/confmgt/include.am | 7 +- src/lib/confmgt/type_defs.c | 727 ++++++++++++++++++++++++++++++ src/lib/confmgt/type_defs.h | 17 + src/lib/confmgt/typedvar.c | 305 +++++++++++++ src/lib/confmgt/typedvar.h | 49 ++ src/lib/confmgt/var_type_def_st.h | 147 ++++++ src/test/test_confparse.c | 4 +- src/test/test_options.c | 12 +- 14 files changed, 1315 insertions(+), 333 deletions(-) create mode 100644 changes/ticket30864 create mode 100644 src/lib/confmgt/type_defs.c create mode 100644 src/lib/confmgt/type_defs.h create mode 100644 src/lib/confmgt/typedvar.c create mode 100644 src/lib/confmgt/typedvar.h create mode 100644 src/lib/confmgt/var_type_def_st.h diff --git a/Makefile.am b/Makefile.am index 0083608ce9..6445502933 100644 --- a/Makefile.am +++ b/Makefile.am @@ -41,6 +41,7 @@ TOR_UTIL_LIBS = \ src/lib/libtor-geoip.a \ src/lib/libtor-process.a \ src/lib/libtor-buf.a \ + src/lib/libtor-confmgt.a \ src/lib/libtor-pubsub.a \ src/lib/libtor-dispatch.a \ src/lib/libtor-time.a \ @@ -54,7 +55,6 @@ TOR_UTIL_LIBS = \ src/lib/libtor-math.a \ src/lib/libtor-meminfo.a \ src/lib/libtor-osinfo.a \ - src/lib/libtor-confmgt.a \ src/lib/libtor-log.a \ src/lib/libtor-lock.a \ src/lib/libtor-fdio.a \ @@ -75,6 +75,7 @@ TOR_UTIL_TESTING_LIBS = \ src/lib/libtor-geoip-testing.a \ src/lib/libtor-process-testing.a \ src/lib/libtor-buf-testing.a \ + src/lib/libtor-confmgt-testing.a \ src/lib/libtor-pubsub-testing.a \ src/lib/libtor-dispatch-testing.a \ src/lib/libtor-time-testing.a \ @@ -89,7 +90,6 @@ TOR_UTIL_TESTING_LIBS = \ src/lib/libtor-meminfo-testing.a \ src/lib/libtor-osinfo-testing.a \ src/lib/libtor-term-testing.a \ - src/lib/libtor-confmgt-testing.a \ src/lib/libtor-log-testing.a \ src/lib/libtor-lock-testing.a \ src/lib/libtor-fdio-testing.a \ diff --git a/changes/ticket30864 b/changes/ticket30864 new file mode 100644 index 0000000000..b8fb571300 --- /dev/null +++ b/changes/ticket30864 @@ -0,0 +1,3 @@ + o Code simplification and refactoring: + - Extract our variable manipulation code from confparse.c to a new + lower-level typedvar.h module. Closes ticket 30864. diff --git a/scripts/maint/add_c_file.py b/scripts/maint/add_c_file.py index 499415974f..adf7ce79bb 100755 --- a/scripts/maint/add_c_file.py +++ b/scripts/maint/add_c_file.py @@ -125,8 +125,8 @@ class AutomakeChunk: Y \ Z """ - self.prespace = "\t" - self.postspace = "\t\t" + prespace = "\t" + postspace = "\t\t" for lineno, line in enumerate(self.lines): m = re.match(r'(\s+)(\S+)(\s+)\\', line) if not m: @@ -135,7 +135,7 @@ class AutomakeChunk: if fname > member: self.insert_before(lineno, member, prespace, postspace) return - self.insert_at_end(member) + self.insert_at_end(member, prespace, postspace) def insert_before(self, lineno, member, prespace, postspace): self.lines.insert(lineno, diff --git a/src/app/config/confparse.c b/src/app/config/confparse.c index 296e7c2a39..bc2ab24e4f 100644 --- a/src/app/config/confparse.c +++ b/src/app/config/confparse.c @@ -30,6 +30,8 @@ #include "lib/container/bitarray.h" #include "lib/encoding/confline.h" +#include "lib/confmgt/typedvar.h" + static void config_reset(const config_format_t *fmt, void *options, const config_var_t *var, int use_defaults); @@ -160,7 +162,6 @@ static int config_assign_value(const config_format_t *fmt, void *options, config_line_t *c, char **msg) { - int i, ok; const config_var_t *var; void *lvalue; @@ -168,144 +169,14 @@ config_assign_value(const config_format_t *fmt, void *options, var = config_find_option(fmt, c->key); tor_assert(var); + tor_assert(!strcmp(c->key, var->name)); lvalue = STRUCT_VAR_P(options, var->var_offset); - switch (var->type) { + if (var->type == CONFIG_TYPE_ROUTERSET) { + // XXXX make the backend extensible so that we don't have to + // XXXX handle ROUTERSET specially. - case CONFIG_TYPE_INT: - case CONFIG_TYPE_POSINT: - i = (int)tor_parse_long(c->value, 10, - var->type==CONFIG_TYPE_INT ? INT_MIN : 0, - INT_MAX, - &ok, NULL); - if (!ok) { - tor_asprintf(msg, - "Int keyword '%s %s' is malformed or out of bounds.", - c->key, c->value); - return -1; - } - *(int *)lvalue = i; - break; - - case CONFIG_TYPE_UINT64: { - uint64_t u64 = tor_parse_uint64(c->value, 10, - 0, UINT64_MAX, &ok, NULL); - if (!ok) { - tor_asprintf(msg, - "uint64 keyword '%s %s' is malformed or out of bounds.", - c->key, c->value); - return -1; - } - *(uint64_t *)lvalue = u64; - break; - } - - case CONFIG_TYPE_CSV_INTERVAL: { - /* We used to have entire smartlists here. But now that all of our - * download schedules use exponential backoff, only the first part - * matters. */ - const char *comma = strchr(c->value, ','); - const char *val = c->value; - char *tmp = NULL; - if (comma) { - tmp = tor_strndup(c->value, comma - c->value); - val = tmp; - } - - i = config_parse_interval(val, &ok); - if (!ok) { - tor_asprintf(msg, - "Interval '%s %s' is malformed or out of bounds.", - c->key, c->value); - tor_free(tmp); - return -1; - } - *(int *)lvalue = i; - tor_free(tmp); - break; - } - - case CONFIG_TYPE_INTERVAL: { - i = config_parse_interval(c->value, &ok); - if (!ok) { - tor_asprintf(msg, - "Interval '%s %s' is malformed or out of bounds.", - c->key, c->value); - return -1; - } - *(int *)lvalue = i; - break; - } - - case CONFIG_TYPE_MSEC_INTERVAL: { - i = config_parse_msec_interval(c->value, &ok); - if (!ok) { - tor_asprintf(msg, - "Msec interval '%s %s' is malformed or out of bounds.", - c->key, c->value); - return -1; - } - *(int *)lvalue = i; - break; - } - - case CONFIG_TYPE_MEMUNIT: { - uint64_t u64 = config_parse_memunit(c->value, &ok); - if (!ok) { - tor_asprintf(msg, - "Value '%s %s' is malformed or out of bounds.", - c->key, c->value); - return -1; - } - *(uint64_t *)lvalue = u64; - break; - } - - case CONFIG_TYPE_BOOL: - i = (int)tor_parse_long(c->value, 10, 0, 1, &ok, NULL); - if (!ok) { - tor_asprintf(msg, - "Boolean '%s %s' expects 0 or 1.", - c->key, c->value); - return -1; - } - *(int *)lvalue = i; - break; - - case CONFIG_TYPE_AUTOBOOL: - if (!strcasecmp(c->value, "auto")) - *(int *)lvalue = -1; - else if (!strcmp(c->value, "0")) - *(int *)lvalue = 0; - else if (!strcmp(c->value, "1")) - *(int *)lvalue = 1; - else { - tor_asprintf(msg, "Boolean '%s %s' expects 0, 1, or 'auto'.", - c->key, c->value); - return -1; - } - break; - - case CONFIG_TYPE_STRING: - case CONFIG_TYPE_FILENAME: - tor_free(*(char **)lvalue); - *(char **)lvalue = tor_strdup(c->value); - break; - - case CONFIG_TYPE_DOUBLE: - *(double *)lvalue = atof(c->value); - break; - - case CONFIG_TYPE_ISOTIME: - if (parse_iso_time(c->value, (time_t *)lvalue)) { - tor_asprintf(msg, - "Invalid time '%s' for keyword '%s'", c->value, c->key); - return -1; - } - break; - - case CONFIG_TYPE_ROUTERSET: if (*(routerset_t**)lvalue) { routerset_free(*(routerset_t**)lvalue); } @@ -315,50 +186,10 @@ config_assign_value(const config_format_t *fmt, void *options, c->value, c->key); return -1; } - break; - - case CONFIG_TYPE_CSV: - if (*(smartlist_t**)lvalue) { - SMARTLIST_FOREACH(*(smartlist_t**)lvalue, char *, cp, tor_free(cp)); - smartlist_clear(*(smartlist_t**)lvalue); - } else { - *(smartlist_t**)lvalue = smartlist_new(); - } - - smartlist_split_string(*(smartlist_t**)lvalue, c->value, ",", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - break; - - case CONFIG_TYPE_LINELIST: - case CONFIG_TYPE_LINELIST_S: - { - config_line_t *lastval = *(config_line_t**)lvalue; - if (lastval && lastval->fragile) { - if (c->command != CONFIG_LINE_APPEND) { - config_free_lines(lastval); - *(config_line_t**)lvalue = NULL; - } else { - lastval->fragile = 0; - } - } - - config_line_append((config_line_t**)lvalue, c->key, c->value); - } - break; - case CONFIG_TYPE_OBSOLETE: - log_warn(LD_CONFIG, "Skipping obsolete configuration option '%s'", c->key); - break; - case CONFIG_TYPE_LINELIST_V: - tor_asprintf(msg, - "You may not provide a value for virtual option '%s'", c->key); - return -1; - // LCOV_EXCL_START - default: - tor_assert_unreached(); - break; - // LCOV_EXCL_STOP + return 0; } - return 0; + + return typed_var_kvassign(lvalue, c, msg, var->type); } /** Mark every linelist in options "fragile", so that fresh assignments @@ -544,100 +375,15 @@ config_get_assigned_option(const config_format_t *fmt, const void *options, } value = STRUCT_VAR_P(options, var->var_offset); - result = tor_malloc_zero(sizeof(config_line_t)); - result->key = tor_strdup(var->name); - switch (var->type) - { - case CONFIG_TYPE_STRING: - case CONFIG_TYPE_FILENAME: - if (*(char**)value) { - result->value = tor_strdup(*(char**)value); - } else { - tor_free(result->key); - tor_free(result); - return NULL; - } - break; - case CONFIG_TYPE_ISOTIME: - if (*(time_t*)value) { - result->value = tor_malloc(ISO_TIME_LEN+1); - format_iso_time(result->value, *(time_t*)value); - } else { - tor_free(result->key); - tor_free(result); - } - escape_val = 0; /* Can't need escape. */ - break; - case CONFIG_TYPE_CSV_INTERVAL: - case CONFIG_TYPE_INTERVAL: - case CONFIG_TYPE_MSEC_INTERVAL: - case CONFIG_TYPE_POSINT: - case CONFIG_TYPE_INT: - /* This means every or_options_t uint or bool element - * needs to be an int. Not, say, a uint16_t or char. */ - tor_asprintf(&result->value, "%d", *(int*)value); - escape_val = 0; /* Can't need escape. */ - break; - case CONFIG_TYPE_UINT64: /* Fall through */ - case CONFIG_TYPE_MEMUNIT: - tor_asprintf(&result->value, "%"PRIu64, - (*(uint64_t*)value)); - escape_val = 0; /* Can't need escape. */ - break; - case CONFIG_TYPE_DOUBLE: - tor_asprintf(&result->value, "%f", *(double*)value); - escape_val = 0; /* Can't need escape. */ - break; - - case CONFIG_TYPE_AUTOBOOL: - if (*(int*)value == -1) { - result->value = tor_strdup("auto"); - escape_val = 0; - break; - } - /* fall through */ - case CONFIG_TYPE_BOOL: - result->value = tor_strdup(*(int*)value ? "1" : "0"); - escape_val = 0; /* Can't need escape. */ - break; - case CONFIG_TYPE_ROUTERSET: - result->value = routerset_to_string(*(routerset_t**)value); - break; - case CONFIG_TYPE_CSV: - if (*(smartlist_t**)value) - result->value = - smartlist_join_strings(*(smartlist_t**)value, ",", 0, NULL); - else - result->value = tor_strdup(""); - break; - case CONFIG_TYPE_OBSOLETE: - log_fn(LOG_INFO, LD_CONFIG, - "You asked me for the value of an obsolete config option '%s'.", - key); - tor_free(result->key); - tor_free(result); - return NULL; - case CONFIG_TYPE_LINELIST_S: - tor_free(result->key); - tor_free(result); - result = config_lines_dup_and_filter(*(const config_line_t **)value, - key); - break; - case CONFIG_TYPE_LINELIST: - case CONFIG_TYPE_LINELIST_V: - tor_free(result->key); - tor_free(result); - result = config_lines_dup(*(const config_line_t**)value); - break; - // LCOV_EXCL_START - default: - tor_free(result->key); - tor_free(result); - log_warn(LD_BUG,"Unknown type %d for known key '%s'", - var->type, key); - return NULL; - // LCOV_EXCL_STOP - } + if (var->type == CONFIG_TYPE_ROUTERSET) { + // XXXX make the backend extensible so that we don't have to + // XXXX handle ROUTERSET specially. + result = tor_malloc_zero(sizeof(config_line_t)); + result->key = tor_strdup(var->name); + result->value = routerset_to_string(*(routerset_t**)value); + } else { + result = typed_var_kvencode(var->name, value, var->type); + } if (escape_val) { config_line_t *line; @@ -765,56 +511,17 @@ config_clear(const config_format_t *fmt, void *options, { void *lvalue = STRUCT_VAR_P(options, var->var_offset); (void)fmt; /* unused */ - switch (var->type) { - case CONFIG_TYPE_STRING: - case CONFIG_TYPE_FILENAME: - tor_free(*(char**)lvalue); - break; - case CONFIG_TYPE_DOUBLE: - *(double*)lvalue = 0.0; - break; - case CONFIG_TYPE_ISOTIME: - *(time_t*)lvalue = 0; - break; - case CONFIG_TYPE_CSV_INTERVAL: - case CONFIG_TYPE_INTERVAL: - case CONFIG_TYPE_MSEC_INTERVAL: - case CONFIG_TYPE_POSINT: - case CONFIG_TYPE_INT: - case CONFIG_TYPE_BOOL: - *(int*)lvalue = 0; - break; - case CONFIG_TYPE_AUTOBOOL: - *(int*)lvalue = -1; - break; - case CONFIG_TYPE_UINT64: - case CONFIG_TYPE_MEMUNIT: - *(uint64_t*)lvalue = 0; - break; - case CONFIG_TYPE_ROUTERSET: - if (*(routerset_t**)lvalue) { - routerset_free(*(routerset_t**)lvalue); - *(routerset_t**)lvalue = NULL; - } - break; - case CONFIG_TYPE_CSV: - if (*(smartlist_t**)lvalue) { - SMARTLIST_FOREACH(*(smartlist_t **)lvalue, char *, cp, tor_free(cp)); - smartlist_free(*(smartlist_t **)lvalue); - *(smartlist_t **)lvalue = NULL; - } - break; - case CONFIG_TYPE_LINELIST: - case CONFIG_TYPE_LINELIST_S: - config_free_lines(*(config_line_t **)lvalue); - *(config_line_t **)lvalue = NULL; - break; - case CONFIG_TYPE_LINELIST_V: - /* handled by linelist_s. */ - break; - case CONFIG_TYPE_OBSOLETE: - break; + if (var->type == CONFIG_TYPE_ROUTERSET) { + // XXXX make the backend extensible so that we don't have to + // XXXX handle ROUTERSET specially. + if (*(routerset_t**)lvalue) { + routerset_free(*(routerset_t**)lvalue); + *(routerset_t**)lvalue = NULL; + } + return; } + + typed_var_free(lvalue, var->type); } /** Clear the option indexed by var in options. Then if diff --git a/src/lib/conf/conftypes.h b/src/lib/conf/conftypes.h index eb4eb1245f..b03234b62c 100644 --- a/src/lib/conf/conftypes.h +++ b/src/lib/conf/conftypes.h @@ -7,6 +7,22 @@ /** * @file conftypes.h * @brief Types used to specify configurable options. + * + * This header defines the types that different modules will use in order to + * declare their configuration and state variables, and tell the configuration + * management code about those variables. From the individual module's point + * of view, its configuration and state are simply data structures. + * + * For defining new variable types, see var_type_def_st.h. + * + * For the code that manipulates variables defined via this module, see + * lib/confmgt/, especially typedvar.h and (later) structvar.h. The + * configuration manager is responsible for encoding, decoding, and + * maintaining the configuration structures used by the various modules. + * + * STATUS NOTE: This is a work in process refactoring. It is not yet possible + * for modules to define their own variables, and much of the configuration + * management code is still in src/app/config/. **/ #ifndef TOR_SRC_LIB_CONF_CONFTYPES_H diff --git a/src/lib/confmgt/.may_include b/src/lib/confmgt/.may_include index 640acf3770..d85dbf6904 100644 --- a/src/lib/confmgt/.may_include +++ b/src/lib/confmgt/.may_include @@ -2,6 +2,8 @@ orconfig.h lib/cc/*.h lib/conf/*.h lib/confmgt/*.h +lib/container/*.h +lib/encoding/*.h lib/log/*.h lib/malloc/*.h lib/string/*.h diff --git a/src/lib/confmgt/include.am b/src/lib/confmgt/include.am index 729e6147ff..a2c7649957 100644 --- a/src/lib/confmgt/include.am +++ b/src/lib/confmgt/include.am @@ -6,6 +6,8 @@ endif # ADD_C_FILE: INSERT SOURCES HERE. src_lib_libtor_confmgt_a_SOURCES = \ + src/lib/confmgt/type_defs.c \ + src/lib/confmgt/typedvar.c \ src/lib/confmgt/unitparse.c src_lib_libtor_confmgt_testing_a_SOURCES = \ @@ -15,4 +17,7 @@ src_lib_libtor_confmgt_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) # ADD_C_FILE: INSERT HEADERS HERE. noinst_HEADERS += \ - src/lib/confmgt/unitparse.h + src/lib/confmgt/type_defs.h \ + src/lib/confmgt/typedvar.h \ + src/lib/confmgt/unitparse.h \ + src/lib/confmgt/var_type_def_st.h diff --git a/src/lib/confmgt/type_defs.c b/src/lib/confmgt/type_defs.c new file mode 100644 index 0000000000..62b4c1019d --- /dev/null +++ b/src/lib/confmgt/type_defs.c @@ -0,0 +1,727 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file type_defs.c + * @brief Definitions for various low-level configuration types. + * + * This module creates a number of var_type_def_t objects, to be used by + * typedvar.c in manipulating variables. + * + * The types here are common types that can be implemented with Tor's + * low-level functionality. To define new types, see var_type_def_st.h. + **/ + +#include "orconfig.h" +#include "lib/conf/conftypes.h" +#include "lib/confmgt/typedvar.h" +#include "lib/confmgt/type_defs.h" +#include "lib/confmgt/unitparse.h" + +#include "lib/cc/compat_compiler.h" +#include "lib/conf/conftypes.h" +#include "lib/container/smartlist.h" +#include "lib/encoding/confline.h" +#include "lib/encoding/time_fmt.h" +#include "lib/log/escape.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/string/parse_int.h" +#include "lib/string/printf.h" + +#include "lib/confmgt/var_type_def_st.h" + +#include +#include + +////// +// CONFIG_TYPE_STRING +// CONFIG_TYPE_FILENAME +// +// These two types are the same for now, but they have different names. +////// + +static int +string_parse(void *target, const char *value, char **errmsg, + const void *params) +{ + (void)params; + (void)errmsg; + char **p = (char**)target; + *p = tor_strdup(value); + return 0; +} + +static char * +string_encode(const void *value, const void *params) +{ + (void)params; + const char **p = (const char**)value; + return *p ? tor_strdup(*p) : NULL; +} + +static void +string_clear(void *value, const void *params) +{ + (void)params; + char **p = (char**)value; + tor_free(*p); // sets *p to NULL. +} + +static const var_type_fns_t string_fns = { + .parse = string_parse, + .encode = string_encode, + .clear = string_clear, +}; + +///// +// CONFIG_TYPE_INT +// CONFIG_TYPE_POSINT +// +// These types are implemented as int, possibly with a restricted range. +///// + +typedef struct int_type_params_t { + int minval; + int maxval; +} int_parse_params_t; + +static const int_parse_params_t INT_PARSE_UNRESTRICTED = { + .minval = INT_MIN, + .maxval = INT_MAX, +}; + +static const int_parse_params_t INT_PARSE_POSINT = { + .minval = 0, + .maxval = INT_MAX, +}; + +static int +int_parse(void *target, const char *value, char **errmsg, const void *params) +{ + const int_parse_params_t *pp; + if (params) { + pp = params; + } else { + pp = &INT_PARSE_UNRESTRICTED; + } + int *p = target; + int ok=0; + *p = (int)tor_parse_long(value, 10, pp->minval, pp->maxval, &ok, NULL); + if (!ok) { + tor_asprintf(errmsg, "Integer %s is malformed or out of bounds.", + value); + return -1; + } + return 0; +} + +static char * +int_encode(const void *value, const void *params) +{ + (void)params; + int v = *(int*)value; + char *result; + tor_asprintf(&result, "%d", v); + return result; +} + +static void +int_clear(void *value, const void *params) +{ + (void)params; + *(int*)value = 0; +} + +static bool +int_ok(const void *value, const void *params) +{ + const int_parse_params_t *pp = params; + if (pp) { + int v = *(int*)value; + return pp->minval <= v && v <= pp->maxval; + } else { + return true; + } +} + +static const var_type_fns_t int_fns = { + .parse = int_parse, + .encode = int_encode, + .clear = int_clear, + .ok = int_ok, +}; + +///// +// CONFIG_TYPE_UINT64 +// +// This type is an unrestricted u64. +///// + +static int +uint64_parse(void *target, const char *value, char **errmsg, + const void *params) +{ + (void)params; + (void)errmsg; + uint64_t *p = target; + int ok=0; + *p = tor_parse_uint64(value, 10, 0, UINT64_MAX, &ok, NULL); + if (!ok) { + tor_asprintf(errmsg, "Integer %s is malformed or out of bounds.", + value); + return -1; + } + return 0; +} + +static char * +uint64_encode(const void *value, const void *params) +{ + (void)params; + uint64_t v = *(uint64_t*)value; + char *result; + tor_asprintf(&result, "%"PRIu64, v); + return result; +} + +static void +uint64_clear(void *value, const void *params) +{ + (void)params; + *(uint64_t*)value = 0; +} + +static const var_type_fns_t uint64_fns = { + .parse = uint64_parse, + .encode = uint64_encode, + .clear = uint64_clear, +}; + +///// +// CONFIG_TYPE_INTERVAL +// CONFIG_TYPE_MSEC_INTERVAL +// CONFIG_TYPE_MEMUNIT +// +// These types are implemented using the config_parse_units() function. +// The intervals are stored as ints, whereas memory units are stored as +// uint64_ts. +///// + +static int +units_parse_u64(void *target, const char *value, char **errmsg, + const void *params) +{ + const unit_table_t *table = params; + tor_assert(table); + uint64_t *v = (uint64_t*)target; + int ok=1; + *v = config_parse_units(value, table, &ok); + if (!ok) { + *errmsg = tor_strdup("Provided value is malformed or out of bounds."); + return -1; + } + return 0; +} + +static int +units_parse_int(void *target, const char *value, char **errmsg, + const void *params) +{ + const unit_table_t *table = params; + tor_assert(table); + int *v = (int*)target; + int ok=1; + uint64_t u64 = config_parse_units(value, table, &ok); + if (!ok) { + *errmsg = tor_strdup("Provided value is malformed or out of bounds."); + return -1; + } + if (u64 > INT_MAX) { + tor_asprintf(errmsg, "Provided value %s is too large", value); + return -1; + } + *v = (int) u64; + return 0; +} + +static bool +units_ok_int(const void *value, const void *params) +{ + (void)params; + int v = *(int*)value; + return v >= 0; +} + +static const var_type_fns_t memunit_fns = { + .parse = units_parse_u64, + .encode = uint64_encode, // doesn't use params + .clear = uint64_clear, // doesn't use params +}; + +static const var_type_fns_t interval_fns = { + .parse = units_parse_int, + .encode = int_encode, // doesn't use params + .clear = int_clear, // doesn't use params, + .ok = units_ok_int // can't use int_ok, since that expects int params. +}; + +///// +// CONFIG_TYPE_DOUBLE +// +// This is a nice simple double. +///// + +static int +double_parse(void *target, const char *value, char **errmsg, + const void *params) +{ + (void)params; + (void)errmsg; + double *v = (double*)target; + // XXXX This is the preexisting behavior, but we should detect errors here. + *v = atof(value); + return 0; +} + +static char * +double_encode(const void *value, const void *params) +{ + (void)params; + double v = *(double*)value; + char *result; + tor_asprintf(&result, "%f", v); + return result; +} + +static void +double_clear(void *value, const void *params) +{ + (void)params; + double *v = (double *)value; + *v = 0.0; +} + +static const var_type_fns_t double_fns = { + .parse = double_parse, + .encode = double_encode, + .clear = double_clear, +}; + +///// +// CONFIG_TYPE_BOOL +// CONFIG_TYPE_AUTOBOOL +// +// These types are implemented as a case-insensitive string-to-integer +// mapping. +///// + +typedef struct enumeration_table_t { + const char *name; + int value; +} enumeration_table_t; + +static int +enum_parse(void *target, const char *value, char **errmsg, + const void *params) +{ + const enumeration_table_t *table = params; + int *p = (int *)target; + for (; table->name; ++table) { + if (!strcasecmp(value, table->name)) { + *p = table->value; + return 0; + } + } + tor_asprintf(errmsg, "Unrecognized value %s.", value); + return -1; +} + +static char * +enum_encode(const void *value, const void *params) +{ + int v = *(const int*)value; + const enumeration_table_t *table = params; + for (; table->name; ++table) { + if (v == table->value) + return tor_strdup(table->name); + } + return NULL; // error. +} + +static void +enum_clear(void *value, const void *params) +{ + int *p = (int*)value; + const enumeration_table_t *table = params; + tor_assert(table->name); + *p = table->value; +} + +static bool +enum_ok(const void *value, const void *params) +{ + int v = *(const int*)value; + const enumeration_table_t *table = params; + for (; table->name; ++table) { + if (v == table->value) + return true; + } + return false; +} + +static const enumeration_table_t enum_table_bool[] = { + { "0", 0 }, + { "1", 1 }, + { NULL, 0 }, +}; + +static const enumeration_table_t enum_table_autobool[] = { + { "0", 0 }, + { "1", 1 }, + { "auto", -1 }, + { NULL, 0 }, +}; + +static const var_type_fns_t enum_fns = { + .parse = enum_parse, + .encode = enum_encode, + .clear = enum_clear, + .ok = enum_ok, +}; + +///// +// CONFIG_TYPE_ISOTIME +// +// This is a time_t, encoded in ISO8601 format. +///// + +static int +time_parse(void *target, const char *value, char **errmsg, + const void *params) +{ + (void) params; + time_t *p = target; + if (parse_iso_time(value, p) < 0) { + tor_asprintf(errmsg, "Invalid time %s", escaped(value)); + return -1; + } + return 0; +} + +static char * +time_encode(const void *value, const void *params) +{ + (void)params; + time_t v = *(const time_t *)value; + char *result = tor_malloc(ISO_TIME_LEN+1); + format_iso_time(result, v); + return result; +} + +static void +time_clear(void *value, const void *params) +{ + (void)params; + time_t *t = value; + *t = 0; +} + +static const var_type_fns_t time_fns = { + .parse = time_parse, + .encode = time_encode, + .clear = time_clear, +}; + +///// +// CONFIG_TYPE_CSV +// +// This type is a comma-separated list of strings, stored in a smartlist_t. +// An empty list may be encoded either as an empty smartlist, or as NULL. +///// + +static int +csv_parse(void *target, const char *value, char **errmsg, + const void *params) +{ + (void)params; + (void)errmsg; + smartlist_t **sl = (smartlist_t**)target; + *sl = smartlist_new(); + smartlist_split_string(*sl, value, ",", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + return 0; +} + +static char * +csv_encode(const void *value, const void *params) +{ + (void)params; + const smartlist_t *sl = *(const smartlist_t **)value; + if (! sl) + return tor_strdup(""); + + return smartlist_join_strings(*(smartlist_t**)value, ",", 0, NULL); +} + +static void +csv_clear(void *value, const void *params) +{ + (void)params; + smartlist_t **sl = (smartlist_t**)value; + if (!*sl) + return; + SMARTLIST_FOREACH(*sl, char *, cp, tor_free(cp)); + smartlist_free(*sl); // clears pointer. +} + +static const var_type_fns_t csv_fns = { + .parse = csv_parse, + .encode = csv_encode, + .clear = csv_clear, +}; + +///// +// CONFIG_TYPE_CSV_INTERVAL +// +// This type used to be a list of time intervals, used to determine a download +// schedule. Now, only the first interval counts: everything after the first +// comma is discarded. +///// + +static int +legacy_csv_interval_parse(void *target, const char *value, char **errmsg, + const void *params) +{ + (void)params; + /* We used to have entire smartlists here. But now that all of our + * download schedules use exponential backoff, only the first part + * matters. */ + const char *comma = strchr(value, ','); + const char *val = value; + char *tmp = NULL; + if (comma) { + tmp = tor_strndup(val, comma - val); + val = tmp; + } + + int rv = units_parse_int(target, val, errmsg, &time_units); + tor_free(tmp); + return rv; +} + +static const var_type_fns_t legacy_csv_interval_fns = { + .parse = legacy_csv_interval_parse, + .encode = int_encode, + .clear = int_clear, +}; + +///// +// CONFIG_TYPE_LINELIST +// CONFIG_TYPE_LINELIST_S +// CONFIG_TYPE_LINELIST_V +// +// A linelist is a raw config_line_t list. Order is preserved. +// +// The LINELIST type is used for homogeneous lists, where all the lines +// have the same key. +// +// The LINELIST_S and LINELIST_V types are used for the case where multiple +// lines of different keys are kept in a single list, to preserve their +// relative order. The unified list is stored as a "virtual" variable whose +// type is LINELIST_V; the individual sublists are treated as variables of +// type LINELIST_S. +// +// A linelist may be fragile or non-fragile. Assigning a line to a fragile +// linelist replaces the list with the line. If the line has the "APPEND" +// command set on it, or if the list is non-fragile, the line is appended. +// Either way, the new list is non-fragile. +///// + +static int +linelist_kv_parse(void *target, const struct config_line_t *line, + char **errmsg, const void *params) +{ + (void)params; + (void)errmsg; + config_line_t **lines = target; + + if (*lines && (*lines)->fragile) { + if (line->command == CONFIG_LINE_APPEND) { + (*lines)->fragile = 0; + } else { + config_free_lines(*lines); // sets it to NULL + } + } + + config_line_append(lines, line->key, line->value); + return 0; +} + +static int +linelist_kv_virt_noparse(void *target, const struct config_line_t *line, + char **errmsg, const void *params) +{ + (void)target; + (void)line; + (void)params; + *errmsg = tor_strdup("Cannot assign directly to virtual option."); + return -1; +} + +static struct config_line_t * +linelist_kv_encode(const char *key, const void *value, + const void *params) +{ + (void)key; + (void)params; + config_line_t *lines = *(config_line_t **)value; + return config_lines_dup(lines); +} + +static struct config_line_t * +linelist_s_kv_encode(const char *key, const void *value, + const void *params) +{ + (void)params; + config_line_t *lines = *(config_line_t **)value; + return config_lines_dup_and_filter(lines, key); +} + +static void +linelist_clear(void *target, const void *params) +{ + (void)params; + config_line_t **lines = target; + config_free_lines(*lines); // sets it to NULL +} + +static bool +linelist_eq(const void *a, const void *b, const void *params) +{ + (void)params; + const config_line_t *lines_a = *(const config_line_t **)a; + const config_line_t *lines_b = *(const config_line_t **)b; + return config_lines_eq(lines_a, lines_b); +} + +static int +linelist_copy(void *target, const void *value, const void *params) +{ + (void)params; + config_line_t **ptr = (config_line_t **)target; + const config_line_t *val = *(const config_line_t **)value; + config_free_lines(*ptr); + *ptr = config_lines_dup(val); + return 0; +} + +static const var_type_fns_t linelist_fns = { + .kv_parse = linelist_kv_parse, + .kv_encode = linelist_kv_encode, + .clear = linelist_clear, + .eq = linelist_eq, + .copy = linelist_copy, +}; + +static const var_type_fns_t linelist_v_fns = { + .kv_parse = linelist_kv_virt_noparse, + .kv_encode = linelist_kv_encode, + .clear = linelist_clear, + .eq = linelist_eq, + .copy = linelist_copy, +}; + +static const var_type_fns_t linelist_s_fns = { + .kv_parse = linelist_kv_parse, + .kv_encode = linelist_s_kv_encode, + .clear = linelist_clear, + .eq = linelist_eq, + .copy = linelist_copy, +}; + +///// +// CONFIG_TYPE_ROUTERSET +// +// XXXX This type is not implemented here, since routerset_t is not available +// XXXX to this module. +///// + +///// +// CONFIG_TYPE_OBSOLETE +// +// Used to indicate an obsolete option. +// +// XXXX This is not a type, and should be handled at a higher level of +// XXXX abstraction. +///// + +static int +ignore_parse(void *target, const char *value, char **errmsg, + const void *params) +{ + (void)target; + (void)value; + (void)errmsg; + (void)params; + // XXXX move this to a higher level, once such a level exists. + log_warn(LD_GENERAL, "Skipping obsolete configuration option."); + return 0; +} + +static char * +ignore_encode(const void *value, const void *params) +{ + (void)value; + (void)params; + return NULL; +} + +static const var_type_fns_t ignore_fns = { + .parse = ignore_parse, + .encode = ignore_encode, +}; + +/** + * Table mapping conf_type_t values to var_type_def_t objects. + **/ +static const var_type_def_t type_definitions_table[] = { + [CONFIG_TYPE_STRING] = { "String", &string_fns, NULL }, + [CONFIG_TYPE_FILENAME] = { "Filename", &string_fns, NULL }, + [CONFIG_TYPE_INT] = { "SignedInteger", &int_fns, &INT_PARSE_UNRESTRICTED }, + [CONFIG_TYPE_POSINT] = { "Integer", &int_fns, &INT_PARSE_POSINT }, + [CONFIG_TYPE_UINT64] = { "Integer", &uint64_fns, NULL, }, + [CONFIG_TYPE_MEMUNIT] = { "DataSize", &memunit_fns, &memory_units }, + [CONFIG_TYPE_INTERVAL] = { "TimeInterval", &interval_fns, &time_units }, + [CONFIG_TYPE_MSEC_INTERVAL] = { "TimeMsecInterval", &interval_fns, + &time_msec_units }, + [CONFIG_TYPE_DOUBLE] = { "Float", &double_fns, NULL }, + [CONFIG_TYPE_BOOL] = { "Boolean", &enum_fns, &enum_table_bool }, + [CONFIG_TYPE_AUTOBOOL] = { "Boolean+Auto", &enum_fns, &enum_table_autobool }, + [CONFIG_TYPE_ISOTIME] = { "Time", &time_fns, NULL }, + [CONFIG_TYPE_CSV] = { "CommaList", &csv_fns, NULL }, + [CONFIG_TYPE_CSV_INTERVAL] = { "TimeInterval", &legacy_csv_interval_fns, + NULL }, + [CONFIG_TYPE_LINELIST] = { "LineList", &linelist_fns, NULL }, + [CONFIG_TYPE_LINELIST_S] = { "Dependent", &linelist_s_fns, NULL }, + [CONFIG_TYPE_LINELIST_V] = { "Virtual", &linelist_v_fns, NULL }, + [CONFIG_TYPE_OBSOLETE] = { "Obsolete", &ignore_fns, NULL } +}; + +/** + * Return a pointer to the var_type_def_t object for the given + * config_type_t value, or NULL if no such type definition exists. + **/ +const var_type_def_t * +lookup_type_def(config_type_t type) +{ + int t = type; + tor_assert(t >= 0); + if (t >= (int)ARRAY_LENGTH(type_definitions_table)) + return NULL; + return &type_definitions_table[t]; +} diff --git a/src/lib/confmgt/type_defs.h b/src/lib/confmgt/type_defs.h new file mode 100644 index 0000000000..ecf040529e --- /dev/null +++ b/src/lib/confmgt/type_defs.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file type_defs.h + * @brief Header for lib/confmgt/type_defs.c + **/ + +#ifndef TOR_LIB_CONFMGT_TYPE_DEFS_H +#define TOR_LIB_CONFMGT_TYPE_DEFS_H + +const struct var_type_def_t *lookup_type_def(config_type_t type); + +#endif /* !defined(TOR_LIB_CONFMGT_TYPE_DEFS_H) */ diff --git a/src/lib/confmgt/typedvar.c b/src/lib/confmgt/typedvar.c new file mode 100644 index 0000000000..fc45c44481 --- /dev/null +++ b/src/lib/confmgt/typedvar.c @@ -0,0 +1,305 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file typedvar.c + * @brief Functions for accessing a pointer as an object of a given type. + * + * These functions represent a low-level API for accessing a typed variable. + * They are used in the configuration system to examine and set fields in + * configuration objects used by individual modules. + * + * Almost no code should call these directly. + **/ + +#include "orconfig.h" +#include "lib/conf/conftypes.h" +#include "lib/confmgt/type_defs.h" +#include "lib/confmgt/typedvar.h" +#include "lib/encoding/confline.h" +#include "lib/log/escape.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/string/util_string.h" + +#include "lib/confmgt/var_type_def_st.h" + +#include +#include + +/** + * Try to parse a string in value that encodes an object of the type + * defined by def. + * + * On success, adjust the lvalue pointed to by target to hold that + * value, and return 0. On failure, set *errmsg to a newly allocated + * string holding an error message, and return -1. + **/ +int +typed_var_assign_ex(void *target, const char *value, char **errmsg, + const var_type_def_t *def) +{ + if (BUG(!def)) + return -1; + // clear old value if needed. + typed_var_free_ex(target, def); + + tor_assert(def->fns->parse); + return def->fns->parse(target, value, errmsg, def->params); +} + +/** + * Try to parse a single line from the head ofline that encodes an + * object of the type defined in def. On success and failure, behave as + * typed_var_assign_ex(). + * + * All types for which keys are significant should use this function. + * + * Note that although multiple lines may be provided in line, + * only the first one is handled by this function. + **/ +int +typed_var_kvassign_ex(void *target, const config_line_t *line, + char **errmsg, const var_type_def_t *def) +{ + if (BUG(!def)) + return -1; + + if (def->fns->kv_parse) { + // We do _not_ free the old value here, since linelist options + // sometimes have append semantics. + return def->fns->kv_parse(target, line, errmsg, def->params); + } + + return typed_var_assign_ex(target, line->value, errmsg, def); +} + +/** + * Release storage held by a variable in target of type defined by + * def, and set target to a reasonable default. + **/ +void +typed_var_free_ex(void *target, const var_type_def_t *def) +{ + if (BUG(!def)) + return; + if (def->fns->clear) { + def->fns->clear(target, def->params); + } +} + +/** + * Encode a value of type def pointed to by value, and return + * its result in a newly allocated string. The string may need to be escaped. + * + * Returns NULL if this option has a NULL value, or on internal error. + **/ +char * +typed_var_encode_ex(const void *value, const var_type_def_t *def) +{ + if (BUG(!def)) + return NULL; + tor_assert(def->fns->encode); + return def->fns->encode(value, def->params); +} + +/** + * As typed_var_encode_ex(), but returns a newly allocated config_line_t + * object. The provided key is used as the key of the lines, unless + * the type is one (line a linelist) that encodes its own keys. + * + * This function may return a list of multiple lines. + * + * Returns NULL if there are no lines to encode, or on internal error. + */ +config_line_t * +typed_var_kvencode_ex(const char *key, const void *value, + const var_type_def_t *def) +{ + if (BUG(!def)) + return NULL; + if (def->fns->kv_encode) { + return def->fns->kv_encode(key, value, def->params); + } + char *encoded_value = typed_var_encode_ex(value, def); + if (!encoded_value) + return NULL; + + config_line_t *result = tor_malloc_zero(sizeof(config_line_t)); + result->key = tor_strdup(key); + result->value = encoded_value; + return result; +} + +/** + * Set dest to contain the same value as src. Both types + * must be as defined by def. + * + * Return 0 on success, and -1 on failure. + **/ +int +typed_var_copy_ex(void *dest, const void *src, const var_type_def_t *def) +{ + if (BUG(!def)) + return -1; + if (def->fns->copy) { + // If we have been provided a copy fuction, use it. + return def->fns->copy(dest, src, def); + } + + // Otherwise, encode 'src' and parse the result into 'def'. + char *enc = typed_var_encode_ex(src, def); + if (!enc) { + typed_var_free_ex(dest, def); + return 0; + } + char *err = NULL; + int rv = typed_var_assign_ex(dest, enc, &err, def); + if (BUG(rv < 0)) { + log_warn(LD_BUG, "Encoded value %s was not parseable as a %s: %s", + escaped(enc), def->name, err?err:""); + } + tor_free(err); + tor_free(enc); + return rv; +} + +/** + * Return true if a and b are semantically equivalent. + * Both types must be as defined by def. + **/ +bool +typed_var_eq_ex(const void *a, const void *b, const var_type_def_t *def) +{ + if (BUG(!def)) + return false; + + if (def->fns->eq) { + // Use a provided eq function if we got one. + return def->fns->eq(a, b, def->params); + } + + // Otherwise, encode the values and compare them. + char *enc_a = typed_var_encode_ex(a, def); + char *enc_b = typed_var_encode_ex(b, def); + bool eq = !strcmp_opt(enc_a,enc_b); + tor_free(enc_a); + tor_free(enc_b); + return eq; +} + +/** + * Check whether value encodes a valid value according to the + * type definition in def. + */ +bool +typed_var_ok_ex(const void *value, const var_type_def_t *def) +{ + if (BUG(!def)) + return false; + + if (def->fns->ok) + return def->fns->ok(value, def->params); + + return true; +} + +/* ===== + * The functions below take a config_type_t instead of a var_type_def_t. + * I'd like to deprecate them eventually and use var_type_def_t everywhere, + * but for now they make migration easier. + * ===== */ + +/** + * As typed_var_assign_ex(), but look up the definition of the configuration + * type from a provided config_type_t enum. + */ +int +typed_var_assign(void *target, const char *value, char **errmsg, + config_type_t type) +{ + const var_type_def_t *def = lookup_type_def(type); + return typed_var_assign_ex(target, value, errmsg, def); +} + +/** + * As typed_var_kvassign_ex(), but look up the definition of the configuration + * type from a provided config_type_t enum. + */ +int +typed_var_kvassign(void *target, const config_line_t *line, char **errmsg, + config_type_t type) +{ + const var_type_def_t *def = lookup_type_def(type); + return typed_var_kvassign_ex(target, line, errmsg, def); +} + +/** + * As typed_var_free_ex(), but look up the definition of the configuration + * type from a provided config_type_t enum. + */ +void +typed_var_free(void *target, config_type_t type) +{ + const var_type_def_t *def = lookup_type_def(type); + return typed_var_free_ex(target, def); +} + +/** + * As typed_var_encode_ex(), but look up the definition of the configuration + * type from a provided config_type_t enum. + */ +char * +typed_var_encode(const void *value, config_type_t type) +{ + const var_type_def_t *def = lookup_type_def(type); + return typed_var_encode_ex(value, def); +} + +/** + * As typed_var_kvencode_ex(), but look up the definition of the configuration + * type from a provided config_type_t enum. + */ +config_line_t * +typed_var_kvencode(const char *key, const void *value, config_type_t type) +{ + const var_type_def_t *def = lookup_type_def(type); + return typed_var_kvencode_ex(key, value, def); +} + +/** + * As typed_var_copy_ex(), but look up the definition of the configuration type + * from a provided config_type_t enum. + */ +int +typed_var_copy(void *dest, const void *src, config_type_t type) +{ + const var_type_def_t *def = lookup_type_def(type); + return typed_var_copy_ex(dest, src, def); +} + +/** + * As typed_var_eq_ex(), but look up the definition of the configuration type + * from a provided config_type_t enum. + */ +bool +typed_var_eq(const void *a, const void *b, config_type_t type) +{ + const var_type_def_t *def = lookup_type_def(type); + return typed_var_eq_ex(a, b, def); +} + +/** + * As typed_var_ok_ex(), but look up the definition of the configuration type + * from a provided config_type_t enum. + */ +bool +typed_var_ok(const void *value, config_type_t type) +{ + const var_type_def_t *def = lookup_type_def(type); + return typed_var_ok_ex(value, def); +} diff --git a/src/lib/confmgt/typedvar.h b/src/lib/confmgt/typedvar.h new file mode 100644 index 0000000000..720ad54fc6 --- /dev/null +++ b/src/lib/confmgt/typedvar.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file typedvar.h + * @brief Header for lib/confmgt/typedvar.c + **/ + +#ifndef TOR_LIB_CONFMGT_TYPEDVAR_H +#define TOR_LIB_CONFMGT_TYPEDVAR_H + +#include + +enum config_type_t; +struct config_line_t; + +typedef struct var_type_fns_t var_type_fns_t; +typedef struct var_type_def_t var_type_def_t; + +int typed_var_assign(void *target, const char *value, char **errmsg, + enum config_type_t type); +void typed_var_free(void *target, enum config_type_t type); +char *typed_var_encode(const void *value, enum config_type_t type); +int typed_var_copy(void *dest, const void *src, enum config_type_t type); +bool typed_var_eq(const void *a, const void *b, enum config_type_t type); +bool typed_var_ok(const void *value, enum config_type_t type); + +int typed_var_kvassign(void *target, const struct config_line_t *line, + char **errmsg, enum config_type_t type); +struct config_line_t *typed_var_kvencode(const char *key, const void *value, + enum config_type_t type); + +int typed_var_assign_ex(void *target, const char *value, char **errmsg, + const var_type_def_t *def); +void typed_var_free_ex(void *target, const var_type_def_t *def); +char *typed_var_encode_ex(const void *value, const var_type_def_t *def); +int typed_var_copy_ex(void *dest, const void *src, const var_type_def_t *def); +bool typed_var_eq_ex(const void *a, const void *b, const var_type_def_t *def); +bool typed_var_ok_ex(const void *value, const var_type_def_t *def); + +int typed_var_kvassign_ex(void *target, const struct config_line_t *line, + char **errmsg, const var_type_def_t *def); +struct config_line_t *typed_var_kvencode_ex(const char *key, const void *value, + const var_type_def_t *def); + +#endif /* !defined(TOR_LIB_CONFMGT_TYPEDVAR_H) */ diff --git a/src/lib/confmgt/var_type_def_st.h b/src/lib/confmgt/var_type_def_st.h new file mode 100644 index 0000000000..c63ff66eff --- /dev/null +++ b/src/lib/confmgt/var_type_def_st.h @@ -0,0 +1,147 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file typedvar.h + * @brief Structure declarations for typedvar type definitions. + * + * This structure is used for defining new variable types. If you are not + * defining a new variable type for use by the configuration management + * system, you don't need this structure. + * + * For defining new variables, see the types in conftypes.h. + * + * For data-driven access to configuration variables, see the other members of + * lib/confmgt/. + * + * STATUS NOTE: It is not yet possible to actually define new variables + * outside of config.c, and many of the types that will eventually be used + * to do so are not yet moved. This will change as more of #29211 is + * completed. + **/ + +#ifndef TOR_LIB_CONFMGT_VAR_TYPE_DEF_ST_H +#define TOR_LIB_CONFMGT_VAR_TYPE_DEF_ST_H + +#include + +struct config_line_t; + +/** + * A structure full of functions pointers to implement a variable type. + * + * Every type MUST implement parse or kv_parse and encode or kv_encode; + * the other functions pointers MAY be NULL. + * + * All functions here take a params argument, whose value + * is determined by the type definition. Two types may have the + * same functions, but differ only in parameters. + **/ +struct var_type_fns_t { + /** + * Try to parse a string in value that encodes an object of this + * type. On success, adjust the lvalue pointed to by target to hold + * that value, and return 0. On failure, set *errmsg to a newly + * allocated string holding an error message, and return -1. + **/ + int (*parse)(void *target, const char *value, char **errmsg, + const void *params); + /** + * Try to parse a single line from the head ofline that encodes + * an object of this type. On success and failure, behave as in the parse() + * function. + * + * If this function is absent, it is implemented in terms of parse(). + * + * All types for which keys are significant should use this method. For + * example, a "linelist" type records the actual keys that are given + * for each line, and so should use this method. + * + * Note that although multiple lines may be provided in line, + * only the first one should be handled by this function. + **/ + int (*kv_parse)(void *target, const struct config_line_t *line, + char **errmsg, const void *params); + /** + * Encode a value pointed to by value and return its result + * in a newly allocated string. The string may need to be escaped. + * + * If this function is absent, it is implemented in terms of kv_encode(). + * + * Returns NULL if this option has a NULL value, or on internal error. + * + * Requirement: all strings generated by encode() should produce a + * semantically equivalent value when given to parse(). + **/ + char *(*encode)(const void *value, const void *params); + /** + * As encode(), but returns a newly allocated config_line_t object. The + * provided key is used as the key of the lines, unless the type is + * one that encodes its own keys. + * + * Unlike kv_parse(), this function will return a list of multiple lines, + * if value is such that it must be encoded by multiple lines. + * + * Returns NULL if there are no lines to encode, or on internal error. + * + * If this function is absent, it is implemented in terms of encode(). + **/ + struct config_line_t *(*kv_encode)(const char *key, const void *value, + const void *params); + /** + * Free all storage held in arg, and set arg to a default + * value -- usually zero or NULL. + * + * If this function is absent, the default implementation does nothing. + **/ + void (*clear)(void *arg, const void *params); + /** + * Return true if a and b hold the same value, and false + * otherwise. + * + * If this function is absent, it is implemented by encoding both a and + * b and comparing their encoded strings for equality. + **/ + bool (*eq)(const void *a, const void *b, const void *params); + /** + * Try to copy the value from value into target. + * On success return 0; on failure return -1. + * + * If this function is absent, it is implemented by encoding the value + * into a string, and then parsing it into the target. + **/ + int (*copy)(void *target, const void *value, const void *params); + /** + * Check whether value holds a valid value according to the + * rules of this type; return true if it does and false if it doesn't. + * + * The default implementation for this function assumes that all + * values are valid. + **/ + bool (*ok)(const void *value, const void *params); +}; + +/** + * A structure describing a type that can be manipulated with the typedvar_* + * functions. + **/ +struct var_type_def_t { + /** + * The name of this type. Should not include spaces. Used for + * debugging, log messages, and the controller API. */ + const char *name; + /** + * A function table for this type. + */ + const struct var_type_fns_t *fns; + /** + * A pointer to a value that should be passed as the 'params' argument when + * calling the functions in this type's function table. + */ + const void *params; +}; + +#endif /* !defined(TOR_LIB_CONFMGT_VAR_TYPE_DEF_ST_H) */ diff --git a/src/test/test_confparse.c b/src/test/test_confparse.c index 89a6eb5265..fef361ef65 100644 --- a/src/test/test_confparse.c +++ b/src/test/test_confparse.c @@ -469,9 +469,9 @@ static const badval_test_t bv_badcsvi2 = { "csv_interval cl,10\n", "malformed" }; static const badval_test_t bv_nonoption = { "fnord 10\n", "Unknown option" }; static const badval_test_t bv_badmem = { "mem 3 trits\n", "malformed" }; -static const badval_test_t bv_badbool = { "boolean 7\n", "expects 0 or 1" }; +static const badval_test_t bv_badbool = { "boolean 7\n", "Unrecognized value"}; static const badval_test_t bv_badabool = - { "autobool 7\n", "expects 0, 1, or 'auto'" }; + { "autobool 7\n", "Unrecognized value" }; static const badval_test_t bv_badtime = { "time lunchtime\n", "Invalid time" }; static const badval_test_t bv_virt = { "MixedLines 7\n", "virtual option" }; static const badval_test_t bv_rs = { "Routerset 2.2.2.2.2\n", "Invalid" }; diff --git a/src/test/test_options.c b/src/test/test_options.c index d693fe0568..b39cd4f1e4 100644 --- a/src/test/test_options.c +++ b/src/test/test_options.c @@ -258,13 +258,17 @@ test_options_validate(void *arg) WANT_ERR("BridgeRelay 1\nDirCache 0", "We're a bridge but DirCache is disabled.", PH_VALIDATE); + // XXXX We should replace this with a more full error message once #29211 + // XXXX is done. It is truncated for now because at the current stage + // XXXX of refactoring, we can't give a full error message like before. WANT_ERR_LOG("HeartbeatPeriod 21 snarks", - "Interval 'HeartbeatPeriod 21 snarks' is malformed or" - " out of bounds.", LOG_WARN, "Unknown unit 'snarks'.", + "malformed or out of bounds", LOG_WARN, + "Unknown unit 'snarks'.", PH_ASSIGN); + // XXXX As above. WANT_ERR_LOG("LogTimeGranularity 21 snarks", - "Msec interval 'LogTimeGranularity 21 snarks' is malformed or" - " out of bounds.", LOG_WARN, "Unknown unit 'snarks'.", + "malformed or out of bounds", LOG_WARN, + "Unknown unit 'snarks'.", PH_ASSIGN); OK("HeartbeatPeriod 1 hour", PH_VALIDATE); OK("LogTimeGranularity 100 milliseconds", PH_VALIDATE);