Commit c60a85d2 authored by Nick Mathewson's avatar Nick Mathewson 👻
Browse files

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.
parent 5a2ab886
......@@ -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 \
......
o Code simplification and refactoring:
- Extract our variable manipulation code from confparse.c to a new
lower-level typedvar.h module. Closes ticket 30864.
......@@ -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,
......
......@@ -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) {
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;
}
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_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 <b>options</b> "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 <b>var</b> in <b>options</b>. Then if
......
......@@ -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
......
......@@ -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
......@@ -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
/* 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;