Merge branch 'tor-gitlab/mr/583' into maint-0.4.7
[tor.git] / src / lib / confmgt / unitparse.c
bloba0cfacaab2b46a2fee019f009f195c0188005563
1 /* Copyright (c) 2001 Matej Pfajfar.
2 * Copyright (c) 2001-2004, Roger Dingledine.
3 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
4 * Copyright (c) 2007-2021, The Tor Project, Inc. */
5 /* See LICENSE for licensing information */
7 /**
8 * @file unitparse.c
9 * @brief Functions for parsing values with units from a configuration file.
10 **/
12 #include "orconfig.h"
13 #include "lib/confmgt/unitparse.h"
14 #include "lib/log/log.h"
15 #include "lib/log/util_bug.h"
16 #include "lib/malloc/malloc.h"
17 #include "lib/string/parse_int.h"
18 #include "lib/string/printf.h"
19 #include "lib/string/util_string.h"
20 #include "lib/intmath/muldiv.h"
22 #include <string.h>
24 /** Table to map the names of memory units to the number of bytes they
25 * contain. */
26 // clang-format off
27 const struct unit_table_t memory_units[] = {
28 { "", 1 },
29 { "b", 1<< 0 },
30 { "byte", 1<< 0 },
31 { "bytes", 1<< 0 },
32 { "kb", 1<<10 },
33 { "kbyte", 1<<10 },
34 { "kbytes", 1<<10 },
35 { "kilobyte", 1<<10 },
36 { "kilobytes", 1<<10 },
37 { "kilobits", 1<<7 },
38 { "kilobit", 1<<7 },
39 { "kbits", 1<<7 },
40 { "kbit", 1<<7 },
41 { "m", 1<<20 },
42 { "mb", 1<<20 },
43 { "mbyte", 1<<20 },
44 { "mbytes", 1<<20 },
45 { "megabyte", 1<<20 },
46 { "megabytes", 1<<20 },
47 { "megabits", 1<<17 },
48 { "megabit", 1<<17 },
49 { "mbits", 1<<17 },
50 { "mbit", 1<<17 },
51 { "gb", 1<<30 },
52 { "gbyte", 1<<30 },
53 { "gbytes", 1<<30 },
54 { "gigabyte", 1<<30 },
55 { "gigabytes", 1<<30 },
56 { "gigabits", 1<<27 },
57 { "gigabit", 1<<27 },
58 { "gbits", 1<<27 },
59 { "gbit", 1<<27 },
60 { "tb", UINT64_C(1)<<40 },
61 { "tbyte", UINT64_C(1)<<40 },
62 { "tbytes", UINT64_C(1)<<40 },
63 { "terabyte", UINT64_C(1)<<40 },
64 { "terabytes", UINT64_C(1)<<40 },
65 { "terabits", UINT64_C(1)<<37 },
66 { "terabit", UINT64_C(1)<<37 },
67 { "tbits", UINT64_C(1)<<37 },
68 { "tbit", UINT64_C(1)<<37 },
69 { NULL, 0 },
71 // clang-format on
73 /** Table to map the names of time units to the number of seconds they
74 * contain. */
75 // clang-format off
76 const struct unit_table_t time_units[] = {
77 { "", 1 },
78 { "second", 1 },
79 { "seconds", 1 },
80 { "minute", 60 },
81 { "minutes", 60 },
82 { "hour", 60*60 },
83 { "hours", 60*60 },
84 { "day", 24*60*60 },
85 { "days", 24*60*60 },
86 { "week", 7*24*60*60 },
87 { "weeks", 7*24*60*60 },
88 { "month", 2629728, }, /* about 30.437 days */
89 { "months", 2629728, },
90 { NULL, 0 },
92 // clang-format on
94 /** Table to map the names of time units to the number of milliseconds
95 * they contain. */
96 // clang-format off
97 const struct unit_table_t time_msec_units[] = {
98 { "", 1 },
99 { "msec", 1 },
100 { "millisecond", 1 },
101 { "milliseconds", 1 },
102 { "second", 1000 },
103 { "seconds", 1000 },
104 { "minute", 60*1000 },
105 { "minutes", 60*1000 },
106 { "hour", 60*60*1000 },
107 { "hours", 60*60*1000 },
108 { "day", 24*60*60*1000 },
109 { "days", 24*60*60*1000 },
110 { "week", 7*24*60*60*1000 },
111 { "weeks", 7*24*60*60*1000 },
112 { NULL, 0 },
114 // clang-format on
116 /** Parse a string <b>val</b> containing a number, zero or more
117 * spaces, and an optional unit string. If the unit appears in the
118 * table <b>u</b>, then multiply the number by the unit multiplier.
119 * On success, set *<b>ok</b> to 1 and return this product.
120 * Otherwise, set *<b>ok</b> to 0.
122 * If an error (like overflow or a negative value is detected), put an error
123 * message in *<b>errmsg_out</b> if that pointer is non-NULL, and otherwise
124 * log a warning.
126 uint64_t
127 config_parse_units(const char *val, const unit_table_t *u, int *ok,
128 char **errmsg_out)
130 uint64_t v = 0;
131 double d = 0;
132 int use_float = 0;
133 char *cp;
134 char *errmsg = NULL;
136 tor_assert(ok);
138 v = tor_parse_uint64(val, 10, 0, UINT64_MAX, ok, &cp);
139 if (!*ok || (cp && *cp == '.')) {
140 d = tor_parse_double(val, 0, (double)UINT64_MAX, ok, &cp);
141 if (!*ok) {
142 tor_asprintf(&errmsg, "Unable to parse %s as a number", val);
143 goto done;
145 use_float = 1;
148 if (BUG(!cp)) {
149 // cp should always be non-NULL if the parse operation succeeds.
151 // LCOV_EXCL_START
152 *ok = 1;
153 v = use_float ? ((uint64_t)d) : v;
154 goto done;
155 // LCOV_EXCL_STOP
158 cp = (char*) eat_whitespace(cp);
160 for ( ;u->unit;++u) {
161 if (!strcasecmp(u->unit, cp)) {
162 if (use_float) {
163 d = u->multiplier * d;
165 if (d < 0) {
166 tor_asprintf(&errmsg, "Got a negative value while parsing %s %s",
167 val, u->unit);
168 *ok = 0;
169 goto done;
172 // Some compilers may warn about casting a double to an unsigned type
173 // because they don't know if d is >= 0
174 if (d >= 0 && (d > (double)INT64_MAX || (uint64_t)d > INT64_MAX)) {
175 tor_asprintf(&errmsg, "Overflow while parsing %s %s",
176 val, u->unit);
177 *ok = 0;
178 goto done;
181 v = (uint64_t) d;
182 } else {
183 v = tor_mul_u64_nowrap(v, u->multiplier);
185 if (v > INT64_MAX) {
186 tor_asprintf(&errmsg, "Overflow while parsing %s %s",
187 val, u->unit);
188 *ok = 0;
189 goto done;
193 *ok = 1;
194 goto done;
197 tor_asprintf(&errmsg, "Unknown unit in %s", val);
198 *ok = 0;
199 done:
201 if (errmsg) {
202 tor_assert_nonfatal(!*ok);
203 if (errmsg_out) {
204 *errmsg_out = errmsg;
205 } else {
206 log_warn(LD_CONFIG, "%s", errmsg);
207 tor_free(errmsg);
211 if (*ok)
212 return v;
213 else
214 return 0;
217 /** Parse a string in the format "number unit", where unit is a unit of
218 * information (byte, KB, M, etc). On success, set *<b>ok</b> to true
219 * and return the number of bytes specified. Otherwise, set
220 * *<b>ok</b> to false and return 0. */
221 uint64_t
222 config_parse_memunit(const char *s, int *ok)
224 uint64_t u = config_parse_units(s, memory_units, ok, NULL);
225 return u;
228 /** Parse a string in the format "number unit", where unit is a unit of
229 * time in milliseconds. On success, set *<b>ok</b> to true and return
230 * the number of milliseconds in the provided interval. Otherwise, set
231 * *<b>ok</b> to 0 and return -1. */
233 config_parse_msec_interval(const char *s, int *ok)
235 uint64_t r;
236 r = config_parse_units(s, time_msec_units, ok, NULL);
237 if (r > INT_MAX) {
238 log_warn(LD_CONFIG, "Msec interval '%s' is too long", s);
239 *ok = 0;
240 return -1;
242 return (int)r;
245 /** Parse a string in the format "number unit", where unit is a unit of time.
246 * On success, set *<b>ok</b> to true and return the number of seconds in
247 * the provided interval. Otherwise, set *<b>ok</b> to 0 and return -1.
250 config_parse_interval(const char *s, int *ok)
252 uint64_t r;
253 r = config_parse_units(s, time_units, ok, NULL);
254 if (r > INT_MAX) {
255 log_warn(LD_CONFIG, "Interval '%s' is too long", s);
256 *ok = 0;
257 return -1;
259 return (int)r;