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