CLI: Import argpar
[lttng-tools.git] / src / common / argpar / argpar.c
1 /*
2 * SPDX-License-Identifier: MIT
3 *
4 * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
5 */
6
7 #include <assert.h>
8 #include <stdarg.h>
9 #include <stdbool.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13
14 #include "argpar.h"
15
16 #define argpar_realloc(_ptr, _type, _nmemb) ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type)))
17 #define argpar_calloc(_type, _nmemb) ((_type *) calloc((_nmemb), sizeof(_type)))
18 #define argpar_zalloc(_type) argpar_calloc(_type, 1)
19
20 #define ARGPAR_ASSERT(_cond) assert(_cond)
21
22 #ifdef __MINGW_PRINTF_FORMAT
23 # define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT
24 #else
25 # define ARGPAR_PRINTF_FORMAT printf
26 #endif
27
28 static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 0)))
29 char *argpar_vasprintf(const char *fmt, va_list args)
30 {
31 int len1, len2;
32 char *str;
33 va_list args2;
34
35 va_copy(args2, args);
36
37 len1 = vsnprintf(NULL, 0, fmt, args);
38 if (len1 < 0) {
39 str = NULL;
40 goto end;
41 }
42
43 str = malloc(len1 + 1);
44 if (!str) {
45 goto end;
46 }
47
48 len2 = vsnprintf(str, len1 + 1, fmt, args2);
49
50 ARGPAR_ASSERT(len1 == len2);
51
52 end:
53 va_end(args2);
54 return str;
55 }
56
57
58 static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 2)))
59 char *argpar_asprintf(const char *fmt, ...)
60 {
61 va_list args;
62 char *str;
63
64 va_start(args, fmt);
65 str = argpar_vasprintf(fmt, args);
66 va_end(args);
67
68 return str;
69 }
70
71 static __attribute__((format(ARGPAR_PRINTF_FORMAT, 2, 3)))
72 bool argpar_string_append_printf(char **str, const char *fmt, ...)
73 {
74 char *new_str = NULL;
75 char *addendum;
76 bool success;
77 va_list args;
78
79 ARGPAR_ASSERT(str);
80
81 va_start(args, fmt);
82 addendum = argpar_vasprintf(fmt, args);
83 va_end(args);
84
85 if (!addendum) {
86 success = false;
87 goto end;
88 }
89
90 new_str = argpar_asprintf("%s%s", *str ? *str : "", addendum);
91 if (!new_str) {
92 success = false;
93 goto end;
94 }
95
96 free(*str);
97 *str = new_str;
98
99 success = true;
100
101 end:
102 free(addendum);
103
104 return success;
105 }
106
107 static
108 void destroy_item(struct argpar_item * const item)
109 {
110 if (!item) {
111 goto end;
112 }
113
114 if (item->type == ARGPAR_ITEM_TYPE_OPT) {
115 struct argpar_item_opt * const opt_item = (void *) item;
116
117 free((void *) opt_item->arg);
118 }
119
120 free(item);
121
122 end:
123 return;
124 }
125
126 static
127 bool push_item(struct argpar_item_array * const array,
128 struct argpar_item * const item)
129 {
130 bool success;
131
132 ARGPAR_ASSERT(array);
133 ARGPAR_ASSERT(item);
134
135 if (array->n_items == array->n_alloc) {
136 unsigned int new_n_alloc = array->n_alloc * 2;
137 struct argpar_item **new_items;
138
139 new_items = argpar_realloc(array->items,
140 struct argpar_item *, new_n_alloc);
141 if (!new_items) {
142 success = false;
143 goto end;
144 }
145
146 array->n_alloc = new_n_alloc;
147 array->items = new_items;
148 }
149
150 array->items[array->n_items] = item;
151 array->n_items++;
152
153 success = true;
154
155 end:
156 return success;
157 }
158
159 static
160 void destroy_item_array(struct argpar_item_array * const array)
161 {
162 if (array) {
163 unsigned int i;
164
165 for (i = 0; i < array->n_items; i++) {
166 destroy_item(array->items[i]);
167 }
168
169 free(array->items);
170 free(array);
171 }
172 }
173
174 static
175 struct argpar_item_array *new_item_array(void)
176 {
177 struct argpar_item_array *ret;
178 const int initial_size = 10;
179
180 ret = argpar_zalloc(struct argpar_item_array);
181 if (!ret) {
182 goto end;
183 }
184
185 ret->items = argpar_calloc(struct argpar_item *, initial_size);
186 if (!ret->items) {
187 goto error;
188 }
189
190 ret->n_alloc = initial_size;
191
192 goto end;
193
194 error:
195 destroy_item_array(ret);
196 ret = NULL;
197
198 end:
199 return ret;
200 }
201
202 static
203 struct argpar_item_opt *create_opt_item(
204 const struct argpar_opt_descr * const descr,
205 const char * const arg)
206 {
207 struct argpar_item_opt *opt_item =
208 argpar_zalloc(struct argpar_item_opt);
209
210 if (!opt_item) {
211 goto end;
212 }
213
214 opt_item->base.type = ARGPAR_ITEM_TYPE_OPT;
215 opt_item->descr = descr;
216
217 if (arg) {
218 opt_item->arg = strdup(arg);
219 if (!opt_item->arg) {
220 goto error;
221 }
222 }
223
224 goto end;
225
226 error:
227 destroy_item(&opt_item->base);
228 opt_item = NULL;
229
230 end:
231 return opt_item;
232 }
233
234 static
235 struct argpar_item_non_opt *create_non_opt_item(const char * const arg,
236 const unsigned int orig_index,
237 const unsigned int non_opt_index)
238 {
239 struct argpar_item_non_opt * const non_opt_item =
240 argpar_zalloc(struct argpar_item_non_opt);
241
242 if (!non_opt_item) {
243 goto end;
244 }
245
246 non_opt_item->base.type = ARGPAR_ITEM_TYPE_NON_OPT;
247 non_opt_item->arg = arg;
248 non_opt_item->orig_index = orig_index;
249 non_opt_item->non_opt_index = non_opt_index;
250
251 end:
252 return non_opt_item;
253 }
254
255 static
256 const struct argpar_opt_descr *find_descr(
257 const struct argpar_opt_descr * const descrs,
258 const char short_name, const char * const long_name)
259 {
260 const struct argpar_opt_descr *descr;
261
262 for (descr = descrs; descr->short_name || descr->long_name; descr++) {
263 if (short_name && descr->short_name &&
264 short_name == descr->short_name) {
265 goto end;
266 }
267
268 if (long_name && descr->long_name &&
269 strcmp(long_name, descr->long_name) == 0) {
270 goto end;
271 }
272 }
273
274 end:
275 return !descr->short_name && !descr->long_name ? NULL : descr;
276 }
277
278 enum parse_orig_arg_opt_ret {
279 PARSE_ORIG_ARG_OPT_RET_OK,
280 PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT = -2,
281 PARSE_ORIG_ARG_OPT_RET_ERROR = -1,
282 };
283
284 static
285 enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts,
286 const char * const next_orig_arg,
287 const struct argpar_opt_descr * const descrs,
288 struct argpar_parse_ret * const parse_ret,
289 bool * const used_next_orig_arg)
290 {
291 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
292 const char *short_opt_ch = short_opts;
293
294 if (strlen(short_opts) == 0) {
295 argpar_string_append_printf(&parse_ret->error, "Invalid argument");
296 goto error;
297 }
298
299 while (*short_opt_ch) {
300 const char *opt_arg = NULL;
301 const struct argpar_opt_descr *descr;
302 struct argpar_item_opt *opt_item;
303
304 /* Find corresponding option descriptor */
305 descr = find_descr(descrs, *short_opt_ch, NULL);
306 if (!descr) {
307 ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT;
308 argpar_string_append_printf(&parse_ret->error,
309 "Unknown option `-%c`", *short_opt_ch);
310 goto error;
311 }
312
313 if (descr->with_arg) {
314 if (short_opt_ch[1]) {
315 /* `-oarg` form */
316 opt_arg = &short_opt_ch[1];
317 } else {
318 /* `-o arg` form */
319 opt_arg = next_orig_arg;
320 *used_next_orig_arg = true;
321 }
322
323 /*
324 * We accept `-o ''` (empty option's argument),
325 * but not `-o` alone if an option's argument is
326 * expected.
327 */
328 if (!opt_arg || (short_opt_ch[1] && strlen(opt_arg) == 0)) {
329 argpar_string_append_printf(&parse_ret->error,
330 "Missing required argument for option `-%c`",
331 *short_opt_ch);
332 *used_next_orig_arg = false;
333 goto error;
334 }
335 }
336
337 /* Create and append option argument */
338 opt_item = create_opt_item(descr, opt_arg);
339 if (!opt_item) {
340 goto error;
341 }
342
343 if (!push_item(parse_ret->items, &opt_item->base)) {
344 goto error;
345 }
346
347 if (descr->with_arg) {
348 /* Option has an argument: no more options */
349 break;
350 }
351
352 /* Go to next short option */
353 short_opt_ch++;
354 }
355
356 goto end;
357
358 error:
359 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
360 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
361 }
362
363 end:
364 return ret;
365 }
366
367 static
368 enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg,
369 const char * const next_orig_arg,
370 const struct argpar_opt_descr * const descrs,
371 struct argpar_parse_ret * const parse_ret,
372 bool * const used_next_orig_arg)
373 {
374 const size_t max_len = 127;
375 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
376 const struct argpar_opt_descr *descr;
377 struct argpar_item_opt *opt_item;
378
379 /* Option's argument, if any */
380 const char *opt_arg = NULL;
381
382 /* Position of first `=`, if any */
383 const char *eq_pos;
384
385 /* Buffer holding option name when `long_opt_arg` contains `=` */
386 char buf[max_len + 1];
387
388 /* Option name */
389 const char *long_opt_name = long_opt_arg;
390
391 if (strlen(long_opt_arg) == 0) {
392 argpar_string_append_printf(&parse_ret->error,
393 "Invalid argument");
394 goto error;
395 }
396
397 /* Find the first `=` in original argument */
398 eq_pos = strchr(long_opt_arg, '=');
399 if (eq_pos) {
400 const size_t long_opt_name_size = eq_pos - long_opt_arg;
401
402 /* Isolate the option name */
403 if (long_opt_name_size > max_len) {
404 argpar_string_append_printf(&parse_ret->error,
405 "Invalid argument `--%s`", long_opt_arg);
406 goto error;
407 }
408
409 memcpy(buf, long_opt_arg, long_opt_name_size);
410 buf[long_opt_name_size] = '\0';
411 long_opt_name = buf;
412 }
413
414 /* Find corresponding option descriptor */
415 descr = find_descr(descrs, '\0', long_opt_name);
416 if (!descr) {
417 argpar_string_append_printf(&parse_ret->error,
418 "Unknown option `--%s`", long_opt_name);
419 ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT;
420 goto error;
421 }
422
423 /* Find option's argument if any */
424 if (descr->with_arg) {
425 if (eq_pos) {
426 /* `--long-opt=arg` style */
427 opt_arg = eq_pos + 1;
428 } else {
429 /* `--long-opt arg` style */
430 if (!next_orig_arg) {
431 argpar_string_append_printf(&parse_ret->error,
432 "Missing required argument for option `--%s`",
433 long_opt_name);
434 goto error;
435 }
436
437 opt_arg = next_orig_arg;
438 *used_next_orig_arg = true;
439 }
440 }
441
442 /* Create and append option argument */
443 opt_item = create_opt_item(descr, opt_arg);
444 if (!opt_item) {
445 goto error;
446 }
447
448 if (!push_item(parse_ret->items, &opt_item->base)) {
449 goto error;
450 }
451
452 goto end;
453
454 error:
455 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
456 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
457 }
458
459 end:
460 return ret;
461 }
462
463 static
464 enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg,
465 const char * const next_orig_arg,
466 const struct argpar_opt_descr * const descrs,
467 struct argpar_parse_ret * const parse_ret,
468 bool * const used_next_orig_arg)
469 {
470 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
471
472 ARGPAR_ASSERT(orig_arg[0] == '-');
473
474 if (orig_arg[1] == '-') {
475 /* Long option */
476 ret = parse_long_opt(&orig_arg[2],
477 next_orig_arg, descrs, parse_ret,
478 used_next_orig_arg);
479 } else {
480 /* Short option */
481 ret = parse_short_opts(&orig_arg[1],
482 next_orig_arg, descrs, parse_ret,
483 used_next_orig_arg);
484 }
485
486 return ret;
487 }
488
489 static
490 bool prepend_while_parsing_arg_to_error(char **error,
491 const unsigned int i, const char * const arg)
492 {
493 char *new_error;
494 bool success;
495
496 ARGPAR_ASSERT(error);
497 ARGPAR_ASSERT(*error);
498
499 new_error = argpar_asprintf("While parsing argument #%u (`%s`): %s",
500 i + 1, arg, *error);
501 if (!new_error) {
502 success = false;
503 goto end;
504 }
505
506 free(*error);
507 *error = new_error;
508 success = true;
509
510 end:
511 return success;
512 }
513
514 ARGPAR_HIDDEN
515 struct argpar_parse_ret argpar_parse(unsigned int argc,
516 const char * const *argv,
517 const struct argpar_opt_descr * const descrs,
518 bool fail_on_unknown_opt)
519 {
520 struct argpar_parse_ret parse_ret = { 0 };
521 unsigned int i;
522 unsigned int non_opt_index = 0;
523
524 parse_ret.items = new_item_array();
525 if (!parse_ret.items) {
526 goto error;
527 }
528
529 for (i = 0; i < argc; i++) {
530 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret;
531 bool used_next_orig_arg = false;
532 const char * const orig_arg = argv[i];
533 const char * const next_orig_arg =
534 i < argc - 1 ? argv[i + 1] : NULL;
535
536 if (orig_arg[0] != '-') {
537 /* Non-option argument */
538 struct argpar_item_non_opt *non_opt_item =
539 create_non_opt_item(orig_arg, i, non_opt_index);
540
541 if (!non_opt_item) {
542 goto error;
543 }
544
545 non_opt_index++;
546
547 if (!push_item(parse_ret.items, &non_opt_item->base)) {
548 goto error;
549 }
550
551 continue;
552 }
553
554 /* Option argument */
555 parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg,
556 next_orig_arg, descrs, &parse_ret, &used_next_orig_arg);
557 switch (parse_orig_arg_opt_ret) {
558 case PARSE_ORIG_ARG_OPT_RET_OK:
559 break;
560 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT:
561 ARGPAR_ASSERT(!used_next_orig_arg);
562
563 if (fail_on_unknown_opt) {
564 prepend_while_parsing_arg_to_error(
565 &parse_ret.error, i, orig_arg);
566 goto error;
567 }
568
569 /*
570 * The current original argument is not
571 * considered ingested because it triggered an
572 * unknown option.
573 */
574 parse_ret.ingested_orig_args = i;
575 free(parse_ret.error);
576 parse_ret.error = NULL;
577 goto end;
578 case PARSE_ORIG_ARG_OPT_RET_ERROR:
579 prepend_while_parsing_arg_to_error(
580 &parse_ret.error, i, orig_arg);
581 goto error;
582 default:
583 abort();
584 }
585
586 if (used_next_orig_arg) {
587 i++;
588 }
589 }
590
591 parse_ret.ingested_orig_args = argc;
592 free(parse_ret.error);
593 parse_ret.error = NULL;
594 goto end;
595
596 error:
597 /* That's how we indicate that an error occurred */
598 destroy_item_array(parse_ret.items);
599 parse_ret.items = NULL;
600
601 end:
602 return parse_ret;
603 }
604
605 ARGPAR_HIDDEN
606 void argpar_parse_ret_fini(struct argpar_parse_ret *ret)
607 {
608 ARGPAR_ASSERT(ret);
609
610 destroy_item_array(ret->items);
611 ret->items = NULL;
612
613 free(ret->error);
614 ret->error = NULL;
615 }
This page took 0.063533 seconds and 4 git commands to generate.