summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Makefile.am2
-rw-r--r--changes/ticket308643
-rwxr-xr-xscripts/maint/add_c_file.py6
-rwxr-xr-xscripts/maint/checkSpace.pl2
-rw-r--r--src/app/config/confparse.c543
-rw-r--r--src/app/config/confparse.h65
-rw-r--r--src/include.am2
-rw-r--r--src/lib/conf/.may_include2
-rw-r--r--src/lib/conf/conftypes.h99
-rw-r--r--src/lib/conf/include.am4
-rw-r--r--src/lib/confmgt/.may_include9
-rw-r--r--src/lib/confmgt/include.am23
-rw-r--r--src/lib/confmgt/type_defs.c727
-rw-r--r--src/lib/confmgt/type_defs.h17
-rw-r--r--src/lib/confmgt/typedvar.c305
-rw-r--r--src/lib/confmgt/typedvar.h49
-rw-r--r--src/lib/confmgt/unitparse.c206
-rw-r--r--src/lib/confmgt/unitparse.h34
-rw-r--r--src/lib/confmgt/var_type_def_st.h147
-rw-r--r--src/lib/encoding/confline.c2
-rw-r--r--src/lib/encoding/confline.h2
-rw-r--r--src/test/test_confparse.c66
-rw-r--r--src/test/test_options.c12
24 files changed, 1739 insertions, 590 deletions
diff --git a/.gitignore b/.gitignore
index 0865f981b1..77610b3193 100644
--- a/.gitignore
+++ b/.gitignore
@@ -162,6 +162,8 @@ uptime-*.json
/src/lib/libtor-buf-testing.a
/src/lib/libtor-compress.a
/src/lib/libtor-compress-testing.a
+/src/lib/libtor-confmgt.a
+/src/lib/libtor-confmgt-testing.a
/src/lib/libtor-container.a
/src/lib/libtor-container-testing.a
/src/lib/libtor-crypt-ops.a
diff --git a/Makefile.am b/Makefile.am
index e823f9e103..309696ede9 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 \
@@ -74,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 \
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/scripts/maint/checkSpace.pl b/scripts/maint/checkSpace.pl
index 433ae62807..9c9b68ff9d 100755
--- a/scripts/maint/checkSpace.pl
+++ b/scripts/maint/checkSpace.pl
@@ -177,7 +177,7 @@ for my $fn (@ARGV) {
$1 ne "elsif" and $1 ne "WINAPI" and $2 ne "WINAPI" and
$1 ne "void" and $1 ne "__attribute__" and $1 ne "op" and
$1 ne "size_t" and $1 ne "double" and $1 ne "uint64_t" and
- $1 ne "workqueue_reply_t") {
+ $1 ne "workqueue_reply_t" and $1 ne "bool") {
msg " fn ():$fn:$.\n";
}
}
diff --git a/src/app/config/confparse.c b/src/app/config/confparse.c
index 3bf14b378d..bc2ab24e4f 100644
--- a/src/app/config/confparse.c
+++ b/src/app/config/confparse.c
@@ -26,12 +26,12 @@
#include "app/config/confparse.h"
#include "feature/nodelist/routerset.h"
+#include "lib/confmgt/unitparse.h"
#include "lib/container/bitarray.h"
#include "lib/encoding/confline.h"
-static uint64_t config_parse_memunit(const char *s, int *ok);
-static int config_parse_msec_interval(const char *s, int *ok);
-static int config_parse_interval(const char *s, int *ok);
+#include "lib/confmgt/typedvar.h"
+
static void config_reset(const config_format_t *fmt, void *options,
const config_var_t *var, int use_defaults);
@@ -162,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;
@@ -170,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) {
-
- 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;
+ 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_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);
}
@@ -317,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 <b>options</b> "fragile", so that fresh assignments
@@ -546,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;
@@ -767,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 <b>var</b> in <b>options</b>. Then if
@@ -1014,193 +719,3 @@ config_dump(const config_format_t *fmt, const void *default_options,
}
return result;
}
-
-/** Mapping from a unit name to a multiplier for converting that unit into a
- * base unit. Used by config_parse_unit. */
-struct unit_table_t {
- const char *unit; /**< The name of the unit */
- uint64_t multiplier; /**< How many of the base unit appear in this unit */
-};
-
-/** Table to map the names of memory units to the number of bytes they
- * contain. */
-static struct unit_table_t memory_units[] = {
- { "", 1 },
- { "b", 1<< 0 },
- { "byte", 1<< 0 },
- { "bytes", 1<< 0 },
- { "kb", 1<<10 },
- { "kbyte", 1<<10 },
- { "kbytes", 1<<10 },
- { "kilobyte", 1<<10 },
- { "kilobytes", 1<<10 },
- { "kilobits", 1<<7 },
- { "kilobit", 1<<7 },
- { "kbits", 1<<7 },
- { "kbit", 1<<7 },
- { "m", 1<<20 },
- { "mb", 1<<20 },
- { "mbyte", 1<<20 },
- { "mbytes", 1<<20 },
- { "megabyte", 1<<20 },
- { "megabytes", 1<<20 },
- { "megabits", 1<<17 },
- { "megabit", 1<<17 },
- { "mbits", 1<<17 },
- { "mbit", 1<<17 },
- { "gb", 1<<30 },
- { "gbyte", 1<<30 },
- { "gbytes", 1<<30 },
- { "gigabyte", 1<<30 },
- { "gigabytes", 1<<30 },
- { "gigabits", 1<<27 },
- { "gigabit", 1<<27 },
- { "gbits", 1<<27 },
- { "gbit", 1<<27 },
- { "tb", UINT64_C(1)<<40 },
- { "tbyte", UINT64_C(1)<<40 },
- { "tbytes", UINT64_C(1)<<40 },
- { "terabyte", UINT64_C(1)<<40 },
- { "terabytes", UINT64_C(1)<<40 },
- { "terabits", UINT64_C(1)<<37 },
- { "terabit", UINT64_C(1)<<37 },
- { "tbits", UINT64_C(1)<<37 },
- { "tbit", UINT64_C(1)<<37 },
- { NULL, 0 },
-};
-
-/** Table to map the names of time units to the number of seconds they
- * contain. */
-static struct unit_table_t time_units[] = {
- { "", 1 },
- { "second", 1 },
- { "seconds", 1 },
- { "minute", 60 },
- { "minutes", 60 },
- { "hour", 60*60 },
- { "hours", 60*60 },
- { "day", 24*60*60 },
- { "days", 24*60*60 },
- { "week", 7*24*60*60 },
- { "weeks", 7*24*60*60 },
- { "month", 2629728, }, /* about 30.437 days */
- { "months", 2629728, },
- { NULL, 0 },
-};
-
-/** Table to map the names of time units to the number of milliseconds
- * they contain. */
-static struct unit_table_t time_msec_units[] = {
- { "", 1 },
- { "msec", 1 },
- { "millisecond", 1 },
- { "milliseconds", 1 },
- { "second", 1000 },
- { "seconds", 1000 },
- { "minute", 60*1000 },
- { "minutes", 60*1000 },
- { "hour", 60*60*1000 },
- { "hours", 60*60*1000 },
- { "day", 24*60*60*1000 },
- { "days", 24*60*60*1000 },
- { "week", 7*24*60*60*1000 },
- { "weeks", 7*24*60*60*1000 },
- { NULL, 0 },
-};
-
-/** Parse a string <b>val</b> containing a number, zero or more
- * spaces, and an optional unit string. If the unit appears in the
- * table <b>u</b>, then multiply the number by the unit multiplier.
- * On success, set *<b>ok</b> to 1 and return this product.
- * Otherwise, set *<b>ok</b> to 0.
- */
-static uint64_t
-config_parse_units(const char *val, struct unit_table_t *u, int *ok)
-{
- uint64_t v = 0;
- double d = 0;
- int use_float = 0;
- char *cp;
-
- tor_assert(ok);
-
- v = tor_parse_uint64(val, 10, 0, UINT64_MAX, ok, &cp);
- if (!*ok || (cp && *cp == '.')) {
- d = tor_parse_double(val, 0, (double)UINT64_MAX, ok, &cp);
- if (!*ok)
- goto done;
- use_float = 1;
- }
-
- if (!cp) {
- *ok = 1;
- v = use_float ? ((uint64_t)d) : v;
- goto done;
- }
-
- cp = (char*) eat_whitespace(cp);
-
- for ( ;u->unit;++u) {
- if (!strcasecmp(u->unit, cp)) {
- if (use_float)
- v = (uint64_t)(u->multiplier * d);
- else
- v *= u->multiplier;
- *ok = 1;
- goto done;
- }
- }
- log_warn(LD_CONFIG, "Unknown unit '%s'.", cp);
- *ok = 0;
- done:
-
- if (*ok)
- return v;
- else
- return 0;
-}
-
-/** Parse a string in the format "number unit", where unit is a unit of
- * information (byte, KB, M, etc). On success, set *<b>ok</b> to true
- * and return the number of bytes specified. Otherwise, set
- * *<b>ok</b> to false and return 0. */
-static uint64_t
-config_parse_memunit(const char *s, int *ok)
-{
- uint64_t u = config_parse_units(s, memory_units, ok);
- return u;
-}
-
-/** Parse a string in the format "number unit", where unit is a unit of
- * time in milliseconds. On success, set *<b>ok</b> to true and return
- * the number of milliseconds in the provided interval. Otherwise, set
- * *<b>ok</b> to 0 and return -1. */
-static int
-config_parse_msec_interval(const char *s, int *ok)
-{
- uint64_t r;
- r = config_parse_units(s, time_msec_units, ok);
- if (r > INT_MAX) {
- log_warn(LD_CONFIG, "Msec interval '%s' is too long", s);
- *ok = 0;
- return -1;
- }
- return (int)r;
-}
-
-/** Parse a string in the format "number unit", where unit is a unit of time.
- * On success, set *<b>ok</b> to true and return the number of seconds in
- * the provided interval. Otherwise, set *<b>ok</b> to 0 and return -1.
- */
-static int
-config_parse_interval(const char *s, int *ok)
-{
- uint64_t r;
- r = config_parse_units(s, time_units, ok);
- if (r > INT_MAX) {
- log_warn(LD_CONFIG, "Interval '%s' is too long", s);
- *ok = 0;
- return -1;
- }
- return (int)r;
-}
diff --git a/src/app/config/confparse.h b/src/app/config/confparse.h
index 2112abf715..bd06a4a0d0 100644
--- a/src/app/config/confparse.h
+++ b/src/app/config/confparse.h
@@ -13,70 +13,7 @@
#ifndef TOR_CONFPARSE_H
#define TOR_CONFPARSE_H
-/** Enumeration of types which option values can take */
-typedef enum config_type_t {
- CONFIG_TYPE_STRING = 0, /**< An arbitrary string. */
- CONFIG_TYPE_FILENAME, /**< A filename: some prefixes get expanded. */
- CONFIG_TYPE_POSINT, /**< A non-negative integer less than MAX_INT */
- CONFIG_TYPE_INT, /**< Any integer. */
- CONFIG_TYPE_UINT64, /**< A value in range 0..UINT64_MAX */
- CONFIG_TYPE_INTERVAL, /**< A number of seconds, with optional units*/
- CONFIG_TYPE_MSEC_INTERVAL,/**< A number of milliseconds, with optional
- * units */
- CONFIG_TYPE_MEMUNIT, /**< A number of bytes, with optional units*/
- CONFIG_TYPE_DOUBLE, /**< A floating-point value */
- CONFIG_TYPE_BOOL, /**< A boolean value, expressed as 0 or 1. */
- CONFIG_TYPE_AUTOBOOL, /**< A boolean+auto value, expressed 0 for false,
- * 1 for true, and -1 for auto */
- CONFIG_TYPE_ISOTIME, /**< An ISO-formatted time relative to UTC. */
- CONFIG_TYPE_CSV, /**< A list of strings, separated by commas and
- * optional whitespace. */
- CONFIG_TYPE_CSV_INTERVAL, /**< A list of strings, separated by commas and
- * optional whitespace, representing intervals in
- * seconds, with optional units. We allow
- * multiple values here for legacy reasons, but
- * ignore every value after the first. */
- CONFIG_TYPE_LINELIST, /**< Uninterpreted config lines */
- CONFIG_TYPE_LINELIST_S, /**< Uninterpreted, context-sensitive config lines,
- * mixed with other keywords. */
- CONFIG_TYPE_LINELIST_V, /**< Catch-all "virtual" option to summarize
- * context-sensitive config lines when fetching.
- */
- CONFIG_TYPE_ROUTERSET, /**< A list of router names, addrs, and fps,
- * parsed into a routerset_t. */
- CONFIG_TYPE_OBSOLETE, /**< Obsolete (ignored) option. */
-} config_type_t;
-
-#ifdef TOR_UNIT_TESTS
-/**
- * Union used when building in test mode typechecking the members of a type
- * used with confparse.c. See CONF_CHECK_VAR_TYPE for a description of how
- * it is used. */
-typedef union {
- char **STRING;
- char **FILENAME;
- int *POSINT; /* yes, really: Even though the confparse type is called
- * "POSINT", it still uses the C int type -- it just enforces that
- * the values are in range [0,INT_MAX].
- */
- uint64_t *UINT64;
- int *INT;
- int *PORT;
- int *INTERVAL;
- int *MSEC_INTERVAL;
- uint64_t *MEMUNIT;
- double *DOUBLE;
- int *BOOL;
- int *AUTOBOOL;
- time_t *ISOTIME;
- smartlist_t **CSV;
- int *CSV_INTERVAL;
- struct config_line_t **LINELIST;
- struct config_line_t **LINELIST_S;
- struct config_line_t **LINELIST_V;
- routerset_t **ROUTERSET;
-} confparse_dummy_values_t;
-#endif /* defined(TOR_UNIT_TESTS) */
+#include "lib/conf/conftypes.h"
/** An abbreviation for a configuration option allowed on the command line. */
typedef struct config_abbrev_t {
diff --git a/src/include.am b/src/include.am
index 77c126ba45..065bdc31cb 100644
--- a/src/include.am
+++ b/src/include.am
@@ -5,6 +5,8 @@ include src/lib/err/include.am
include src/lib/cc/include.am
include src/lib/ctime/include.am
include src/lib/compress/include.am
+include src/lib/conf/include.am
+include src/lib/confmgt/include.am
include src/lib/container/include.am
include src/lib/crypt_ops/include.am
include src/lib/defs/include.am
diff --git a/src/lib/conf/.may_include b/src/lib/conf/.may_include
new file mode 100644
index 0000000000..4285c3dcb8
--- /dev/null
+++ b/src/lib/conf/.may_include
@@ -0,0 +1,2 @@
+orconfig.h
+lib/cc/*.h
diff --git a/src/lib/conf/conftypes.h b/src/lib/conf/conftypes.h
new file mode 100644
index 0000000000..e66ab3d5aa
--- /dev/null
+++ b/src/lib/conf/conftypes.h
@@ -0,0 +1,99 @@
+/* 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 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
+#define TOR_SRC_LIB_CONF_CONFTYPES_H
+
+#include "lib/cc/torint.h"
+
+/** Enumeration of types which option values can take */
+typedef enum config_type_t {
+ CONFIG_TYPE_STRING = 0, /**< An arbitrary string. */
+ CONFIG_TYPE_FILENAME, /**< A filename: some prefixes get expanded. */
+ CONFIG_TYPE_POSINT, /**< A non-negative integer less than MAX_INT */
+ CONFIG_TYPE_INT, /**< Any integer. */
+ CONFIG_TYPE_UINT64, /**< A value in range 0..UINT64_MAX */
+ CONFIG_TYPE_INTERVAL, /**< A number of seconds, with optional units*/
+ CONFIG_TYPE_MSEC_INTERVAL,/**< A number of milliseconds, with optional
+ * units */
+ CONFIG_TYPE_MEMUNIT, /**< A number of bytes, with optional units*/
+ CONFIG_TYPE_DOUBLE, /**< A floating-point value */
+ CONFIG_TYPE_BOOL, /**< A boolean value, expressed as 0 or 1. */
+ CONFIG_TYPE_AUTOBOOL, /**< A boolean+auto value, expressed 0 for false,
+ * 1 for true, and -1 for auto */
+ CONFIG_TYPE_ISOTIME, /**< An ISO-formatted time relative to UTC. */
+ CONFIG_TYPE_CSV, /**< A list of strings, separated by commas and
+ * optional whitespace. */
+ CONFIG_TYPE_CSV_INTERVAL, /**< A list of strings, separated by commas and
+ * optional whitespace, representing intervals in
+ * seconds, with optional units. We allow
+ * multiple values here for legacy reasons, but
+ * ignore every value after the first. */
+ CONFIG_TYPE_LINELIST, /**< Uninterpreted config lines */
+ CONFIG_TYPE_LINELIST_S, /**< Uninterpreted, context-sensitive config lines,
+ * mixed with other keywords. */
+ CONFIG_TYPE_LINELIST_V, /**< Catch-all "virtual" option to summarize
+ * context-sensitive config lines when fetching.
+ */
+ // XXXX this doesn't belong at this level of abstraction.
+ CONFIG_TYPE_ROUTERSET, /**< A list of router names, addrs, and fps,
+ * parsed into a routerset_t. */
+ CONFIG_TYPE_OBSOLETE, /**< Obsolete (ignored) option. */
+} config_type_t;
+
+#ifdef TOR_UNIT_TESTS
+/**
+ * Union used when building in test mode typechecking the members of a type
+ * used with confparse.c. See CONF_CHECK_VAR_TYPE for a description of how
+ * it is used. */
+typedef union {
+ char **STRING;
+ char **FILENAME;
+ int *POSINT; /* yes, this is really an int, and not an unsigned int. For
+ * historical reasons, many configuration values are restricted
+ * to the range [0,INT_MAX], and stored in signed ints.
+ */
+ uint64_t *UINT64;
+ int *INT;
+ int *INTERVAL;
+ int *MSEC_INTERVAL;
+ uint64_t *MEMUNIT;
+ double *DOUBLE;
+ int *BOOL;
+ int *AUTOBOOL;
+ time_t *ISOTIME;
+ struct smartlist_t **CSV;
+ int *CSV_INTERVAL;
+ struct config_line_t **LINELIST;
+ struct config_line_t **LINELIST_S;
+ struct config_line_t **LINELIST_V;
+ // XXXX this doesn't belong at this level of abstraction.
+ struct routerset_t **ROUTERSET;
+} confparse_dummy_values_t;
+#endif /* defined(TOR_UNIT_TESTS) */
+
+#endif /* !defined(TOR_SRC_LIB_CONF_CONFTYPES_H) */
diff --git a/src/lib/conf/include.am b/src/lib/conf/include.am
new file mode 100644
index 0000000000..25355697d2
--- /dev/null
+++ b/src/lib/conf/include.am
@@ -0,0 +1,4 @@
+
+# ADD_C_FILE: INSERT HEADERS HERE.
+noinst_HEADERS += \
+ src/lib/conf/conftypes.h
diff --git a/src/lib/confmgt/.may_include b/src/lib/confmgt/.may_include
new file mode 100644
index 0000000000..d85dbf6904
--- /dev/null
+++ b/src/lib/confmgt/.may_include
@@ -0,0 +1,9 @@
+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
new file mode 100644
index 0000000000..a2c7649957
--- /dev/null
+++ b/src/lib/confmgt/include.am
@@ -0,0 +1,23 @@
+noinst_LIBRARIES += src/lib/libtor-confmgt.a
+
+if UNITTESTS_ENABLED
+noinst_LIBRARIES += src/lib/libtor-confmgt-testing.a
+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 = \
+ $(src_lib_libtor_confmgt_a_SOURCES)
+src_lib_libtor_confmgt_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS)
+src_lib_libtor_confmgt_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
+
+# ADD_C_FILE: INSERT HEADERS HERE.
+noinst_HEADERS += \
+ 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 <stddef.h>
+#include <string.h>
+
+//////
+// 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 <stddef.h>
+#include <string.h>
+
+/**
+ * Try to parse a string in <b>value</b> that encodes an object of the type
+ * defined by <b>def</b>.
+ *
+ * On success, adjust the lvalue pointed to by <b>target</b> to hold that
+ * value, and return 0. On failure, set *<b>errmsg</b> 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 of<b>line</b> that encodes an
+ * object of the type defined in <b>def</b>. 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 <b>line</b>,
+ * 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 <b>target</b> of type defined by
+ * <b>def</b>, and set <b>target</b> 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 <b>def</b> pointed to by <b>value</b>, 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 <b>key</b> 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 <b>dest</b> to contain the same value as <b>src</b>. Both types
+ * must be as defined by <b>def</b>.
+ *
+ * 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 <b>a</b> and <b>b</b> are semantically equivalent.
+ * Both types must be as defined by <b>def</b>.
+ **/
+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 <b>value</b> encodes a valid value according to the
+ * type definition in <b>def</b>.
+ */
+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 <stdbool.h>
+
+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/unitparse.c b/src/lib/confmgt/unitparse.c
new file mode 100644
index 0000000000..c3ed8285a4
--- /dev/null
+++ b/src/lib/confmgt/unitparse.c
@@ -0,0 +1,206 @@
+/* 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 unitparse.c
+ * @brief Functions for parsing values with units from a configuration file.
+ **/
+
+#include "orconfig.h"
+#include "lib/confmgt/unitparse.h"
+#include "lib/log/log.h"
+#include "lib/log/util_bug.h"
+#include "lib/string/parse_int.h"
+#include "lib/string/util_string.h"
+
+#include <string.h>
+
+/** Table to map the names of memory units to the number of bytes they
+ * contain. */
+const struct unit_table_t memory_units[] = {
+ { "", 1 },
+ { "b", 1<< 0 },
+ { "byte", 1<< 0 },
+ { "bytes", 1<< 0 },
+ { "kb", 1<<10 },
+ { "kbyte", 1<<10 },
+ { "kbytes", 1<<10 },
+ { "kilobyte", 1<<10 },
+ { "kilobytes", 1<<10 },
+ { "kilobits", 1<<7 },
+ { "kilobit", 1<<7 },
+ { "kbits", 1<<7 },
+ { "kbit", 1<<7 },
+ { "m", 1<<20 },
+ { "mb", 1<<20 },
+ { "mbyte", 1<<20 },
+ { "mbytes", 1<<20 },
+ { "megabyte", 1<<20 },
+ { "megabytes", 1<<20 },
+ { "megabits", 1<<17 },
+ { "megabit", 1<<17 },
+ { "mbits", 1<<17 },
+ { "mbit", 1<<17 },
+ { "gb", 1<<30 },
+ { "gbyte", 1<<30 },
+ { "gbytes", 1<<30 },
+ { "gigabyte", 1<<30 },
+ { "gigabytes", 1<<30 },
+ { "gigabits", 1<<27 },
+ { "gigabit", 1<<27 },
+ { "gbits", 1<<27 },
+ { "gbit", 1<<27 },
+ { "tb", UINT64_C(1)<<40 },
+ { "tbyte", UINT64_C(1)<<40 },
+ { "tbytes", UINT64_C(1)<<40 },
+ { "terabyte", UINT64_C(1)<<40 },
+ { "terabytes", UINT64_C(1)<<40 },
+ { "terabits", UINT64_C(1)<<37 },
+ { "terabit", UINT64_C(1)<<37 },
+ { "tbits", UINT64_C(1)<<37 },
+ { "tbit", UINT64_C(1)<<37 },
+ { NULL, 0 },
+};
+
+/** Table to map the names of time units to the number of seconds they
+ * contain. */
+const struct unit_table_t time_units[] = {
+ { "", 1 },
+ { "second", 1 },
+ { "seconds", 1 },
+ { "minute", 60 },
+ { "minutes", 60 },
+ { "hour", 60*60 },
+ { "hours", 60*60 },
+ { "day", 24*60*60 },
+ { "days", 24*60*60 },
+ { "week", 7*24*60*60 },
+ { "weeks", 7*24*60*60 },
+ { "month", 2629728, }, /* about 30.437 days */
+ { "months", 2629728, },
+ { NULL, 0 },
+};
+
+/** Table to map the names of time units to the number of milliseconds
+ * they contain. */
+const struct unit_table_t time_msec_units[] = {
+ { "", 1 },
+ { "msec", 1 },
+ { "millisecond", 1 },
+ { "milliseconds", 1 },
+ { "second", 1000 },
+ { "seconds", 1000 },
+ { "minute", 60*1000 },
+ { "minutes", 60*1000 },
+ { "hour", 60*60*1000 },
+ { "hours", 60*60*1000 },
+ { "day", 24*60*60*1000 },
+ { "days", 24*60*60*1000 },
+ { "week", 7*24*60*60*1000 },
+ { "weeks", 7*24*60*60*1000 },
+ { NULL, 0 },
+};
+
+/** Parse a string <b>val</b> containing a number, zero or more
+ * spaces, and an optional unit string. If the unit appears in the
+ * table <b>u</b>, then multiply the number by the unit multiplier.
+ * On success, set *<b>ok</b> to 1 and return this product.
+ * Otherwise, set *<b>ok</b> to 0.
+ */
+uint64_t
+config_parse_units(const char *val, const unit_table_t *u, int *ok)
+{
+ uint64_t v = 0;
+ double d = 0;
+ int use_float = 0;
+ char *cp;
+
+ tor_assert(ok);
+
+ v = tor_parse_uint64(val, 10, 0, UINT64_MAX, ok, &cp);
+ if (!*ok || (cp && *cp == '.')) {
+ d = tor_parse_double(val, 0, (double)UINT64_MAX, ok, &cp);
+ if (!*ok)
+ goto done;
+ use_float = 1;
+ }
+
+ if (BUG(!cp)) {
+ // cp should always be non-NULL if the parse operation succeeds.
+
+ // LCOV_EXCL_START
+ *ok = 1;
+ v = use_float ? ((uint64_t)d) : v;
+ goto done;
+ // LCOV_EXCL_STOP
+ }
+
+ cp = (char*) eat_whitespace(cp);
+
+ for ( ;u->unit;++u) {
+ if (!strcasecmp(u->unit, cp)) {
+ if (use_float)
+ v = (uint64_t)(u->multiplier * d);
+ else
+ v *= u->multiplier;
+ *ok = 1;
+ goto done;
+ }
+ }
+ log_warn(LD_CONFIG, "Unknown unit '%s'.", cp);
+ *ok = 0;
+ done:
+
+ if (*ok)
+ return v;
+ else
+ return 0;
+}
+
+/** Parse a string in the format "number unit", where unit is a unit of
+ * information (byte, KB, M, etc). On success, set *<b>ok</b> to true
+ * and return the number of bytes specified. Otherwise, set
+ * *<b>ok</b> to false and return 0. */
+uint64_t
+config_parse_memunit(const char *s, int *ok)
+{
+ uint64_t u = config_parse_units(s, memory_units, ok);
+ return u;
+}
+
+/** Parse a string in the format "number unit", where unit is a unit of
+ * time in milliseconds. On success, set *<b>ok</b> to true and return
+ * the number of milliseconds in the provided interval. Otherwise, set
+ * *<b>ok</b> to 0 and return -1. */
+int
+config_parse_msec_interval(const char *s, int *ok)
+{
+ uint64_t r;
+ r = config_parse_units(s, time_msec_units, ok);
+ if (r > INT_MAX) {
+ log_warn(LD_CONFIG, "Msec interval '%s' is too long", s);
+ *ok = 0;
+ return -1;
+ }
+ return (int)r;
+}
+
+/** Parse a string in the format "number unit", where unit is a unit of time.
+ * On success, set *<b>ok</b> to true and return the number of seconds in
+ * the provided interval. Otherwise, set *<b>ok</b> to 0 and return -1.
+ */
+int
+config_parse_interval(const char *s, int *ok)
+{
+ uint64_t r;
+ r = config_parse_units(s, time_units, ok);
+ if (r > INT_MAX) {
+ log_warn(LD_CONFIG, "Interval '%s' is too long", s);
+ *ok = 0;
+ return -1;
+ }
+ return (int)r;
+}
diff --git a/src/lib/confmgt/unitparse.h b/src/lib/confmgt/unitparse.h
new file mode 100644
index 0000000000..216361a7d4
--- /dev/null
+++ b/src/lib/confmgt/unitparse.h
@@ -0,0 +1,34 @@
+/* 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 unitparse.h
+ * @brief Header for lib/confmgt/unitparse.c
+ **/
+
+#ifndef TOR_LIB_CONFMGT_UNITPARSE_H
+#define TOR_LIB_CONFMGT_UNITPARSE_H
+
+#include <lib/cc/torint.h>
+
+/** Mapping from a unit name to a multiplier for converting that unit into a
+ * base unit. Used by config_parse_unit. */
+typedef struct unit_table_t {
+ const char *unit; /**< The name of the unit */
+ uint64_t multiplier; /**< How many of the base unit appear in this unit */
+} unit_table_t;
+
+extern const unit_table_t memory_units[];
+extern const unit_table_t time_units[];
+extern const struct unit_table_t time_msec_units[];
+
+uint64_t config_parse_units(const char *val, const unit_table_t *u, int *ok);
+
+uint64_t config_parse_memunit(const char *s, int *ok);
+int config_parse_msec_interval(const char *s, int *ok);
+int config_parse_interval(const char *s, int *ok);
+
+#endif /* !defined(TOR_LIB_CONFMGT_UNITPARSE_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..d142ee1104
--- /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 var_type_def_st.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 <stdbool.h>
+
+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 <b>params</b> 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 <b>value</b> that encodes an object of this
+ * type. On success, adjust the lvalue pointed to by <b>target</b> to hold
+ * that value, and return 0. On failure, set *<b>errmsg</b> 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 of<b>line</b> 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 <b>line</b>,
+ * 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 <b>value</b> 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 <b>key</b> 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 <b>value</b> 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 <b>arg</b>, and set <b>arg</b> 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 <b>a</b> and <b>b</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 <b>value</b> into <b>target</b>.
+ * 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 <b>value</b> 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/lib/encoding/confline.c b/src/lib/encoding/confline.c
index fdb575e03f..0d8384db13 100644
--- a/src/lib/encoding/confline.c
+++ b/src/lib/encoding/confline.c
@@ -256,7 +256,7 @@ config_lines_dup_and_filter(const config_line_t *inp,
/** Return true iff a and b contain identical keys and values in identical
* order. */
int
-config_lines_eq(config_line_t *a, config_line_t *b)
+config_lines_eq(const config_line_t *a, const config_line_t *b)
{
while (a && b) {
if (strcasecmp(a->key, b->key) || strcmp(a->value, b->value))
diff --git a/src/lib/encoding/confline.h b/src/lib/encoding/confline.h
index 56ea36bf61..12c554c6e7 100644
--- a/src/lib/encoding/confline.h
+++ b/src/lib/encoding/confline.h
@@ -50,7 +50,7 @@ const config_line_t *config_line_find(const config_line_t *lines,
const char *key);
const config_line_t *config_line_find_case(const config_line_t *lines,
const char *key);
-int config_lines_eq(config_line_t *a, config_line_t *b);
+int config_lines_eq(const config_line_t *a, const config_line_t *b);
int config_count_key(const config_line_t *a, const char *key);
void config_free_lines_(config_line_t *front);
#define config_free_lines(front) \
diff --git a/src/test/test_confparse.c b/src/test/test_confparse.c
index 89a6eb5265..dde61b1c81 100644
--- a/src/test/test_confparse.c
+++ b/src/test/test_confparse.c
@@ -18,6 +18,8 @@
#include "test/test.h"
#include "test/log_test_helpers.h"
+#include "lib/confmgt/unitparse.h"
+
typedef struct test_struct_t {
uint32_t magic;
char *s;
@@ -469,9 +471,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" };
@@ -805,6 +807,65 @@ test_confparse_extra_lines(void *arg)
config_free(&etest_fmt, tst);
}
+static void
+test_confparse_unitparse(void *args)
+{
+ (void)args;
+ /* spot-check a few memunit values. */
+ int ok = 3;
+ tt_u64_op(config_parse_memunit("100 MB", &ok), OP_EQ, 100<<20);
+ tt_assert(ok);
+ tt_u64_op(config_parse_memunit("100 TB", &ok), OP_EQ, UINT64_C(100)<<40);
+ tt_assert(ok);
+ // This is a floating-point value, but note that 1.5 can be represented
+ // precisely.
+ tt_u64_op(config_parse_memunit("1.5 MB", &ok), OP_EQ, 3<<19);
+ tt_assert(ok);
+
+ /* Try some good intervals and msec intervals */
+ tt_int_op(config_parse_interval("2 days", &ok), OP_EQ, 48*3600);
+ tt_assert(ok);
+ tt_int_op(config_parse_interval("1.5 hour", &ok), OP_EQ, 5400);
+ tt_assert(ok);
+ tt_u64_op(config_parse_interval("1 minute", &ok), OP_EQ, 60);
+ tt_assert(ok);
+ tt_int_op(config_parse_msec_interval("2 days", &ok), OP_EQ, 48*3600*1000);
+ tt_assert(ok);
+ tt_int_op(config_parse_msec_interval("10 msec", &ok), OP_EQ, 10);
+ tt_assert(ok);
+
+ /* Try a couple of unitless values. */
+ tt_int_op(config_parse_interval("10", &ok), OP_EQ, 10);
+ tt_assert(ok);
+ tt_u64_op(config_parse_interval("15.0", &ok), OP_EQ, 15);
+ tt_assert(ok);
+
+ /* u64 overflow */
+ /* XXXX our implementation does not currently detect this. See bug 30920. */
+ /*
+ tt_u64_op(config_parse_memunit("20000000 TB", &ok), OP_EQ, 0);
+ tt_assert(!ok);
+ */
+
+ /* i32 overflow */
+ tt_int_op(config_parse_interval("1000 months", &ok), OP_EQ, -1);
+ tt_assert(!ok);
+ tt_int_op(config_parse_msec_interval("4 weeks", &ok), OP_EQ, -1);
+ tt_assert(!ok);
+
+ /* bad units */
+ tt_u64_op(config_parse_memunit("7 nybbles", &ok), OP_EQ, 0);
+ tt_assert(!ok);
+ // XXXX these next two should return -1 according to the documentation.
+ tt_int_op(config_parse_interval("7 cowznofski", &ok), OP_EQ, 0);
+ tt_assert(!ok);
+ tt_int_op(config_parse_msec_interval("1 kalpa", &ok), OP_EQ, 0);
+ tt_assert(!ok);
+
+ done:
+ ;
+}
+
#define CONFPARSE_TEST(name, flags) \
{ #name, test_confparse_ ## name, flags, NULL, NULL }
@@ -838,5 +899,6 @@ struct testcase_t confparse_tests[] = {
CONFPARSE_TEST(reassign_extend, 0),
CONFPARSE_TEST(get_assigned, 0),
CONFPARSE_TEST(extra_lines, 0),
+ CONFPARSE_TEST(unitparse, 0),
END_OF_TESTCASES
};
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);