Fix: argpar: Error out when passing an argument to long option that takes no argument
[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 479 }
ebdbbd32
SM
480 } else if (eq_pos) {
481 /*
482 * Unexpected `--opt=arg` style for a long option which
483 * doesn't accept an argument.
484 */
485 argpar_string_append_printf(error,
486 "Unexpected argument for option `--%s`",
487 long_opt_name);
488 goto error;
e2fb96d8
SM
489 }
490
491 /* Create and append option argument */
492 opt_item = create_opt_item(descr, opt_arg);
493 if (!opt_item) {
494 goto error;
495 }
496
4624dad0
SM
497 if (used_next_orig_arg) {
498 state->i += 2;
499 } else {
500 state->i += 1;
e2fb96d8
SM
501 }
502
4624dad0 503 *item = &opt_item->base;
e2fb96d8
SM
504 goto end;
505
506error:
507 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
508 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
509 }
510
511end:
512 return ret;
513}
514
515static
516enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg,
517 const char * const next_orig_arg,
518 const struct argpar_opt_descr * const descrs,
4624dad0
SM
519 struct argpar_state *state,
520 char **error,
521 struct argpar_item **item)
e2fb96d8
SM
522{
523 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
524
525 ARGPAR_ASSERT(orig_arg[0] == '-');
526
527 if (orig_arg[1] == '-') {
528 /* Long option */
529 ret = parse_long_opt(&orig_arg[2],
4624dad0 530 next_orig_arg, descrs, state, error, item);
e2fb96d8
SM
531 } else {
532 /* Short option */
533 ret = parse_short_opts(&orig_arg[1],
4624dad0 534 next_orig_arg, descrs, state, error, item);
e2fb96d8
SM
535 }
536
537 return ret;
538}
539
540static
541bool prepend_while_parsing_arg_to_error(char **error,
542 const unsigned int i, const char * const arg)
543{
544 char *new_error;
545 bool success;
546
547 ARGPAR_ASSERT(error);
548 ARGPAR_ASSERT(*error);
549
550 new_error = argpar_asprintf("While parsing argument #%u (`%s`): %s",
551 i + 1, arg, *error);
552 if (!new_error) {
553 success = false;
554 goto end;
555 }
556
557 free(*error);
558 *error = new_error;
559 success = true;
560
561end:
562 return success;
563}
564
4624dad0
SM
565ARGPAR_HIDDEN
566struct argpar_state *argpar_state_create(
567 unsigned int argc,
568 const char * const *argv,
569 const struct argpar_opt_descr * const descrs)
570{
571 struct argpar_state *state;
572
573 state = argpar_zalloc(struct argpar_state);
574 if (!state) {
575 goto end;
576 }
577
578 state->argc = argc;
579 state->argv = argv;
580 state->descrs = descrs;
581
582end:
583 return state;
584}
585
586ARGPAR_HIDDEN
587void argpar_state_destroy(struct argpar_state *state)
588{
589 free(state);
590}
591
592ARGPAR_HIDDEN
593enum argpar_state_parse_next_status argpar_state_parse_next(
594 struct argpar_state *state,
595 struct argpar_item **item,
596 char **error)
597{
598 enum argpar_state_parse_next_status status;
599
600 ARGPAR_ASSERT(state->i <= state->argc);
601
602 *error = NULL;
603
604 if (state->i == state->argc) {
605 status = ARGPAR_STATE_PARSE_NEXT_STATUS_END;
606 goto end;
607 }
608
609 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret;
610 const char * const orig_arg = state->argv[state->i];
611 const char * const next_orig_arg =
612 state->i < (state->argc - 1) ? state->argv[state->i + 1] : NULL;
613
614 if (orig_arg[0] != '-') {
615 /* Non-option argument */
616 struct argpar_item_non_opt *non_opt_item =
617 create_non_opt_item(orig_arg, state->i, state->non_opt_index);
618
619 if (!non_opt_item) {
620 status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR;
621 goto end;
622 }
623
624 state->non_opt_index++;
625 state->i++;
626
627 *item = &non_opt_item->base;
628 status = ARGPAR_STATE_PARSE_NEXT_STATUS_OK;
629 goto end;
630 }
631
632 /* Option argument */
633 parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg,
634 next_orig_arg, state->descrs, state, error, item);
635 switch (parse_orig_arg_opt_ret) {
636 case PARSE_ORIG_ARG_OPT_RET_OK:
637 status = ARGPAR_STATE_PARSE_NEXT_STATUS_OK;
638 break;
639 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT:
640 status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT;
641 break;;
642 case PARSE_ORIG_ARG_OPT_RET_ERROR:
643 prepend_while_parsing_arg_to_error(
644 error, state->i, orig_arg);
645 status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR;
646 break;
647 default:
648 abort();
649 }
650
651end:
652 return status;
653}
654
655ARGPAR_HIDDEN
656int argpar_state_get_ingested_orig_args(struct argpar_state *state)
657{
658 return state->i;
659}
660
e2fb96d8
SM
661ARGPAR_HIDDEN
662struct argpar_parse_ret argpar_parse(unsigned int argc,
663 const char * const *argv,
664 const struct argpar_opt_descr * const descrs,
665 bool fail_on_unknown_opt)
666{
667 struct argpar_parse_ret parse_ret = { 0 };
4624dad0
SM
668 struct argpar_item *item = NULL;
669 struct argpar_state *state = NULL;
e2fb96d8
SM
670
671 parse_ret.items = new_item_array();
672 if (!parse_ret.items) {
4624dad0
SM
673 parse_ret.error = strdup("Failed to create items array.");
674 ARGPAR_ASSERT(parse_ret.error);
e2fb96d8
SM
675 goto error;
676 }
677
4624dad0
SM
678 state = argpar_state_create(argc, argv, descrs);
679 if (!state) {
680 parse_ret.error = strdup("Failed to create argpar state.");
681 ARGPAR_ASSERT(parse_ret.error);
682 goto error;
683 }
e2fb96d8 684
4624dad0
SM
685 while (true) {
686 enum argpar_state_parse_next_status status;
e2fb96d8 687
4624dad0
SM
688 status = argpar_state_parse_next(state, &item, &parse_ret.error);
689 if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) {
690 goto error;
691 } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) {
e2fb96d8 692 break;
4624dad0 693 } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) {
e2fb96d8 694 if (fail_on_unknown_opt) {
4624dad0
SM
695 parse_ret.ingested_orig_args =
696 argpar_state_get_ingested_orig_args(state);
e2fb96d8 697 prepend_while_parsing_arg_to_error(
4624dad0
SM
698 &parse_ret.error, parse_ret.ingested_orig_args,
699 argv[parse_ret.ingested_orig_args]);
700 status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR;
e2fb96d8
SM
701 goto error;
702 }
703
e2fb96d8
SM
704 free(parse_ret.error);
705 parse_ret.error = NULL;
4624dad0 706 break;
e2fb96d8
SM
707 }
708
4624dad0
SM
709 ARGPAR_ASSERT(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK);
710
711 if (!push_item(parse_ret.items, item)) {
712 goto error;
e2fb96d8 713 }
4624dad0 714 item = NULL;
e2fb96d8
SM
715 }
716
4624dad0
SM
717 ARGPAR_ASSERT(!parse_ret.error);
718 parse_ret.ingested_orig_args =
719 argpar_state_get_ingested_orig_args(state);
e2fb96d8
SM
720 goto end;
721
722error:
4624dad0
SM
723 ARGPAR_ASSERT(parse_ret.error);
724
e2fb96d8
SM
725 /* That's how we indicate that an error occurred */
726 destroy_item_array(parse_ret.items);
727 parse_ret.items = NULL;
728
729end:
4624dad0
SM
730 argpar_state_destroy(state);
731 argpar_item_destroy(item);
e2fb96d8
SM
732 return parse_ret;
733}
734
735ARGPAR_HIDDEN
736void argpar_parse_ret_fini(struct argpar_parse_ret *ret)
737{
738 ARGPAR_ASSERT(ret);
739
740 destroy_item_array(ret->items);
741 ret->items = NULL;
742
743 free(ret->error);
744 ret->error = NULL;
745}
This page took 0.052871 seconds and 4 git commands to generate.