diff options
Diffstat (limited to 'src/lib/confmgt/unitparse.c')
-rw-r--r-- | src/lib/confmgt/unitparse.c | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/src/lib/confmgt/unitparse.c b/src/lib/confmgt/unitparse.c new file mode 100644 index 0000000000..99716e8d9d --- /dev/null +++ b/src/lib/confmgt/unitparse.c @@ -0,0 +1,260 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, 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/malloc/malloc.h" +#include "lib/string/parse_int.h" +#include "lib/string/printf.h" +#include "lib/string/util_string.h" +#include "lib/intmath/muldiv.h" + +#include <string.h> + +/** Table to map the names of memory units to the number of bytes they + * contain. */ +// clang-format off +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 }, +}; +// clang-format on + +/** Table to map the names of time units to the number of seconds they + * contain. */ +// clang-format off +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 }, +}; +// clang-format on + +/** Table to map the names of time units to the number of milliseconds + * they contain. */ +// clang-format off +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 }, +}; +// clang-format on + +/** 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. + * + * If an error (like overflow or a negative value is detected), put an error + * message in *<b>errmsg_out</b> if that pointer is non-NULL, and otherwise + * log a warning. + */ +uint64_t +config_parse_units(const char *val, const unit_table_t *u, int *ok, + char **errmsg_out) +{ + uint64_t v = 0; + double d = 0; + int use_float = 0; + char *cp; + char *errmsg = NULL; + + 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) { + tor_asprintf(&errmsg, "Unable to parse %s as a number", val); + 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) { + d = u->multiplier * d; + + if (d < 0) { + tor_asprintf(&errmsg, "Got a negative value while parsing %s %s", + val, u->unit); + *ok = 0; + goto done; + } + + // Some compilers may warn about casting a double to an unsigned type + // because they don't know if d is >= 0 + if (d >= 0 && (d > (double)INT64_MAX || (uint64_t)d > INT64_MAX)) { + tor_asprintf(&errmsg, "Overflow while parsing %s %s", + val, u->unit); + *ok = 0; + goto done; + } + + v = (uint64_t) d; + } else { + v = tor_mul_u64_nowrap(v, u->multiplier); + + if (v > INT64_MAX) { + tor_asprintf(&errmsg, "Overflow while parsing %s %s", + val, u->unit); + *ok = 0; + goto done; + } + } + + *ok = 1; + goto done; + } + } + tor_asprintf(&errmsg, "Unknown unit in %s", val); + *ok = 0; + done: + + if (errmsg) { + tor_assert_nonfatal(!*ok); + if (errmsg_out) { + *errmsg_out = errmsg; + } else { + log_warn(LD_CONFIG, "%s", errmsg); + tor_free(errmsg); + } + } + + 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, NULL); + 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, NULL); + 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, NULL); + if (r > INT_MAX) { + log_warn(LD_CONFIG, "Interval '%s' is too long", s); + *ok = 0; + return -1; + } + return (int)r; +} |