SoW-2020-0002: Trace Hit Counters: trigger error reporting integration
[lttng-tools.git] / src / common / argpar / argpar.c
1 /*
2 * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23 #include <assert.h>
24 #include <stdarg.h>
25 #include <stdbool.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include "argpar.h"
31
32 #define argpar_realloc(_ptr, _type, _nmemb) ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type)))
33 #define argpar_calloc(_type, _nmemb) ((_type *) calloc((_nmemb), sizeof(_type)))
34 #define argpar_zalloc(_type) argpar_calloc(_type, 1)
35
36 #define ARGPAR_ASSERT(_cond) assert(_cond)
37
38 /*
39 * Structure holding the argpar state between successive argpar_state_parse_next
40 * calls.
41 *
42 * Created with `argpar_state_create` and destroyed with `argpar_state_destroy`.
43 */
44 struct argpar_state {
45 /*
46 * Data provided by the user in argpar_state_create, does not change
47 * afterwards.
48 */
49 unsigned int argc;
50 const char * const *argv;
51 const struct argpar_opt_descr *descrs;
52
53 /*
54 * Index of the argument to process in the next argpar_state_parse_next
55 * call.
56 */
57 unsigned int i;
58
59 /* Counter of non-option arguments. */
60 int non_opt_index;
61
62 /*
63 * Short option state: if set, we are in the middle of a short option
64 * group, so we should resume there at the next argpar_state_parse_next
65 * call.
66 */
67 const char *short_opt_ch;
68 };
69
70 static
71 char *argpar_vasprintf(const char *fmt, va_list args)
72 {
73 int len1, len2;
74 char *str;
75 va_list args2;
76
77 va_copy(args2, args);
78
79 len1 = vsnprintf(NULL, 0, fmt, args);
80 if (len1 < 0) {
81 str = NULL;
82 goto end;
83 }
84
85 str = malloc(len1 + 1);
86 if (!str) {
87 goto end;
88 }
89
90 len2 = vsnprintf(str, len1 + 1, fmt, args2);
91
92 ARGPAR_ASSERT(len1 == len2);
93
94 end:
95 va_end(args2);
96 return str;
97 }
98
99
100 static
101 char *argpar_asprintf(const char *fmt, ...)
102 {
103 va_list args;
104 char *str;
105
106 va_start(args, fmt);
107 str = argpar_vasprintf(fmt, args);
108 va_end(args);
109
110 return str;
111 }
112
113 static
114 bool argpar_string_append_printf(char **str, const char *fmt, ...)
115 {
116 char *new_str = NULL;
117 char *addendum;
118 bool success;
119 va_list args;
120
121 ARGPAR_ASSERT(str);
122
123 va_start(args, fmt);
124 addendum = argpar_vasprintf(fmt, args);
125 va_end(args);
126
127 if (!addendum) {
128 success = false;
129 goto end;
130 }
131
132 new_str = argpar_asprintf("%s%s", *str ? *str : "", addendum);
133 if (!new_str) {
134 success = false;
135 goto end;
136 }
137
138 free(*str);
139 *str = new_str;
140
141 success = true;
142
143 end:
144 free(addendum);
145
146 return success;
147 }
148
149 ARGPAR_HIDDEN
150 void argpar_item_destroy(struct argpar_item *item)
151 {
152 if (!item) {
153 goto end;
154 }
155
156 if (item->type == ARGPAR_ITEM_TYPE_OPT) {
157 struct argpar_item_opt * const opt_item = (void *) item;
158
159 free((void *) opt_item->arg);
160 }
161
162 free(item);
163
164 end:
165 return;
166 }
167
168 static
169 bool push_item(struct argpar_item_array * const array,
170 struct argpar_item * const item)
171 {
172 bool success;
173
174 ARGPAR_ASSERT(array);
175 ARGPAR_ASSERT(item);
176
177 if (array->n_items == array->n_alloc) {
178 unsigned int new_n_alloc = array->n_alloc * 2;
179 struct argpar_item **new_items;
180
181 new_items = argpar_realloc(array->items,
182 struct argpar_item *, new_n_alloc);
183 if (!new_items) {
184 success = false;
185 goto end;
186 }
187
188 array->n_alloc = new_n_alloc;
189 array->items = new_items;
190 }
191
192 array->items[array->n_items] = item;
193 array->n_items++;
194
195 success = true;
196
197 end:
198 return success;
199 }
200
201 static
202 void destroy_item_array(struct argpar_item_array * const array)
203 {
204 if (array) {
205 unsigned int i;
206
207 for (i = 0; i < array->n_items; i++) {
208 argpar_item_destroy(array->items[i]);
209 }
210
211 free(array->items);
212 free(array);
213 }
214 }
215
216 static
217 struct argpar_item_array *new_item_array(void)
218 {
219 struct argpar_item_array *ret;
220 const int initial_size = 10;
221
222 ret = argpar_zalloc(struct argpar_item_array);
223 if (!ret) {
224 goto end;
225 }
226
227 ret->items = argpar_calloc(struct argpar_item *, initial_size);
228 if (!ret->items) {
229 goto error;
230 }
231
232 ret->n_alloc = initial_size;
233
234 goto end;
235
236 error:
237 destroy_item_array(ret);
238 ret = NULL;
239
240 end:
241 return ret;
242 }
243
244 static
245 struct argpar_item_opt *create_opt_item(
246 const struct argpar_opt_descr * const descr,
247 const char * const arg)
248 {
249 struct argpar_item_opt *opt_item =
250 argpar_zalloc(struct argpar_item_opt);
251
252 if (!opt_item) {
253 goto end;
254 }
255
256 opt_item->base.type = ARGPAR_ITEM_TYPE_OPT;
257 opt_item->descr = descr;
258
259 if (arg) {
260 opt_item->arg = strdup(arg);
261 if (!opt_item->arg) {
262 goto error;
263 }
264 }
265
266 goto end;
267
268 error:
269 argpar_item_destroy(&opt_item->base);
270 opt_item = NULL;
271
272 end:
273 return opt_item;
274 }
275
276 static
277 struct argpar_item_non_opt *create_non_opt_item(const char * const arg,
278 const unsigned int orig_index,
279 const unsigned int non_opt_index)
280 {
281 struct argpar_item_non_opt * const non_opt_item =
282 argpar_zalloc(struct argpar_item_non_opt);
283
284 if (!non_opt_item) {
285 goto end;
286 }
287
288 non_opt_item->base.type = ARGPAR_ITEM_TYPE_NON_OPT;
289 non_opt_item->arg = arg;
290 non_opt_item->orig_index = orig_index;
291 non_opt_item->non_opt_index = non_opt_index;
292
293 end:
294 return non_opt_item;
295 }
296
297 static
298 const struct argpar_opt_descr *find_descr(
299 const struct argpar_opt_descr * const descrs,
300 const char short_name, const char * const long_name)
301 {
302 const struct argpar_opt_descr *descr;
303
304 for (descr = descrs; descr->short_name || descr->long_name; descr++) {
305 if (short_name && descr->short_name &&
306 short_name == descr->short_name) {
307 goto end;
308 }
309
310 if (long_name && descr->long_name &&
311 strcmp(long_name, descr->long_name) == 0) {
312 goto end;
313 }
314 }
315
316 end:
317 return !descr->short_name && !descr->long_name ? NULL : descr;
318 }
319
320 enum parse_orig_arg_opt_ret {
321 PARSE_ORIG_ARG_OPT_RET_OK,
322 PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT = -2,
323 PARSE_ORIG_ARG_OPT_RET_ERROR = -1,
324 };
325
326 static
327 enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts,
328 const char * const next_orig_arg,
329 const struct argpar_opt_descr * const descrs,
330 struct argpar_state *state,
331 char **error,
332 struct argpar_item **item)
333 {
334 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
335 bool used_next_orig_arg = false;
336
337 if (strlen(short_opts) == 0) {
338 argpar_string_append_printf(error, "Invalid argument");
339 goto error;
340 }
341
342 if (!state->short_opt_ch) {
343 state->short_opt_ch = short_opts;
344 }
345
346 const char *opt_arg = NULL;
347 const struct argpar_opt_descr *descr;
348 struct argpar_item_opt *opt_item;
349
350 /* Find corresponding option descriptor */
351 descr = find_descr(descrs, *state->short_opt_ch, NULL);
352 if (!descr) {
353 ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT;
354 argpar_string_append_printf(error,
355 "Unknown option `-%c`", *state->short_opt_ch);
356 goto error;
357 }
358
359 if (descr->with_arg) {
360 if (state->short_opt_ch[1]) {
361 /* `-oarg` form */
362 opt_arg = &state->short_opt_ch[1];
363 } else {
364 /* `-o arg` form */
365 opt_arg = next_orig_arg;
366 used_next_orig_arg = true;
367 }
368
369 /*
370 * We accept `-o ''` (empty option's argument),
371 * but not `-o` alone if an option's argument is
372 * expected.
373 */
374 if (!opt_arg || (state->short_opt_ch[1] && strlen(opt_arg) == 0)) {
375 argpar_string_append_printf(error,
376 "Missing required argument for option `-%c`",
377 *state->short_opt_ch);
378 used_next_orig_arg = false;
379 goto error;
380 }
381 }
382
383 /* Create and append option argument */
384 opt_item = create_opt_item(descr, opt_arg);
385 if (!opt_item) {
386 goto error;
387 }
388
389 *item = &opt_item->base;
390
391 state->short_opt_ch++;
392
393 if (descr->with_arg || !*state->short_opt_ch) {
394 /* Option has an argument: no more options */
395 state->short_opt_ch = NULL;
396
397 if (used_next_orig_arg) {
398 state->i += 2;
399 } else {
400 state->i += 1;
401 }
402 }
403
404 goto end;
405
406 error:
407 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
408 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
409 }
410
411 end:
412 return ret;
413 }
414
415 static
416 enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg,
417 const char * const next_orig_arg,
418 const struct argpar_opt_descr * const descrs,
419 struct argpar_state *state,
420 char **error,
421 struct argpar_item **item)
422 {
423 const size_t max_len = 127;
424 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
425 const struct argpar_opt_descr *descr;
426 struct argpar_item_opt *opt_item;
427 bool used_next_orig_arg = false;
428
429 /* Option's argument, if any */
430 const char *opt_arg = NULL;
431
432 /* Position of first `=`, if any */
433 const char *eq_pos;
434
435 /* Buffer holding option name when `long_opt_arg` contains `=` */
436 char buf[max_len + 1];
437
438 /* Option name */
439 const char *long_opt_name = long_opt_arg;
440
441 if (strlen(long_opt_arg) == 0) {
442 argpar_string_append_printf(error,
443 "Invalid argument");
444 goto error;
445 }
446
447 /* Find the first `=` in original argument */
448 eq_pos = strchr(long_opt_arg, '=');
449 if (eq_pos) {
450 const size_t long_opt_name_size = eq_pos - long_opt_arg;
451
452 /* Isolate the option name */
453 if (long_opt_name_size > max_len) {
454 argpar_string_append_printf(error,
455 "Invalid argument `--%s`", long_opt_arg);
456 goto error;
457 }
458
459 memcpy(buf, long_opt_arg, long_opt_name_size);
460 buf[long_opt_name_size] = '\0';
461 long_opt_name = buf;
462 }
463
464 /* Find corresponding option descriptor */
465 descr = find_descr(descrs, '\0', long_opt_name);
466 if (!descr) {
467 argpar_string_append_printf(error,
468 "Unknown option `--%s`", long_opt_name);
469 ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT;
470 goto error;
471 }
472
473 /* Find option's argument if any */
474 if (descr->with_arg) {
475 if (eq_pos) {
476 /* `--long-opt=arg` style */
477 opt_arg = eq_pos + 1;
478 } else {
479 /* `--long-opt arg` style */
480 if (!next_orig_arg) {
481 argpar_string_append_printf(error,
482 "Missing required argument for option `--%s`",
483 long_opt_name);
484 goto error;
485 }
486
487 opt_arg = next_orig_arg;
488 used_next_orig_arg = true;
489 }
490 }
491
492 /* Create and append option argument */
493 opt_item = create_opt_item(descr, opt_arg);
494 if (!opt_item) {
495 goto error;
496 }
497
498 if (used_next_orig_arg) {
499 state->i += 2;
500 } else {
501 state->i += 1;
502 }
503
504 *item = &opt_item->base;
505 goto end;
506
507 error:
508 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
509 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
510 }
511
512 end:
513 return ret;
514 }
515
516 static
517 enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg,
518 const char * const next_orig_arg,
519 const struct argpar_opt_descr * const descrs,
520 struct argpar_state *state,
521 char **error,
522 struct argpar_item **item)
523 {
524 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
525
526 ARGPAR_ASSERT(orig_arg[0] == '-');
527
528 if (orig_arg[1] == '-') {
529 /* Long option */
530 ret = parse_long_opt(&orig_arg[2],
531 next_orig_arg, descrs, state, error, item);
532 } else {
533 /* Short option */
534 ret = parse_short_opts(&orig_arg[1],
535 next_orig_arg, descrs, state, error, item);
536 }
537
538 return ret;
539 }
540
541 static
542 bool prepend_while_parsing_arg_to_error(char **error,
543 const unsigned int i, const char * const arg)
544 {
545 char *new_error;
546 bool success;
547
548 ARGPAR_ASSERT(error);
549 ARGPAR_ASSERT(*error);
550
551 new_error = argpar_asprintf("While parsing argument #%u (`%s`): %s",
552 i + 1, arg, *error);
553 if (!new_error) {
554 success = false;
555 goto end;
556 }
557
558 free(*error);
559 *error = new_error;
560 success = true;
561
562 end:
563 return success;
564 }
565
566 ARGPAR_HIDDEN
567 struct argpar_state *argpar_state_create(
568 unsigned int argc,
569 const char * const *argv,
570 const struct argpar_opt_descr * const descrs)
571 {
572 struct argpar_state *state;
573
574 state = argpar_zalloc(struct argpar_state);
575 if (!state) {
576 goto end;
577 }
578
579 state->argc = argc;
580 state->argv = argv;
581 state->descrs = descrs;
582
583 end:
584 return state;
585 }
586
587 ARGPAR_HIDDEN
588 void argpar_state_destroy(struct argpar_state *state)
589 {
590 free(state);
591 }
592
593 ARGPAR_HIDDEN
594 enum argpar_state_parse_next_status argpar_state_parse_next(
595 struct argpar_state *state,
596 struct argpar_item **item,
597 char **error)
598 {
599 enum argpar_state_parse_next_status status;
600
601 ARGPAR_ASSERT(state->i <= state->argc);
602
603 *error = NULL;
604
605 if (state->i == state->argc) {
606 status = ARGPAR_STATE_PARSE_NEXT_STATUS_END;
607 goto end;
608 }
609
610 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret;
611 const char * const orig_arg = state->argv[state->i];
612 const char * const next_orig_arg =
613 state->i < (state->argc - 1) ? state->argv[state->i + 1] : NULL;
614
615 if (orig_arg[0] != '-') {
616 /* Non-option argument */
617 struct argpar_item_non_opt *non_opt_item =
618 create_non_opt_item(orig_arg, state->i, state->non_opt_index);
619
620 if (!non_opt_item) {
621 status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR;
622 goto end;
623 }
624
625 state->non_opt_index++;
626 state->i++;
627
628 *item = &non_opt_item->base;
629 status = ARGPAR_STATE_PARSE_NEXT_STATUS_OK;
630 goto end;
631 }
632
633 /* Option argument */
634 parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg,
635 next_orig_arg, state->descrs, state, error, item);
636 switch (parse_orig_arg_opt_ret) {
637 case PARSE_ORIG_ARG_OPT_RET_OK:
638 status = ARGPAR_STATE_PARSE_NEXT_STATUS_OK;
639 break;
640 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT:
641 status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT;
642 break;;
643 case PARSE_ORIG_ARG_OPT_RET_ERROR:
644 prepend_while_parsing_arg_to_error(
645 error, state->i, orig_arg);
646 status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR;
647 break;
648 default:
649 abort();
650 }
651
652 end:
653 return status;
654 }
655
656 ARGPAR_HIDDEN
657 int argpar_state_get_ingested_orig_args(struct argpar_state *state)
658 {
659 return state->i;
660 }
661
662 ARGPAR_HIDDEN
663 struct argpar_parse_ret argpar_parse(unsigned int argc,
664 const char * const *argv,
665 const struct argpar_opt_descr * const descrs,
666 bool fail_on_unknown_opt)
667 {
668 struct argpar_parse_ret parse_ret = { 0 };
669 struct argpar_item *item = NULL;
670 struct argpar_state *state = NULL;
671
672 parse_ret.items = new_item_array();
673 if (!parse_ret.items) {
674 parse_ret.error = strdup("Failed to create items array.");
675 ARGPAR_ASSERT(parse_ret.error);
676 goto error;
677 }
678
679 state = argpar_state_create(argc, argv, descrs);
680 if (!state) {
681 parse_ret.error = strdup("Failed to create argpar state.");
682 ARGPAR_ASSERT(parse_ret.error);
683 goto error;
684 }
685
686 while (true) {
687 enum argpar_state_parse_next_status status;
688
689 status = argpar_state_parse_next(state, &item, &parse_ret.error);
690 if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) {
691 goto error;
692 } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) {
693 break;
694 } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) {
695 if (fail_on_unknown_opt) {
696 parse_ret.ingested_orig_args =
697 argpar_state_get_ingested_orig_args(state);
698 prepend_while_parsing_arg_to_error(
699 &parse_ret.error, parse_ret.ingested_orig_args,
700 argv[parse_ret.ingested_orig_args]);
701 status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR;
702 goto error;
703 }
704
705 free(parse_ret.error);
706 parse_ret.error = NULL;
707 break;
708 }
709
710 ARGPAR_ASSERT(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK);
711
712 if (!push_item(parse_ret.items, item)) {
713 goto error;
714 }
715 item = NULL;
716 }
717
718 ARGPAR_ASSERT(!parse_ret.error);
719 parse_ret.ingested_orig_args =
720 argpar_state_get_ingested_orig_args(state);
721 goto end;
722
723 error:
724 ARGPAR_ASSERT(parse_ret.error);
725
726 /* That's how we indicate that an error occured */
727 destroy_item_array(parse_ret.items);
728 parse_ret.items = NULL;
729
730 end:
731 argpar_state_destroy(state);
732 argpar_item_destroy(item);
733 return parse_ret;
734 }
735
736 ARGPAR_HIDDEN
737 void argpar_parse_ret_fini(struct argpar_parse_ret *ret)
738 {
739 ARGPAR_ASSERT(ret);
740
741 destroy_item_array(ret->items);
742 ret->items = NULL;
743
744 free(ret->error);
745 ret->error = NULL;
746 }
This page took 0.044723 seconds and 5 git commands to generate.