unitparse.c (6998B)
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 */ 6 7 /** 8 * @file unitparse.c 9 * @brief Functions for parsing values with units from a configuration file. 10 **/ 11 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" 21 22 #include <string.h> 23 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 }, 70 }; 71 // clang-format on 72 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 }, 91 }; 92 // clang-format on 93 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 }, 113 }; 114 // clang-format on 115 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. 121 * 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. 125 */ 126 uint64_t 127 config_parse_units(const char *val, const unit_table_t *u, int *ok, 128 char **errmsg_out) 129 { 130 uint64_t v = 0; 131 double d = 0; 132 int use_float = 0; 133 char *cp; 134 char *errmsg = NULL; 135 136 tor_assert(ok); 137 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; 144 } 145 use_float = 1; 146 } 147 148 if (BUG(!cp)) { 149 // cp should always be non-NULL if the parse operation succeeds. 150 151 // LCOV_EXCL_START 152 *ok = 1; 153 v = use_float ? ((uint64_t)d) : v; 154 goto done; 155 // LCOV_EXCL_STOP 156 } 157 158 cp = (char*) eat_whitespace(cp); 159 160 for ( ;u->unit;++u) { 161 if (!strcasecmp(u->unit, cp)) { 162 if (use_float) { 163 d = u->multiplier * d; 164 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; 170 } 171 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; 179 } 180 181 v = (uint64_t) d; 182 } else { 183 v = tor_mul_u64_nowrap(v, u->multiplier); 184 185 if (v > INT64_MAX) { 186 tor_asprintf(&errmsg, "Overflow while parsing %s %s", 187 val, u->unit); 188 *ok = 0; 189 goto done; 190 } 191 } 192 193 *ok = 1; 194 goto done; 195 } 196 } 197 tor_asprintf(&errmsg, "Unknown unit in %s", val); 198 *ok = 0; 199 done: 200 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); 208 } 209 } 210 211 if (*ok) 212 return v; 213 else 214 return 0; 215 } 216 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) 223 { 224 uint64_t u = config_parse_units(s, memory_units, ok, NULL); 225 return u; 226 } 227 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. */ 232 int 233 config_parse_msec_interval(const char *s, int *ok) 234 { 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; 241 } 242 return (int)r; 243 } 244 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. 248 */ 249 int 250 config_parse_interval(const char *s, int *ok) 251 { 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; 258 } 259 return (int)r; 260 }