1 /** @file
2 Option parsing functions to complement getopt.
3
4 Copyright (C) 2017 Christian Zuckschwerdt
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 */
11
12 #include "optparse.h"
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <limits.h>
16 #include <string.h>
17
tls_param(tls_opts_t * tls_opts,char const * key,char const * val)18 int tls_param(tls_opts_t *tls_opts, char const *key, char const *val)
19 {
20 if (!tls_opts || !key || !*key)
21 return 1;
22 else if (!strcasecmp(key, "tls_cert"))
23 tls_opts->tls_cert = val;
24 else if (!strcasecmp(key, "tls_key"))
25 tls_opts->tls_key = val;
26 else if (!strcasecmp(key, "tls_ca_cert"))
27 tls_opts->tls_ca_cert = val;
28 else if (!strcasecmp(key, "tls_cipher_suites"))
29 tls_opts->tls_cipher_suites = val;
30 else if (!strcasecmp(key, "tls_server_name"))
31 tls_opts->tls_server_name = val;
32 else if (!strcasecmp(key, "tls_psk_identity"))
33 tls_opts->tls_psk_identity = val;
34 else if (!strcasecmp(key, "tls_psk_key"))
35 tls_opts->tls_psk_key = val;
36 else
37 return 1;
38 return 0;
39 }
40
atobv(char const * arg,int def)41 int atobv(char const *arg, int def)
42 {
43 if (!arg)
44 return def;
45 if (!strcasecmp(arg, "true") || !strcasecmp(arg, "yes") || !strcasecmp(arg, "on") || !strcasecmp(arg, "enable"))
46 return 1;
47 return atoi(arg);
48 }
49
atoiv(char const * arg,int def)50 int atoiv(char const *arg, int def)
51 {
52 if (!arg)
53 return def;
54 char *endptr;
55 int val = strtol(arg, &endptr, 10);
56 if (arg == endptr)
57 return def;
58 return val;
59 }
60
arg_param(char const * arg)61 char *arg_param(char const *arg)
62 {
63 if (!arg)
64 return NULL;
65 char *p = strchr(arg, ':');
66 char *c = strchr(arg, ',');
67 if (p && (!c || p < c))
68 return ++p;
69 else if (c)
70 return c;
71 else
72 return p;
73 }
74
arg_float(char const * str,char const * error_hint)75 double arg_float(char const *str, char const *error_hint)
76 {
77 if (!str) {
78 fprintf(stderr, "%smissing number argument\n", error_hint);
79 exit(1);
80 }
81
82 if (!*str) {
83 fprintf(stderr, "%sempty number argument\n", error_hint);
84 exit(1);
85 }
86
87 // allow whitespace and equals char
88 while (*str == ' ' || *str == '=')
89 ++str;
90
91 char *endptr;
92 double val = strtod(str, &endptr);
93
94 if (str == endptr) {
95 fprintf(stderr, "%sinvalid number argument (%s)\n", error_hint, str);
96 exit(1);
97 }
98
99 return val;
100 }
101
hostport_param(char * param,char ** host,char ** port)102 char *hostport_param(char *param, char **host, char **port)
103 {
104 if (param && *param) {
105 if (param[0] == '/' && param[1] == '/') {
106 param += 2;
107 }
108 if (*param != ':' && *param != ',') {
109 *host = param;
110 if (*param == '[') {
111 (*host)++;
112 param = strchr(param, ']');
113 if (param) {
114 *param++ = '\0';
115 }
116 else {
117 fprintf(stderr, "Malformed Ipv6 address!\n");
118 exit(1);
119 }
120 }
121 }
122 char *colon = strchr(param, ':');
123 char *comma = strchr(param, ',');
124 if (colon && (!comma || colon < comma)) {
125 *colon++ = '\0';
126 *port = colon;
127 }
128 if (comma) {
129 *comma++ = '\0';
130 return comma;
131 }
132 }
133 return NULL;
134 }
135
atouint32_metric(char const * str,char const * error_hint)136 uint32_t atouint32_metric(char const *str, char const *error_hint)
137 {
138 if (!str) {
139 fprintf(stderr, "%smissing number argument\n", error_hint);
140 exit(1);
141 }
142
143 if (!*str) {
144 fprintf(stderr, "%sempty number argument\n", error_hint);
145 exit(1);
146 }
147
148 char *endptr;
149 double val = strtod(str, &endptr);
150
151 if (str == endptr) {
152 fprintf(stderr, "%sinvalid number argument (%s)\n", error_hint, str);
153 exit(1);
154 }
155
156 if (val < 0.0) {
157 fprintf(stderr, "%snon-negative number argument expected (%f)\n", error_hint, val);
158 exit(1);
159 }
160
161 // allow whitespace before suffix
162 while (*endptr == ' ' || *endptr == '\t')
163 ++endptr;
164
165 switch (*endptr) {
166 case '\0':
167 break;
168 case 'k':
169 case 'K':
170 val *= 1e3;
171 break;
172 case 'M':
173 case 'm':
174 val *= 1e6;
175 break;
176 case 'G':
177 case 'g':
178 val *= 1e9;
179 break;
180 default:
181 fprintf(stderr, "%sunknown number suffix (%s)\n", error_hint, endptr);
182 exit(1);
183 }
184
185 if (val > UINT32_MAX) {
186 fprintf(stderr, "%snumber argument too big (%f)\n", error_hint, val);
187 exit(1);
188 }
189
190 val += 1e-5; // rounding (e.g. 4123456789.99999)
191 if (val - (uint32_t)val > 2e-5) {
192 fprintf(stderr, "%sdecimal fraction (%f) did you forget k, M, or G suffix?\n", error_hint, val - (uint32_t)val);
193 }
194
195 return (uint32_t)val;
196 }
197
atoi_time(char const * str,char const * error_hint)198 int atoi_time(char const *str, char const *error_hint)
199 {
200 if (!str) {
201 fprintf(stderr, "%smissing time argument\n", error_hint);
202 exit(1);
203 }
204
205 if (!*str) {
206 fprintf(stderr, "%sempty time argument\n", error_hint);
207 exit(1);
208 }
209
210 char *endptr = NULL;
211 double val = 0.0;
212 unsigned colons = 0;
213
214 do {
215 double num = strtod(str, &endptr);
216
217 if (!endptr || str == endptr) {
218 fprintf(stderr, "%sinvalid time argument (%s)\n", error_hint, str);
219 exit(1);
220 }
221
222 // allow whitespace before suffix
223 while (*endptr == ' ' || *endptr == '\t')
224 ++endptr;
225
226 switch (*endptr) {
227 case '\0':
228 if (colons == 0) {
229 // assume seconds
230 val += num;
231 break;
232 }
233 // intentional fallthrough
234 #if defined(__GNUC__) || defined(__clang__)
235 #if __has_attribute(fallthrough)
236 __attribute__((fallthrough));
237 #endif
238 #endif
239 case ':':
240 ++colons;
241 if (colons == 1)
242 val += num * 60 * 60;
243 else if (colons == 2)
244 val += num * 60;
245 else if (colons == 3)
246 val += num;
247 else {
248 fprintf(stderr, "%stoo many colons (use HH:MM[:SS]))\n", error_hint);
249 exit(1);
250 }
251 if (*endptr)
252 ++endptr;
253 break;
254 case 's':
255 case 'S':
256 val += num;
257 ++endptr;
258 break;
259 case 'm':
260 case 'M':
261 val += num * 60;
262 ++endptr;
263 break;
264 case 'h':
265 case 'H':
266 val += num * 60 * 60;
267 ++endptr;
268 break;
269 case 'd':
270 case 'D':
271 val += num * 60 * 60 * 24;
272 ++endptr;
273 break;
274 default:
275 fprintf(stderr, "%sunknown time suffix (%s)\n", error_hint, endptr);
276 exit(1);
277 }
278
279 // chew up any remaining whitespace
280 while (*endptr == ' ' || *endptr == '\t')
281 ++endptr;
282 str = endptr;
283
284 } while (*endptr);
285
286 if (val > INT_MAX || val < INT_MIN) {
287 fprintf(stderr, "%stime argument too big (%f)\n", error_hint, val);
288 exit(1);
289 }
290
291 if (val < 0) {
292 val -= 1e-5; // rounding (e.g. -4123456789.99999)
293 }
294 else {
295 val += 1e-5; // rounding (e.g. 4123456789.99999)
296 }
297 if (val - (int)(val) > 2e-5) {
298 fprintf(stderr, "%sdecimal fraction (%f) did you forget m, or h suffix?\n", error_hint, val - (uint32_t)val);
299 }
300
301 return (int)val;
302 }
303
asepc(char ** stringp,char delim)304 char *asepc(char **stringp, char delim)
305 {
306 if (!stringp || !*stringp) return NULL;
307 char *s = strchr(*stringp, delim);
308 if (s) *s++ = '\0';
309 char *p = *stringp;
310 *stringp = s;
311 return p;
312 }
313
achrb(char * s,int c,int b)314 static char *achrb(char *s, int c, int b)
315 {
316 for (; s && *s && *s != b; ++s)
317 if (*s == c) return (char *)s;
318 return NULL;
319 }
320
asepcb(char ** stringp,char delim,char stop)321 char *asepcb(char **stringp, char delim, char stop)
322 {
323 if (!stringp || !*stringp) return NULL;
324 char *s = achrb(*stringp, delim, stop);
325 if (s) *s++ = '\0';
326 char *p = *stringp;
327 *stringp = s;
328 return p;
329 }
330
kwargs_match(char const * s,char const * key,char const ** val)331 int kwargs_match(char const *s, char const *key, char const **val)
332 {
333 size_t len = strlen(key);
334 // check prefix match
335 if (strncmp(s, key, len)) {
336 return 0; // no match
337 }
338 s += len;
339 // skip whitespace after match
340 while (*s == ' ' || *s == '\t')
341 ++s;
342 if (*s == '\0' || * s == ',') {
343 if (val)
344 *val = NULL;
345 return 1; // match with no arg
346 }
347 if (*s == '=') {
348 if (val)
349 *val = s + 1;
350 return 1; // match with arg
351 }
352 return 0; // no exact match
353 }
354
kwargs_skip(char const * s)355 char const *kwargs_skip(char const *s)
356 {
357 // skip to the next comma if possible
358 while (s && *s && *s != ',')
359 ++s;
360 // skip comma and whitespace if possible
361 while (s && *s && (*s == ',' || *s == ' ' || *s == '\t'))
362 ++s;
363 return s;
364 }
365
getkwargs(char ** s,char ** key,char ** val)366 char *getkwargs(char **s, char **key, char **val)
367 {
368 char *v = asepc(s, ',');
369 char *k = asepc(&v, '=');
370 if (key) *key = k;
371 if (val) *val = v;
372 return k;
373 }
374
trim_ws(char * str)375 char *trim_ws(char *str)
376 {
377 if (!str || !*str)
378 return str;
379 while (*str == ' ' || *str == '\t' || *str == '\r' || *str == '\n')
380 ++str;
381 char *e = str; // end pointer (last non ws)
382 char *p = str; // scanning pointer
383 while (*p) {
384 while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')
385 ++p;
386 if (*p)
387 e = p++;
388 }
389 *++e = '\0';
390 return str;
391 }
392
remove_ws(char * str)393 char *remove_ws(char *str)
394 {
395 if (!str)
396 return str;
397 char *d = str; // dst pointer
398 char *s = str; // src pointer
399 while (*s) {
400 while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
401 ++s;
402 if (*s)
403 *d++ = *s++;
404 }
405 *d++ = '\0';
406 return str;
407 }
408
409 // Unit testing
410 #ifdef _TEST
411 #define ASSERT_EQUALS(a,b) \
412 do { \
413 if ((a) == (b)) \
414 ++passed; \
415 else { \
416 ++failed; \
417 fprintf(stderr, "FAIL: %d <> %d\n", (a), (b)); \
418 } \
419 } while (0)
420
main(void)421 int main(void)
422 {
423 unsigned passed = 0;
424 unsigned failed = 0;
425
426 fprintf(stderr, "optparse:: atouint32_metric\n");
427 ASSERT_EQUALS(atouint32_metric("0", ""), 0);
428 ASSERT_EQUALS(atouint32_metric("1", ""), 1);
429 ASSERT_EQUALS(atouint32_metric("0.0", ""), 0);
430 ASSERT_EQUALS(atouint32_metric("1.0", ""), 1);
431 ASSERT_EQUALS(atouint32_metric("1.024k", ""), 1024);
432 ASSERT_EQUALS(atouint32_metric("433.92M", ""), 433920000);
433 ASSERT_EQUALS(atouint32_metric("433.94M", ""), 433940000);
434 ASSERT_EQUALS(atouint32_metric(" +1 G ", ""), 1000000000);
435
436 fprintf(stderr, "optparse:: atoi_time\n");
437 ASSERT_EQUALS(atoi_time("0", ""), 0);
438 ASSERT_EQUALS(atoi_time("1", ""), 1);
439 ASSERT_EQUALS(atoi_time("0.0", ""), 0);
440 ASSERT_EQUALS(atoi_time("1.0", ""), 1);
441 ASSERT_EQUALS(atoi_time("1s", ""), 1);
442 ASSERT_EQUALS(atoi_time("2d", ""), 2 * 60 * 60 * 24);
443 ASSERT_EQUALS(atoi_time("2h", ""), 2 * 60 * 60);
444 ASSERT_EQUALS(atoi_time("2m", ""), 2 * 60);
445 ASSERT_EQUALS(atoi_time("2s", ""), 2);
446 ASSERT_EQUALS(atoi_time("2D", ""), 2 * 60 * 60 * 24);
447 ASSERT_EQUALS(atoi_time("2H", ""), 2 * 60 * 60);
448 ASSERT_EQUALS(atoi_time("2M", ""), 2 * 60);
449 ASSERT_EQUALS(atoi_time("2S", ""), 2);
450 ASSERT_EQUALS(atoi_time("2h3m4s", ""), 2 * 60 * 60 + 3 * 60 + 4);
451 ASSERT_EQUALS(atoi_time("2h 3m 4s", ""), 2 * 60 * 60 + 3 * 60 + 4);
452 ASSERT_EQUALS(atoi_time("2h3h 3m 4s 5", ""), 5 * 60 * 60 + 3 * 60 + 9);
453 ASSERT_EQUALS(atoi_time(" 2m ", ""), 2 * 60);
454 ASSERT_EQUALS(atoi_time("2 m", ""), 2 * 60);
455 ASSERT_EQUALS(atoi_time(" 2 m ", ""), 2 * 60);
456 ASSERT_EQUALS(atoi_time("-1m", ""), -60);
457 ASSERT_EQUALS(atoi_time("1h-15m", ""), 45 * 60);
458
459 ASSERT_EQUALS(atoi_time("2:3", ""), 2 * 60 * 60 + 3 * 60);
460 ASSERT_EQUALS(atoi_time("2:3:4", ""), 2 * 60 * 60 + 3 * 60 + 4);
461 ASSERT_EQUALS(atoi_time("02:03", ""), 2 * 60 * 60 + 3 * 60);
462 ASSERT_EQUALS(atoi_time("02:03:04", ""), 2 * 60 * 60 + 3 * 60 + 4);
463 ASSERT_EQUALS(atoi_time(" 2 : 3 ", ""), 2 * 60 * 60 + 3 * 60);
464 ASSERT_EQUALS(atoi_time(" 2 : 3 : 4 ", ""), 2 * 60 * 60 + 3 * 60 + 4);
465
466 fprintf(stderr, "optparse:: test (%u/%u) passed, (%u) failed.\n", passed, passed + failed, failed);
467
468 return failed;
469 }
470 #endif /* _TEST */
471