bf19808c54715657330d926268a3f04dd17a350c
[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 <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
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 */
34 struct 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
60 static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 0)))
61 char *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
84 end:
85 va_end(args2);
86 return str;
87 }
88
89
90 static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 2)))
91 char *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
103 static __attribute__((format(ARGPAR_PRINTF_FORMAT, 2, 3)))
104 bool 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
133 end:
134 free(addendum);
135
136 return success;
137 }
138
139 ARGPAR_HIDDEN
140 void argpar_item_destroy(struct argpar_item *item)
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
154 end:
155 return;
156 }
157
158 static
159 bool 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
187 end:
188 return success;
189 }
190
191 static
192 void 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++) {
198 argpar_item_destroy(array->items[i]);
199 }
200
201 free(array->items);
202 free(array);
203 }
204 }
205
206 static
207 struct 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
226 error:
227 destroy_item_array(ret);
228 ret = NULL;
229
230 end:
231 return ret;
232 }
233
234 static
235 struct 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
258 error:
259 argpar_item_destroy(&opt_item->base);
260 opt_item = NULL;
261
262 end:
263 return opt_item;
264 }
265
266 static
267 struct 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
283 end:
284 return non_opt_item;
285 }
286
287 static
288 const 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
306 end:
307 return !descr->short_name && !descr->long_name ? NULL : descr;
308 }
309
310 enum 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
316 static
317 enum 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,
320 struct argpar_state *state,
321 char **error,
322 struct argpar_item **item)
323 {
324 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
325 bool used_next_orig_arg = false;
326
327 if (strlen(short_opts) == 0) {
328 argpar_string_append_printf(error, "Invalid argument");
329 goto error;
330 }
331
332 if (!state->short_opt_ch) {
333 state->short_opt_ch = short_opts;
334 }
335
336 const char *opt_arg = NULL;
337 const struct argpar_opt_descr *descr;
338 struct argpar_item_opt *opt_item;
339
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 }
348
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;
357 }
358
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;
369 goto error;
370 }
371 }
372
373 /* Create and append option argument */
374 opt_item = create_opt_item(descr, opt_arg);
375 if (!opt_item) {
376 goto error;
377 }
378
379 *item = &opt_item->base;
380
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 }
392 }
393
394 goto end;
395
396 error:
397 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
398 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
399 }
400
401 end:
402 return ret;
403 }
404
405 static
406 enum 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,
409 struct argpar_state *state,
410 char **error,
411 struct argpar_item **item)
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;
417 bool used_next_orig_arg = false;
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) {
432 argpar_string_append_printf(error,
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) {
444 argpar_string_append_printf(error,
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) {
457 argpar_string_append_printf(error,
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) {
471 argpar_string_append_printf(error,
472 "Missing required argument for option `--%s`",
473 long_opt_name);
474 goto error;
475 }
476
477 opt_arg = next_orig_arg;
478 used_next_orig_arg = true;
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
488 if (used_next_orig_arg) {
489 state->i += 2;
490 } else {
491 state->i += 1;
492 }
493
494 *item = &opt_item->base;
495 goto end;
496
497 error:
498 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
499 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
500 }
501
502 end:
503 return ret;
504 }
505
506 static
507 enum 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,
510 struct argpar_state *state,
511 char **error,
512 struct argpar_item **item)
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],
521 next_orig_arg, descrs, state, error, item);
522 } else {
523 /* Short option */
524 ret = parse_short_opts(&orig_arg[1],
525 next_orig_arg, descrs, state, error, item);
526 }
527
528 return ret;
529 }
530
531 static
532 bool 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
552 end:
553 return success;
554 }
555
556 ARGPAR_HIDDEN
557 struct 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
573 end:
574 return state;
575 }
576
577 ARGPAR_HIDDEN
578 void argpar_state_destroy(struct argpar_state *state)
579 {
580 free(state);
581 }
582
583 ARGPAR_HIDDEN
584 enum 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
642 end:
643 return status;
644 }
645
646 ARGPAR_HIDDEN
647 int argpar_state_get_ingested_orig_args(struct argpar_state *state)
648 {
649 return state->i;
650 }
651
652 ARGPAR_HIDDEN
653 struct 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 };
659 struct argpar_item *item = NULL;
660 struct argpar_state *state = NULL;
661
662 parse_ret.items = new_item_array();
663 if (!parse_ret.items) {
664 parse_ret.error = strdup("Failed to create items array.");
665 ARGPAR_ASSERT(parse_ret.error);
666 goto error;
667 }
668
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 }
675
676 while (true) {
677 enum argpar_state_parse_next_status status;
678
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) {
683 break;
684 } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) {
685 if (fail_on_unknown_opt) {
686 parse_ret.ingested_orig_args =
687 argpar_state_get_ingested_orig_args(state);
688 prepend_while_parsing_arg_to_error(
689 &parse_ret.error, parse_ret.ingested_orig_args,
690 argv[parse_ret.ingested_orig_args]);
691 status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR;
692 goto error;
693 }
694
695 free(parse_ret.error);
696 parse_ret.error = NULL;
697 break;
698 }
699
700 ARGPAR_ASSERT(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK);
701
702 if (!push_item(parse_ret.items, item)) {
703 goto error;
704 }
705 item = NULL;
706 }
707
708 ARGPAR_ASSERT(!parse_ret.error);
709 parse_ret.ingested_orig_args =
710 argpar_state_get_ingested_orig_args(state);
711 goto end;
712
713 error:
714 ARGPAR_ASSERT(parse_ret.error);
715
716 /* That's how we indicate that an error occurred */
717 destroy_item_array(parse_ret.items);
718 parse_ret.items = NULL;
719
720 end:
721 argpar_state_destroy(state);
722 argpar_item_destroy(item);
723 return parse_ret;
724 }
725
726 ARGPAR_HIDDEN
727 void 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.04184 seconds and 3 git commands to generate.