Bug Summary

File:.build-ci/../src/argconfig.c
Warning:line 343, column 12
Potential leak of memory pointed to by 'name'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-redhat-linux-gnu -O3 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name argconfig.c -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model static -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/__w/nvme-cli/nvme-cli/.build-ci -fcoverage-compilation-dir=/__w/nvme-cli/nvme-cli/.build-ci -resource-dir /usr/bin/../lib/clang/22 -include /__w/nvme-cli/nvme-cli/.build-ci/nvme-config.h -I nvme.p -I . -I .. -I src -I ../src -I ccan -I ../ccan -I libnvme/src -I ../libnvme/src -I /usr/include/json-c -D _FILE_OFFSET_BITS=64 -D _GNU_SOURCE -U NDEBUG -internal-isystem /usr/bin/../lib/clang/22/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/16/../../../../x86_64-redhat-linux/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -std=gnu11 -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcolor-diagnostics -vectorize-loops -vectorize-slp -analyzer-opt-analyze-headers -analyzer-output=html -faddrsig -fdwarf2-cfi-asm -o /__w/nvme-cli/nvme-cli/.build-ci/scan-results/2026-10-24-125112-639-1 -x c ../src/argconfig.c
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * This file is part of nvme-cli.
4 * Copyright (c) 2014 PMC-Sierra, Inc.
5 * Copyright (c) 2026 SUSE Software Solutions
6 *
7 * Authors: Logan Gunthorpe <logang@deltatee.com>
8 * Daniel Wagner <dwagner@suse.de>
9 */
10
11#include <ctype.h>
12#include <errno(*__errno_location ()).h>
13#include <inttypes.h>
14#include <limits.h>
15#include <locale.h>
16#include <stdarg.h>
17#include <stdbool.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21#include <unistd.h>
22
23#include <libnvme.h>
24
25#include <shared/parse-util.h>
26#include <shared/string-util.h>
27#include <shared/suffix-util.h>
28#include <shared/wrap-util.h>
29
30#include "argconfig.h"
31#include "cleanup.h"
32
33static bool_Bool is_null_or_empty(const char *s)
34{
35 return !s || !*s;
36}
37
38static const char *append_usage_str = "";
39
40/*
41 * Options every command accepts, registered once instead of being added to
42 * each command's table. They are looked up before the command's own
43 * options, so a global option means the same for every command; a command
44 * must not use their names.
45 */
46static struct argconfig_commandline_options *global_options;
47
48/* Forget the options given before, also those of included tables */
49static void argconfig_reset_seen(struct argconfig_commandline_options *s)
50{
51 for (; s->option; s++) {
52 if (s->config_type == CFG_INCLUDE)
53 argconfig_reset_seen(s->default_value);
54 s->seen = false0;
55 }
56}
57
58/* None of the global options counts as given yet */
59void argconfig_set_global_options(struct argconfig_commandline_options *options)
60{
61 global_options = options;
62 if (options)
63 argconfig_reset_seen(options);
64}
65
66struct argconfig_commandline_options *argconfig_get_global_options(void)
67{
68 return global_options;
69}
70
71/* The entries of a root, see argconfig_root() */
72#define ARGCONFIG_ROOT_LEN3 3
73
74/*
75 * The options a command accepts: its table @options and the global
76 * options, each as an include with the heading of the help. The global
77 * options are included last, as root[1]. The table of the global options
78 * themselves includes nothing else.
79 */
80static void argconfig_root(struct argconfig_commandline_options *root,
81 struct argconfig_commandline_options *options)
82{
83 struct argconfig_commandline_options r[ARGCONFIG_ROOT_LEN3] = {
84 OPT_INCLUDE(options, "Options"){"", 0, ((void*)0), CFG_INCLUDE, options, ARGCONFIG_ARG_NONE,
"Options", 0, ((void*)0)}
,
85 OPT_INCLUDE(global_options, "Global options"){"", 0, ((void*)0), CFG_INCLUDE, global_options, ARGCONFIG_ARG_NONE
, "Global options", 0, ((void*)0)}
,
86 OPT_END(){ ((void*)0) }
87 };
88 struct argconfig_commandline_options end[] = { OPT_END(){ ((void*)0) } };
89
90 memcpy(root, r, sizeof(r));
91 if (!global_options || options == global_options)
92 memcpy(&root[1], end, sizeof(end));
93}
94
95void argconfig_append_usage(const char *str)
96{
97 append_usage_str = str;
98}
99
100/* "nvme" and "id ctrl" of the current command, for the usage errors */
101static const char *command_prog;
102static const char *command_path;
103/* argv[0] of the current parse, if no command is set */
104static const char *parse_argv0 = "";
105
106void argconfig_set_command(const char *prog, const char *path)
107{
108 command_prog = prog;
109 command_path = path;
110}
111
112static const char *command_name(void)
113{
114 return command_path ? command_path : parse_argv0;
115}
116
117static void print_label(const char *name)
118{
119 fprintf(stderrstderr, "%s%s:%s ", argconfig_esc(stderrstderr, ARGCONFIG_RED"\033[31m"), name,
120 argconfig_esc(stderrstderr, ARGCONFIG_RESET"\033[0m"));
121}
122
123/* "<command>: " before an error of the current command, if one runs */
124void argconfig_print_error_label(void)
125{
126 if (command_path)
127 print_label(command_path);
128}
129
130/* An error of the current command, or of nvme-cli before one runs */
131void argconfig_error(const char *fmt, ...)
132{
133 va_list ap;
134
135 print_label(command_name());
136 va_start(ap, fmt)__builtin_va_start(ap, fmt);
137 vfprintf(stderrstderr, fmt, ap);
138 va_end(ap)__builtin_va_end(ap);
139 fprintf(stderrstderr, "\n");
140}
141
142/* The synopsis and where to find the help, after a usage error */
143void argconfig_print_usage(void)
144{
145 fprintf(stderrstderr, "Usage: %s\n", append_usage_str);
146 if (command_prog && command_path)
147 fprintf(stderrstderr, "Try '%s %s --help' for more information.\n",
148 command_prog, command_path);
149}
150
151/* A usage error of the current command, followed by its usage */
152int argconfig_usage_error(const char *fmt, ...)
153{
154 va_list ap;
155
156 print_label(command_name());
157 va_start(ap, fmt)__builtin_va_start(ap, fmt);
158 vfprintf(stderrstderr, fmt, ap);
159 va_end(ap)__builtin_va_end(ap);
160 fprintf(stderrstderr, "\n");
161 argconfig_print_usage();
162
163 return -EINVAL22;
164}
165
166/* The names an option accepts, below its description */
167static void show_value_names(FILE *out, const struct argconfig_opt_val *v)
168{
169 __cleanup_free__attribute__((cleanup(shr_freep))) char *names = NULL((void*)0);
170 size_t len = sizeof("\nNames:");
171 const struct argconfig_opt_val *n;
172 char *p;
173
174 if (!v || !v->str)
175 return;
176
177 for (n = v; n->str; n++)
178 len += strlen(n->str) + 2;
179
180 names = malloc(len);
181 if (!names)
182 return;
183
184 p = names + snprintf(names, len, "\nNames:");
185 for (n = v; n->str; n++)
186 p += snprintf(p, len - (p - names), " %s%s", n->str,
187 n[1].str ? "," : "");
188
189 shr_print_word_wrapped(names, 44, 44, out);
190}
191
192static void show_option(FILE *out,
193 const struct argconfig_commandline_options *option)
194{
195 const char *meta = option->meta ? option->meta : "arg";
196 enum argconfig_argument arg = option->argument_type;
197 char buffer[0x1000];
198 size_t len = 0;
199
200 buffer[0] = '\0';
201 shr_snprintf_append(buffer, sizeof(buffer), &len, " [ ");
202 if (option->option) {
203 shr_snprintf_append(buffer, sizeof(buffer), &len, " --%s",
204 option->option);
205 if (arg == ARGCONFIG_ARG_OPTIONAL)
206 shr_snprintf_append(buffer, sizeof(buffer), &len,
207 "[=<%s>]", meta);
208 if (arg == ARGCONFIG_ARG_REQUIRED)
209 shr_snprintf_append(buffer, sizeof(buffer), &len,
210 "=<%s>", meta);
211 if (option->short_option)
212 shr_snprintf_append(buffer, sizeof(buffer), &len, ",");
213 }
214 if (option->short_option) {
215 shr_snprintf_append(buffer, sizeof(buffer), &len, " -%c",
216 option->short_option);
217 if (arg == ARGCONFIG_ARG_OPTIONAL)
218 shr_snprintf_append(buffer, sizeof(buffer), &len,
219 " [<%s>]", meta);
220 if (arg == ARGCONFIG_ARG_REQUIRED)
221 shr_snprintf_append(buffer, sizeof(buffer), &len,
222 " <%s>", meta);
223 }
224 shr_snprintf_append(buffer, sizeof(buffer), &len, " ] ");
225
226 fprintf(out, "%s", buffer);
227 if (option->help) {
228 shr_print_word_wrapped("--- ", 40, len, out);
229 shr_print_word_wrapped(option->help, 44, 44, out);
230 show_value_names(out, option->opt_val);
231 }
232 fprintf(out, "\n");
233}
234
235/*
236 * Escape sequences for bold and colors go only to a terminal, and not
237 * when NO_COLOR is set to a non-empty value (https://no-color.org/).
238 */
239const char *argconfig_esc(FILE *out, const char *seq)
240{
241 const char *no_color = getenv("NO_COLOR");
242
243 if (!isatty(fileno(out)) || (no_color && *no_color))
244 return "";
245
246 return seq;
247}
248
249/* "WARNING:" on stderr, in yellow on a terminal */
250void argconfig_print_warning(void)
251{
252 fprintf(stderrstderr, "%sWARNING:%s", argconfig_esc(stderrstderr, ARGCONFIG_YELLOW"\033[33m"),
253 argconfig_esc(stderrstderr, ARGCONFIG_RESET"\033[0m"));
254}
255
256/* A heading, in bold on a terminal */
257void argconfig_print_heading(FILE *out, const char *fmt, const char *text)
258{
259 fputs(argconfig_esc(out, ARGCONFIG_BOLD"\033[1m"), out);
260 fprintf(out, fmt, text);
261 fputs(argconfig_esc(out, ARGCONFIG_RESET"\033[0m"), out);
262}
263
264/* A group header is printed before the first option which follows it */
265static void print_options(FILE *out, struct argconfig_commandline_options *s,
266 const char *header)
267{
268 bool_Bool header_printed = false0;
269
270 for (; s && s->option; s++) {
271 if (s->config_type == CFG_INCLUDE) {
272 print_options(out, s->default_value, s->help);
273 /* the own options after it get their heading again */
274 header_printed = false0;
275 continue;
276 }
277 if (s->config_type == CFG_GROUP_SEPARATOR) {
278 header = s->help;
279 header_printed = false0;
280 continue;
281 }
282 if (!header_printed) {
283 fputc('\n', out);
284 argconfig_print_heading(out, "%s:", header);
285 fputc('\n', out);
286 header_printed = true1;
287 }
288 if (!s->deprecated)
289 show_option(out, s);
290 }
291}
292
293static void show_help(FILE *out, const char *program_desc,
294 struct argconfig_commandline_options *root)
295{
296 argconfig_print_heading(out, "Usage: %s", append_usage_str);
297 fputs("\n\n", out);
298
299 shr_print_word_wrapped(program_desc, 0, 0, out);
300 fputc('\n', out);
301
302 print_options(out, root, "Options");
303}
304
305static bool_Bool help_requested;
306
307/* True once argconfig_parse() printed the help -h or --help asked for */
308bool_Bool argconfig_help_requested(void)
309{
310 return help_requested;
311}
312
313static bool_Bool argconfig_is_string(enum argconfig_types type)
314{
315 return type == CFG_STRING || type == CFG_FILE || type == CFG_DIR;
316}
317
318/* @u is in the range of @s, if it has one */
319bool_Bool argconfig_in_range(const struct argconfig_commandline_options *s,
320 uint64_t u)
321{
322 return !s->max || (u >= s->min && u <= s->max);
323}
324
325static struct argconfig_opt_val *
326argconfig_match_val(struct argconfig_opt_val *v, const char *str);
327
328/* The bits the names in the comma-separated list @arg stand for */
329static int argconfig_parse_enum_list(struct argconfig_commandline_options *s,
330 const char *arg, uint32_t *bits)
331{
332 __cleanup_free__attribute__((cleanup(shr_freep))) char *list = strdup(arg);
4
Memory is allocated
333 struct argconfig_opt_val *v;
334 char *p = list, *name;
335
336 if (!list)
5
Assuming 'list' is non-null
6
Taking false branch
337 return -ENOMEM12;
338
339 *bits = 0;
340 while ((name = shr_strsep(&p, ",")) != NULL((void*)0)) {
341 v = s->opt_val ? argconfig_match_val(s->opt_val, name) : NULL((void*)0);
7
Loop condition is true. Entering loop body
8
Assuming field 'opt_val' is null
9
'?' condition is false
342 if (!v
9.1
'v' is null
)
10
Taking true branch
343 return -EINVAL22;
11
Potential leak of memory pointed to by 'name'
344 *bits |= (uint32_t)v->val.int_val;
345 }
346
347 return 0;
348}
349
350/* An unsigned number up to @max, in the range of @s */
351static int argconfig_parse_u64(struct argconfig_commandline_options *s,
352 const char *arg, uint64_t max, uint64_t *u)
353{
354 int ret = shr_parse_u64(arg, max, u);
355
356 if (!ret && !argconfig_in_range(s, *u))
357 return -ERANGE34;
358
359 return ret;
360}
361
362static int argconfig_parse_type(struct argconfig_commandline_options *s,
363 char *arg)
364{
365 void *value = s->default_value;
366 uint64_t u;
367 int64_t i;
368 double d;
369 int ret = 0;
370
371 /* only a string option can do without a value */
372 if (!arg && !argconfig_is_string(s->config_type) &&
1
Assuming 'arg' is non-null
373 s->config_type != CFG_FLAG && s->config_type != CFG_INCREMENT)
374 return -EINVAL22;
375
376 switch (s->config_type) {
2
Control jumps to 'case CFG_ENUM_LIST:' at line 409
377 case CFG_STRING:
378 case CFG_FILE:
379 case CFG_DIR:
380 *(char **)value = arg;
381 break;
382 case CFG_INT:
383 ret = shr_parse_s64(arg, INT_MIN(-2147483647 -1), INT_MAX2147483647, &i);
384 if (!ret)
385 *(int *)value = i;
386 break;
387 case CFG_BYTE:
388 ret = argconfig_parse_u64(s, arg, UINT8_MAX(255), &u);
389 if (!ret)
390 *(uint8_t *)value = u;
391 break;
392 case CFG_SHORT:
393 ret = argconfig_parse_u64(s, arg, UINT16_MAX(65535), &u);
394 if (!ret)
395 *(uint16_t *)value = u;
396 break;
397 case CFG_POSITIVE:
398 ret = argconfig_parse_u64(s, arg, UINT32_MAX(4294967295U), &u);
399 if (!ret)
400 *(uint32_t *)value = u;
401 break;
402 case CFG_INCREMENT:
403 *(int *)value += 1;
404 break;
405 case CFG_ENUM:
406 /* only the names, which argconfig_parse_val() looked up */
407 ret = -EINVAL22;
408 break;
409 case CFG_ENUM_LIST: {
410 uint32_t bits;
411
412 ret = argconfig_parse_enum_list(s, arg, &bits);
3
Calling 'argconfig_parse_enum_list'
413 if (!ret)
414 *(uint32_t *)value |= bits;
415 break;
416 }
417 case CFG_LONG:
418 ret = argconfig_parse_u64(s, arg, UINT64_MAX(18446744073709551615UL), &u);
419 if (!ret)
420 *(uint64_t *)value = u;
421 break;
422 case CFG_LONG_SUFFIX: {
423 char *endptr;
424
425 /* the suffix parser takes whitespace and a sign */
426 if (!isdigit((unsigned char)*arg)((*__ctype_b_loc ())[(int) (((unsigned char)*arg))] & (unsigned
short int) _ISdigit)
)
427 return -EINVAL22;
428 ret = shr_suffix_binary_parse(arg, &endptr, &u);
429 if (!ret && !argconfig_in_range(s, u))
430 ret = -ERANGE34;
431 if (!ret)
432 *(uint64_t *)value = u;
433 break;
434 }
435 case CFG_DOUBLE:
436 ret = shr_parse_double(arg, &d);
437 if (!ret)
438 *(double *)value = d;
439 break;
440 case CFG_FLAG:
441 *(bool_Bool *)value = true1;
442 break;
443 case CFG_GROUP_SEPARATOR:
444 case CFG_INCLUDE:
445 break;
446 }
447
448 return ret;
449}
450
451static void argconfig_set_opt_val(enum argconfig_types type, union argconfig_val *opt_val, void *val)
452{
453 switch (type) {
454 case CFG_FLAG:
455 *(bool_Bool *)val = opt_val->bool_val;
456 break;
457 case CFG_LONG_SUFFIX:
458 *(uint64_t *)val = opt_val->long_suffix;
459 break;
460 case CFG_POSITIVE:
461 *(uint32_t *)val = opt_val->positive;
462 break;
463 case CFG_INT:
464 case CFG_ENUM:
465 *(int *)val = opt_val->int_val;
466 break;
467 case CFG_LONG:
468 *(uint64_t *)val = opt_val->long_val;
469 break;
470 case CFG_DOUBLE:
471 *(double *)val = opt_val->double_val;
472 break;
473 case CFG_BYTE:
474 *(uint8_t *)val = opt_val->byte;
475 break;
476 case CFG_SHORT:
477 *(uint16_t *)val = opt_val->short_val;
478 break;
479 case CFG_INCREMENT:
480 *(int *)val = opt_val->increment;
481 break;
482 case CFG_STRING:
483 case CFG_FILE:
484 case CFG_DIR:
485 *(char **)val = opt_val->string;
486 break;
487 case CFG_ENUM_LIST:
488 case CFG_GROUP_SEPARATOR:
489 case CFG_INCLUDE:
490 break;
491 }
492}
493
494static struct argconfig_opt_val *
495argconfig_match_val(struct argconfig_opt_val *v, const char *str)
496{
497 size_t len = strlen(str);
498 struct argconfig_opt_val *match = NULL((void*)0);
499
500 /* an empty value is a prefix of every name */
501 if (!len)
502 return NULL((void*)0);
503
504 for (; v->str; v++) {
505 if (strncasecmp(str, v->str, len))
506 continue;
507
508 if (len == strlen(v->str))
509 return v;
510
511 if (match)
512 return NULL((void*)0); /* multiple matches; input is ambiguous */
513
514 match = v;
515 }
516
517 return match;
518}
519
520/*
521 * Store the value which @str names in the value table of @s. False if
522 * the option has no value of that name.
523 */
524bool_Bool argconfig_set_value_name(struct argconfig_commandline_options *s,
525 const char *str)
526{
527 struct argconfig_opt_val *v = s->opt_val;
528
529 if (v)
530 v = argconfig_match_val(v, str);
531 if (!v)
532 return false0;
533
534 argconfig_set_opt_val(v->type, &v->val, s->default_value);
535 return true1;
536}
537
538static int argconfig_parse_val(struct argconfig_commandline_options *s,
539 char *arg)
540{
541 /* the names of a list are looked up one by one */
542 if (arg && s->config_type != CFG_ENUM_LIST &&
543 argconfig_set_value_name(s, arg))
544 return 0;
545
546 return argconfig_parse_type(s, arg);
547}
548
549/* The non-option arguments of the last successful argconfig_parse() */
550static struct {
551 char **argv;
552 int argc;
553} operands;
554
555int argconfig_nr_operands(void)
556{
557 return operands.argc;
558}
559
560char *argconfig_operand(int i)
561{
562 if (i < 0 || i >= operands.argc)
563 return NULL((void*)0);
564
565 return operands.argv[i];
566}
567
568/*
569 * The state of one parse. argv is not modified: the operands are collected
570 * in their own array, in the order they are given.
571 */
572struct argconfig_parser {
573 int argc;
574 char **argv;
575 int ind;
576 /* the table of the command, the global options included */
577 struct argconfig_commandline_options root[ARGCONFIG_ROOT_LEN3];
578 /*
579 * Global options stop at the first operand, the subcommand. Their
580 * implicit -h, --help, -V and --version are recorded and the parse
581 * goes on.
582 */
583 bool_Bool global;
584 bool_Bool help;
585 bool_Bool version;
586 char **operands;
587 int nr_operands;
588};
589
590/*
591 * Internal result of argconfig_parse_args(): an option was malformed. Like
592 * ARGCONFIG_HELP, it prints the help of a command.
593 */
594#define ARGCONFIG_USAGE4 4
595
596/*
597 * The parse errors use the wording of glibc's getopt, which printed them
598 * before argconfig had its own parser.
599 */
600static int argconfig_parse_error(struct argconfig_parser *p,
601 const char *fmt, ...)
602{
603 va_list ap;
604
605 print_label(command_name());
606 va_start(ap, fmt)__builtin_va_start(ap, fmt);
607 vfprintf(stderrstderr, fmt, ap);
608 va_end(ap)__builtin_va_end(ap);
609 fprintf(stderrstderr, "\n");
610
611 return ARGCONFIG_USAGE4;
612}
613
614static struct argconfig_commandline_options *
615argconfig_find_long(struct argconfig_commandline_options *s,
616 const char *name, size_t len)
617{
618 struct argconfig_commandline_options *o;
619
620 for (; s->option; s++) {
621 if (s->config_type == CFG_INCLUDE) {
622 o = argconfig_find_long(s->default_value, name, len);
623 if (o)
624 return o;
625 continue;
626 }
627 if (s->config_type == CFG_GROUP_SEPARATOR ||
628 is_null_or_empty(s->option))
629 continue;
630 if (strlen(s->option) == len && !strncmp(s->option, name, len))
631 return s;
632 if (s->old_name && strlen(s->old_name) == len &&
633 !strncmp(s->old_name, name, len))
634 return s;
635 }
636
637 return NULL((void*)0);
638}
639
640static struct argconfig_commandline_options *
641argconfig_find_short(struct argconfig_commandline_options *s, char c)
642{
643 struct argconfig_commandline_options *o;
644
645 for (; s->option; s++) {
646 if (s->config_type == CFG_INCLUDE) {
647 o = argconfig_find_short(s->default_value, c);
648 if (o)
649 return o;
650 continue;
651 }
652 if (s->config_type == CFG_GROUP_SEPARATOR)
653 continue;
654 if (s->short_option == c)
655 return s;
656 }
657
658 return NULL((void*)0);
659}
660
661/*
662 * The option of @root with the long name @name, a global option first: a
663 * global option is the same for every command. &root[1] is the include
664 * of the global options alone.
665 */
666static struct argconfig_commandline_options *
667argconfig_find_long_any(struct argconfig_commandline_options *root,
668 const char *name, size_t len)
669{
670 struct argconfig_commandline_options *o;
671
672 o = argconfig_find_long(&root[1], name, len);
673
674 return o ? o : argconfig_find_long(root, name, len);
675}
676
677static struct argconfig_commandline_options *
678argconfig_find_short_any(struct argconfig_commandline_options *root, char c)
679{
680 struct argconfig_commandline_options *o;
681
682 o = argconfig_find_short(&root[1], c);
683
684 return o ? o : argconfig_find_short(root, c);
685}
686
687/* What a value of @s has to look like */
688static const char *argconfig_expected(struct argconfig_commandline_options *s)
689{
690 static char range[64];
691
692 if (s->max) {
693 snprintf(range, sizeof(range), "a number from %" PRIu64"l" "u"
694 " to %" PRIu64"l" "u", s->min, s->max);
695 return range;
696 }
697
698 switch (s->config_type) {
699 case CFG_BYTE:
700 return "a number from 0 to 255";
701 case CFG_SHORT:
702 return "a number from 0 to 65535";
703 case CFG_POSITIVE:
704 return "a number from 0 to 4294967295";
705 case CFG_LONG:
706 return "a number from 0 to 18446744073709551615";
707 case CFG_LONG_SUFFIX:
708 return "a size such as 4096 or 4Ki";
709 default:
710 return "a number";
711 }
712}
713
714/* @arg is no value of @s, given by its short name @c or its long name */
715static int argconfig_value_error(struct argconfig_commandline_options *s,
716 char c, const char *arg)
717{
718 __cleanup_free__attribute__((cleanup(shr_freep))) char *e = arg ? shr_escape(arg) : NULL((void*)0);
719 bool_Bool list = s->config_type == CFG_ENUM_LIST;
720 const struct argconfig_opt_val *v;
721 int n = 0;
722
723 print_label(command_name());
724 fprintf(stderrstderr, "invalid value '%s' for ", e ? e : "");
725 if (c)
726 fprintf(stderrstderr, "-%c", c);
727 else
728 fprintf(stderrstderr, "--%s", s->option);
729 fprintf(stderrstderr, ": expected ");
730 if (list)
731 fprintf(stderrstderr, "a comma-separated list of ");
732 else if (s->config_type != CFG_ENUM)
733 fprintf(stderrstderr, "%s%s", argconfig_expected(s),
734 s->opt_val ? " or " : "");
735
736 /* A long list of names would drown the error, the help has them */
737 for (v = s->opt_val; v && v->str; v++)
738 n++;
739 if (n > 8)
740 fprintf(stderrstderr, "%sthe names in the help",
741 list ? "" : "one of ");
742 for (v = s->opt_val; n <= 8 && v && v->str; v++)
743 fprintf(stderrstderr, "%s%s", v != s->opt_val ? ", " :
744 list ? "" : "one of: ", v->str);
745 fprintf(stderrstderr, "\n");
746
747 /* "010" was octal once, say what to write instead */
748 if (arg && !list && s->config_type != CFG_ENUM &&
749 shr_has_leading_zero(arg)) {
750 const char *d = arg;
751
752 while (d[0] == '0' && d[1])
753 d++;
754 fprintf(stderrstderr,
755 "a number must not start with 0: write %s, or 0x%s for hexadecimal\n",
756 d, d);
757 }
758
759 return ARGCONFIG_USAGE4;
760}
761
762/* @s given by its short name @c, or by its long name without, and @arg */
763static int argconfig_apply(struct argconfig_commandline_options *s, char c,
764 char *arg)
765{
766 int ret;
767
768 if (s->deprecated && !s->seen) {
769 argconfig_print_warning();
770 if (c)
771 fprintf(stderrstderr, " '-%c'", c);
772 else
773 fprintf(stderrstderr, " '--%s'", s->option);
774 fprintf(stderrstderr,
775 " is deprecated and will be removed in the next major version, use '%s' instead\n",
776 s->deprecated);
777 }
778 s->seen = true1;
779
780 if (!s->default_value)
781 return 0;
782
783 ret = argconfig_parse_val(s, arg);
784 if (ret == -EINVAL22 || ret == -ERANGE34)
785 return argconfig_value_error(s, c, arg);
786
787 return ret;
788}
789
790/*
791 * A copy of the template table @tmpl, whose options store into the
792 * struct at @tmpl_base of @size bytes, bound to the struct of the same
793 * type at @base. Commands share the template and parse into their own
794 * struct. The caller frees the copy.
795 */
796struct argconfig_commandline_options *
797argconfig_bind(const struct argconfig_commandline_options *tmpl,
798 const void *tmpl_base, size_t size, void *base)
799{
800 struct argconfig_commandline_options *dst;
801 uintptr_t start = (uintptr_t)tmpl_base;
802 uintptr_t v;
803 size_t n = 0, i;
804
805 while (tmpl[n].option)
806 n++;
807
808 dst = calloc(n + 1, sizeof(*dst));
809 if (!dst)
810 return NULL((void*)0);
811 memcpy(dst, tmpl, (n + 1) * sizeof(*dst));
812
813 for (i = 0; i < n; i++) {
814 v = (uintptr_t)tmpl[i].default_value;
815 if (v >= start && v < start + size)
816 dst[i].default_value = (char *)base + (v - start);
817 }
818
819 return dst;
820}
821
822/* The name of the enum value @val in the value table @v, or NULL */
823const char *argconfig_value_name(const struct argconfig_opt_val *v, int val)
824{
825 for (; v && v->str; v++) {
826 if (v->type == CFG_ENUM && v->val.int_val == val)
827 return v->str;
828 }
829
830 return NULL((void*)0);
831}
832
833/* The value of a required argument which is not attached to the option */
834static char *argconfig_next_arg(struct argconfig_parser *p)
835{
836 if (p->ind + 1 >= p->argc)
837 return NULL((void*)0);
838
839 return p->argv[++p->ind];
840}
841
842/* "--name", "--name=value" or "--name value" */
843static int argconfig_parse_long(struct argconfig_parser *p, char *arg)
844{
845 struct argconfig_commandline_options *s;
846 char *name = arg + 2, *value = NULL((void*)0);
847 char *eq = strchr(name, '=')_Generic (0 ? (name) : (void *) 1, const void *: (const char *
) (strchr (name, '=')), default: strchr (name, '='))
;
848 size_t len = eq ? (size_t)(eq - name) : strlen(name);
849
850 s = argconfig_find_long_any(p->root, name, len);
851 if (!s) {
852 bool_Bool help = len == 4 && !strncmp(name, "help", 4);
853 bool_Bool version = len == 7 && !strncmp(name, "version", 7);
854
855 if (help && !p->global)
856 return ARGCONFIG_HELP1;
857 if (!p->global || (!help && !version)) {
858 __cleanup_free__attribute__((cleanup(shr_freep))) char *e = shr_escape(arg);
859
860 return argconfig_parse_error(p,
861 "unrecognized option '%s'", e ? e : "");
862 }
863 if (eq)
864 return argconfig_parse_error(p,
865 "option '--%.*s' doesn't allow an argument",
866 (int)len, name);
867 p->help |= help;
868 p->version |= version;
869 return 0;
870 }
871
872 switch (s->argument_type) {
873 case ARGCONFIG_ARG_NONE:
874 if (eq)
875 return argconfig_parse_error(p,
876 "option '--%s' doesn't allow an argument",
877 s->option);
878 break;
879 case ARGCONFIG_ARG_REQUIRED:
880 value = eq ? eq + 1 : argconfig_next_arg(p);
881 if (!value)
882 return argconfig_parse_error(p,
883 "option '--%s' requires an argument",
884 s->option);
885 break;
886 case ARGCONFIG_ARG_OPTIONAL:
887 /* Only an attached value, "--name value" is an operand */
888 if (eq)
889 value = eq + 1;
890 break;
891 }
892
893 if (s->old_name && !s->seen &&
894 (strlen(s->option) != len || strncmp(s->option, name, len))) {
895 argconfig_print_warning();
896 fprintf(stderrstderr,
897 " '--%s' is deprecated and will be removed in the next major version, use '--%s' instead\n",
898 s->old_name, s->option);
899 }
900
901 return argconfig_apply(s, 0, value);
902}
903
904/* "-a", "-abc", "-ovalue", "-o value" */
905static int argconfig_parse_short(struct argconfig_parser *p, char *arg)
906{
907 struct argconfig_commandline_options *s;
908 char *c, *value;
909 int ret;
910
911 for (c = arg + 1; *c; c++) {
912 if (!p->global && (*c == 'h' || *c == '?'))
913 return ARGCONFIG_HELP1;
914
915 s = argconfig_find_short_any(p->root, *c);
916 if (!s && p->global && (*c == 'h' || *c == 'V')) {
917 p->help |= *c == 'h';
918 p->version |= *c == 'V';
919 continue;
920 }
921 if (!s) {
922 char bad[2] = { *c, '\0' };
923 __cleanup_free__attribute__((cleanup(shr_freep))) char *e = shr_escape(bad);
924
925 return argconfig_parse_error(p,
926 "invalid option -- '%s'", e ? e : "");
927 }
928
929 value = NULL((void*)0);
930 switch (s->argument_type) {
931 case ARGCONFIG_ARG_NONE:
932 break;
933 case ARGCONFIG_ARG_REQUIRED:
934 value = c[1] ? c + 1 : argconfig_next_arg(p);
935 if (!value)
936 return argconfig_parse_error(p,
937 "option requires an argument -- '%c'",
938 *c);
939 break;
940 case ARGCONFIG_ARG_OPTIONAL:
941 if (c[1])
942 value = c + 1;
943 break;
944 }
945
946 ret = argconfig_apply(s, *c, value);
947 if (ret || s->argument_type != ARGCONFIG_ARG_NONE)
948 return ret;
949 }
950
951 return 0;
952}
953
954/*
955 * Options and operands may be given in any order, "--" ends the options.
956 * Returns 0, ARGCONFIG_HELP, ARGCONFIG_USAGE or a negative errno from the
957 * value conversion.
958 */
959static int argconfig_parse_args(struct argconfig_parser *p)
960{
961 char *arg;
962 int ret;
963
964 /*
965 * The global options keep theirs: they may have been given before
966 * the command name.
967 */
968 argconfig_reset_seen(p->root[0].default_value);
969
970 /* argv ends at argc or at a NULL, whichever comes first */
971 for (p->ind = 1; p->ind < p->argc && p->argv[p->ind]; p->ind++) {
972 arg = p->argv[p->ind];
973
974 if (!strcmp(arg, "--")) {
975 p->ind++;
976 if (p->global)
977 return 0;
978 while (p->ind < p->argc && p->argv[p->ind])
979 p->operands[p->nr_operands++] =
980 p->argv[p->ind++];
981 return 0;
982 }
983
984 if (arg[0] != '-' || !arg[1]) {
985 if (p->global)
986 return 0;
987 p->operands[p->nr_operands++] = arg;
988 continue;
989 }
990
991 if (arg[1] == '-')
992 ret = argconfig_parse_long(p, arg);
993 else
994 ret = argconfig_parse_short(p, arg);
995 if (ret)
996 return ret;
997 }
998
999 return 0;
1000}
1001
1002static argconfig_parse_hook_fn argconfig_parse_hook;
1003
1004void argconfig_set_parse_hook(argconfig_parse_hook_fn hook)
1005{
1006 argconfig_parse_hook = hook;
1007}
1008
1009static bool_Bool given_in(struct argconfig_commandline_options *s,
1010 const void *value)
1011{
1012 bool_Bool given = false0;
1013
1014 for (; s && s->option; s++) {
1015 if (s->config_type == CFG_INCLUDE)
1016 given |= given_in(s->default_value, value);
1017 else
1018 given |= s->default_value == value && s->seen;
1019 }
1020
1021 return given;
1022}
1023
1024/* --verbose, or an alias of it such as --human-readable, was given */
1025static bool_Bool argconfig_check_verbose(struct argconfig_commandline_options *root)
1026{
1027 struct argconfig_commandline_options *v;
1028
1029 v = argconfig_find_long_any(root, "verbose", strlen("verbose"));
1030 if (!v || v->config_type != CFG_INCREMENT)
1031 return false0;
1032
1033 return given_in(root, v->default_value);
1034}
1035
1036int argconfig_parse(int argc, char *argv[], const char *program_desc,
1037 struct argconfig_commandline_options *options)
1038{
1039 static char **operand_buf;
1040 struct argconfig_parser p = {
1041 .argc = argc,
1042 .argv = argv,
1043 };
1044 int ret;
1045
1046 argconfig_root(p.root, options);
1047 parse_argv0 = argc > 0 ? argv[0] : "";
1048 free(operand_buf);
1049 operand_buf = NULL((void*)0);
1050 operands.argv = NULL((void*)0);
1051 operands.argc = 0;
1052
1053 if (argconfig_parse_hook)
1054 return argconfig_parse_hook(argc, argv, program_desc, p.root);
1055
1056 p.operands = calloc(argc > 0 ? argc : 1, sizeof(*p.operands));
1057 if (!p.operands)
1058 return -ENOMEM12;
1059
1060 ret = argconfig_parse_args(&p);
1061 if (ret) {
1062 /*
1063 * The help that was asked for goes to stdout, a usage error is
1064 * followed by the synopsis on stderr. The command stops
1065 * either way: it gets ARGCONFIG_HELP or an error.
1066 */
1067 if (ret == ARGCONFIG_HELP1) {
1068 show_help(stdoutstdout, program_desc, p.root);
1069 help_requested = true1;
1070 } else if (ret > 0) {
1071 argconfig_print_usage();
1072 ret = -EINVAL22;
1073 }
1074 free(p.operands);
1075 } else {
1076 operand_buf = p.operands;
1077 operands.argv = p.operands;
1078 operands.argc = p.nr_operands;
1079 }
1080
1081 if (!argconfig_check_verbose(p.root))
1082 setlocale(LC_ALL6, "C");
1083
1084 return ret;
1085}
1086
1087/*
1088 * Parse global (pre-subcommand) options from argc/argv. Stops at the first
1089 * non-option argument (i.e. the subcommand name) so that subcommand-specific
1090 * options are left in place for the subcommand dispatcher. After a successful
1091 * call, *argind is the index of the first non-option argument (the
1092 * subcommand), or argc if there is none.
1093 *
1094 * Unless the table has its own, -h, --help, -V and --version are accepted
1095 * like a global option. Returns ARGCONFIG_HELP and ARGCONFIG_VERSION for
1096 * them, 0 otherwise, or a negative errno on failure. @options may be NULL.
1097 */
1098int argconfig_parse_global(int argc, char *argv[],
1099 struct argconfig_commandline_options *options,
1100 int *argind)
1101{
1102 static struct argconfig_commandline_options no_options[] = {
1103 OPT_END(){ ((void*)0) }
1104 };
1105 struct argconfig_parser p = {
1106 .argc = argc,
1107 .argv = argv,
1108 .global = true1,
1109 };
1110 int ret;
1111
1112 argconfig_root(p.root, options ? options : no_options);
1113
1114 parse_argv0 = argc > 0 ? argv[0] : "";
1115 ret = argconfig_parse_args(&p);
1116 *argind = p.ind;
1117 if (ret)
1118 return ret > 0 ? -EINVAL22 : ret;
1119
1120 return (p.help ? ARGCONFIG_HELP1 : 0) |
1121 (p.version ? ARGCONFIG_VERSION2 : 0);
1122}
1123
1124/*
1125 * True if an option which stores to @value was given, in the table or in
1126 * the global options. Aliases share the variable, so any of them counts.
1127 */
1128bool_Bool argconfig_given(struct argconfig_commandline_options *s,
1129 const void *value)
1130{
1131 struct argconfig_commandline_options root[ARGCONFIG_ROOT_LEN3];
1132
1133 if (!value)
1134 return false0;
1135 argconfig_root(root, s);
1136
1137 return given_in(root, value);
1138}