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