Force usage of assert() condition when NDEBUG is defined
[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 <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
19 #define ARGPAR_ASSERT(_cond) ((void) sizeof((void) (_cond), 0))
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
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 */
33 struct 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
59 static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 0)))
60 char *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
83 end:
84 va_end(args2);
85 return str;
86 }
87
88
89 static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 2)))
90 char *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
102 static __attribute__((format(ARGPAR_PRINTF_FORMAT, 2, 3)))
103 bool 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
132 end:
133 free(addendum);
134
135 return success;
136 }
137
138 ARGPAR_HIDDEN
139 void argpar_item_destroy(struct argpar_item *item)
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
153 end:
154 return;
155 }
156
157 static
158 bool 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
186 end:
187 return success;
188 }
189
190 static
191 void 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++) {
197 argpar_item_destroy(array->items[i]);
198 }
199
200 free(array->items);
201 free(array);
202 }
203 }
204
205 static
206 struct 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
225 error:
226 destroy_item_array(ret);
227 ret = NULL;
228
229 end:
230 return ret;
231 }
232
233 static
234 struct 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
257 error:
258 argpar_item_destroy(&opt_item->base);
259 opt_item = NULL;
260
261 end:
262 return opt_item;
263 }
264
265 static
266 struct 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
282 end:
283 return non_opt_item;
284 }
285
286 static
287 const 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
305 end:
306 return !descr->short_name && !descr->long_name ? NULL : descr;
307 }
308
309 enum 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
315 static
316 enum 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,
319 struct argpar_state *state,
320 char **error,
321 struct argpar_item **item)
322 {
323 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
324 bool used_next_orig_arg = false;
325
326 if (strlen(short_opts) == 0) {
327 argpar_string_append_printf(error, "Invalid argument");
328 goto error;
329 }
330
331 if (!state->short_opt_ch) {
332 state->short_opt_ch = short_opts;
333 }
334
335 const char *opt_arg = NULL;
336 const struct argpar_opt_descr *descr;
337 struct argpar_item_opt *opt_item;
338
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 }
347
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;
356 }
357
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;
368 goto error;
369 }
370 }
371
372 /* Create and append option argument */
373 opt_item = create_opt_item(descr, opt_arg);
374 if (!opt_item) {
375 goto error;
376 }
377
378 *item = &opt_item->base;
379
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 }
391 }
392
393 goto end;
394
395 error:
396 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
397 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
398 }
399
400 end:
401 return ret;
402 }
403
404 static
405 enum 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,
408 struct argpar_state *state,
409 char **error,
410 struct argpar_item **item)
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;
416 bool used_next_orig_arg = false;
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) {
431 argpar_string_append_printf(error,
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) {
443 argpar_string_append_printf(error,
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) {
456 argpar_string_append_printf(error,
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) {
470 argpar_string_append_printf(error,
471 "Missing required argument for option `--%s`",
472 long_opt_name);
473 goto error;
474 }
475
476 opt_arg = next_orig_arg;
477 used_next_orig_arg = true;
478 }
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;
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
496 if (used_next_orig_arg) {
497 state->i += 2;
498 } else {
499 state->i += 1;
500 }
501
502 *item = &opt_item->base;
503 goto end;
504
505 error:
506 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
507 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
508 }
509
510 end:
511 return ret;
512 }
513
514 static
515 enum 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,
518 struct argpar_state *state,
519 char **error,
520 struct argpar_item **item)
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],
529 next_orig_arg, descrs, state, error, item);
530 } else {
531 /* Short option */
532 ret = parse_short_opts(&orig_arg[1],
533 next_orig_arg, descrs, state, error, item);
534 }
535
536 return ret;
537 }
538
539 static
540 bool 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
560 end:
561 return success;
562 }
563
564 ARGPAR_HIDDEN
565 struct 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
581 end:
582 return state;
583 }
584
585 ARGPAR_HIDDEN
586 void argpar_state_destroy(struct argpar_state *state)
587 {
588 free(state);
589 }
590
591 ARGPAR_HIDDEN
592 enum 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
650 end:
651 return status;
652 }
653
654 ARGPAR_HIDDEN
655 int argpar_state_get_ingested_orig_args(struct argpar_state *state)
656 {
657 return state->i;
658 }
659
660 ARGPAR_HIDDEN
661 struct 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 };
667 struct argpar_item *item = NULL;
668 struct argpar_state *state = NULL;
669
670 parse_ret.items = new_item_array();
671 if (!parse_ret.items) {
672 parse_ret.error = strdup("Failed to create items array.");
673 ARGPAR_ASSERT(parse_ret.error);
674 goto error;
675 }
676
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 }
683
684 while (true) {
685 enum argpar_state_parse_next_status status;
686
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) {
691 break;
692 } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) {
693 if (fail_on_unknown_opt) {
694 parse_ret.ingested_orig_args =
695 argpar_state_get_ingested_orig_args(state);
696 prepend_while_parsing_arg_to_error(
697 &parse_ret.error, parse_ret.ingested_orig_args,
698 argv[parse_ret.ingested_orig_args]);
699 status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR;
700 goto error;
701 }
702
703 free(parse_ret.error);
704 parse_ret.error = NULL;
705 break;
706 }
707
708 ARGPAR_ASSERT(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK);
709
710 if (!push_item(parse_ret.items, item)) {
711 goto error;
712 }
713 item = NULL;
714 }
715
716 ARGPAR_ASSERT(!parse_ret.error);
717 parse_ret.ingested_orig_args =
718 argpar_state_get_ingested_orig_args(state);
719 goto end;
720
721 error:
722 ARGPAR_ASSERT(parse_ret.error);
723
724 /* That's how we indicate that an error occurred */
725 destroy_item_array(parse_ret.items);
726 parse_ret.items = NULL;
727
728 end:
729 argpar_state_destroy(state);
730 argpar_item_destroy(item);
731 return parse_ret;
732 }
733
734 ARGPAR_HIDDEN
735 void 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.047851 seconds and 4 git commands to generate.