Bug Summary

File:.build-ci/../plugins/utils/command-metadata.c
Warning:line 579, column 10
Potential leak of memory pointed to by 'model'

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 command-metadata.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 ../plugins/utils/command-metadata.c
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (c) 2026 Micron Technology, Inc.
4 *
5 * Command/option metadata dump for nvme-cli.
6 *
7 * Builds an in-memory model of every command and its options, then writes it to
8 * stdout as JSON for the `dump-command-metadata` subcommand. The JSON is a
9 * machine-readable description of the CLI surface, intended for tooling such as
10 * shell-completion generators, documentation, and drift checks.
11 *
12 * The model is captured by walking the live plugin/command tree and, for each
13 * command, intercepting the options array it builds on its stack.
14 * Capture installs a hook in argconfig_parse() (see argconfig_set_parse_hook):
15 * when a command calls into the parser, the hook copies the options array into
16 * the model and returns a sentinel so the command unwinds before opening any
17 * device.
18 */
19
20#include <assert.h>
21#include <errno(*__errno_location ()).h>
22#include <fcntl.h>
23#include <signal.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <unistd.h>
28#ifdef _WIN32
29#include <windows.h>
30#endif
31
32#include <shared/fs-util.h>
33#include <shared/string-util.h>
34
35#include "command-metadata.h"
36#include "nvme-json.h"
37#include "plugin.h"
38
39/*
40 * The whole command is JSON-only, so it is compiled out entirely without
41 * json-c support: the utils plugin does not register it and does not define
42 * its handler, so dump_command_metadata() is never referenced.
43 */
44#ifdef CONFIG_JSONC
45
46/*
47 * Returned by the capture hook so argconfig_parse() unwinds before the
48 * command's fn opens a device.
49 */
50#define METADATA_CAPTURE_SENTINEL(-125) (-ECANCELED125)
51
52/*
53 * Version of the emitted JSON schema, bumped on any breaking change to the
54 * output structure (renamed/removed keys, changed value semantics). Additive
55 * changes that keep existing keys stable do not require a bump. Consumers
56 * should reject a major version they do not understand.
57 */
58#define COMMAND_METADATA_SCHEMA_VERSION2 2
59
60/*
61 * The command currently being captured; set by capture_command()
62 * before it invokes the command fn, read by metadata_capture_hook().
63 */
64static struct command_metadata_command *command_metadata_cur_command;
65
66/*
67 * Capture OOM, reported out-of-band because the hook's return value is reserved
68 * for the parser-unwind sentinel; checked after each command fn returns.
69 */
70static int command_metadata_capture_error;
71
72/*
73 * capture_saved_stderr_fd holds a dup of stderr from before the NUL redirect,
74 * so crash messages still reach the user.
75 */
76static const char *capture_current_command;
77static int capture_saved_stderr_fd = -1;
78
79/*
80 * (void)write() does not suppress GCC's warn_unused_result under
81 * _FORTIFY_SOURCE; assigning to a discarded variable does.
82 */
83static void write_raw(int fd, const char *buf, size_t len)
84{
85 ssize_t ret = write(fd, buf, len);
86 (void)ret;
87}
88
89/* strlen() is not async-signal-safe; measure inline for the crash handler. */
90static void write_str(int fd, const char *s)
91{
92 size_t len = 0;
93
94 if (!s)
95 return;
96
97 while (s[len])
98 len++;
99
100 write_raw(fd, s, len);
101}
102
103static void write_uint(int fd, unsigned int n)
104{
105 char buf[10]; /* enough for any 32-bit unsigned int */
106 int i = sizeof(buf);
107
108 do {
109 buf[--i] = '0' + (n % 10);
110 n /= 10;
111 } while (n && i);
112
113 write_raw(fd, buf + i, sizeof(buf) - i);
114}
115
116static void capture_crash_handler(int sig)
117{
118 int fd = capture_saved_stderr_fd >= 0 ? capture_saved_stderr_fd : STDERR_FILENO2;
119
120 write_str(fd, "dump-command-metadata: fatal: '");
121 write_str(fd, capture_current_command ? capture_current_command : "(unknown)");
122 write_str(fd, "' crashed during option capture (signal ");
123 write_uint(fd, sig);
124 write_str(fd, ")\n");
125
126 signal(sig, SIG_DFL((__sighandler_t) 0));
127 raise(sig);
128}
129
130/*
131 * On Windows, OS-level exceptions (stack overflow, access violations) bypass
132 * the C signal() mechanism entirely and terminate the process silently.
133 * SetUnhandledExceptionFilter intercepts these so we can print the same
134 * diagnostic before dying.
135 */
136#ifdef _WIN32
137static void write_hex(int fd, unsigned int n)
138{
139 char buf[8];
140 int i = sizeof(buf);
141
142 do {
143 buf[--i] = "0123456789abcdef"[n & 0xf];
144 n >>= 4;
145 } while (n && i);
146
147 write_raw(fd, buf + i, sizeof(buf) - i);
148}
149
150static LONG WINAPI capture_exception_filter(EXCEPTION_POINTERS *ep)
151{
152 int fd = capture_saved_stderr_fd >= 0 ? capture_saved_stderr_fd : STDERR_FILENO2;
153
154 write_str(fd, "dump-command-metadata: fatal: '");
155 write_str(fd, capture_current_command ? capture_current_command : "(unknown)");
156 write_str(fd, "' crashed during option capture (exception 0x");
157 write_hex(fd, ep->ExceptionRecord->ExceptionCode);
158 write_str(fd, ")\n");
159
160 return EXCEPTION_CONTINUE_SEARCH;
161}
162
163static LPTOP_LEVEL_EXCEPTION_FILTER capture_prev_filter;
164#endif
165
166static void (*capture_prev_sigsegv)(int);
167static void (*capture_prev_sigabrt)(int);
168#ifdef SIGBUS7
169static void (*capture_prev_sigbus)(int);
170#endif
171
172/* Normalize SIG_ERR to SIG_DFL so restoration is always a valid disposition. */
173static void (*set_crash_handler(int sig))(int)
174{
175 void (*prev)(int) = signal(sig, capture_crash_handler);
176
177 return prev == SIG_ERR((__sighandler_t) -1) ? SIG_DFL((__sighandler_t) 0) : prev;
178}
179
180static void install_crash_handlers(void)
181{
182 capture_prev_sigsegv = set_crash_handler(SIGSEGV11);
183 capture_prev_sigabrt = set_crash_handler(SIGABRT6);
184#ifdef SIGBUS7
185 capture_prev_sigbus = set_crash_handler(SIGBUS7);
186#endif
187#ifdef _WIN32
188 capture_prev_filter = SetUnhandledExceptionFilter(capture_exception_filter);
189#endif
190}
191
192static void remove_crash_handlers(void)
193{
194 signal(SIGSEGV11, capture_prev_sigsegv);
195 signal(SIGABRT6, capture_prev_sigabrt);
196#ifdef SIGBUS7
197 signal(SIGBUS7, capture_prev_sigbus);
198#endif
199#ifdef _WIN32
200 SetUnhandledExceptionFilter(capture_prev_filter);
201 capture_prev_filter = NULL((void*)0);
202#endif
203}
204
205/* ------------------------------------------------------------------ */
206/* Pass 1: capture */
207/* ------------------------------------------------------------------ */
208
209static char *xstrdup(const char *s)
210{
211 return s ? strdup(s) : NULL((void*)0);
212}
213
214/*
215 * Deep-copy an opt_val table into *out. A NULL src is not an error: *out is set
216 * to NULL, the "no value table" sentinel consumers expect. Returns -ENOMEM on
217 * allocation failure so the caller can abort rather than silently drop values.
218 */
219static int copy_opt_val(const struct argconfig_opt_val *src, const struct argconfig_opt_val **out)
220{
221 struct argconfig_opt_val *dst;
222 int n = 0, i;
223
224 *out = NULL((void*)0);
225 if (!src)
226 return 0;
227
228 for (; src[n].str; n++)
229 ;
230
231 dst = calloc(n + 1, sizeof(*dst));
232 if (!dst)
233 return -ENOMEM12;
234
235 for (i = 0; i < n; i++) {
236 dst[i] = src[i];
237 /* src[i].str is non-NULL for i < n, so NULL here means OOM. */
238 dst[i].str = strdup(src[i].str);
239 if (!dst[i].str) {
240 while (--i >= 0)
241 free((char *)dst[i].str);
242 free(dst);
243 return -ENOMEM12;
244 }
245 }
246 dst[n].str = NULL((void*)0);
247
248 *out = dst;
249 return 0;
250}
251
252/*
253 * Duplicate a possibly-NULL string: NULL src succeeds (copies to NULL); a
254 * non-NULL src that fails to duplicate returns -ENOMEM.
255 */
256static int dup_field(const char *src, const char **dst)
257{
258 *dst = xstrdup(src);
259 if (src && !*dst)
260 return -ENOMEM12;
261 return 0;
262}
263
264/* Free the deep-copied fields of one option entry (safe on a partial entry). */
265static void free_option_fields(struct command_metadata_option *o)
266{
267 free((char *)o->option);
268 free((char *)o->meta);
269 free((char *)o->help);
270 free((char *)o->deprecated);
271 free((char *)o->old_name);
272 if (o->opt_val) {
273 const struct argconfig_opt_val *v;
274
275 for (v = o->opt_val; v->str; v++)
276 free((char *)v->str);
277 free((void *)o->opt_val);
278 }
279}
280
281/*
282 * Deep-copy an options array into *out (a heap array of *n_out entries).
283 * Returns -ENOMEM on failure; the partial allocation is left for process exit
284 * to reclaim, as the model is never explicitly freed.
285 */
286static int copy_options(const struct argconfig_commandline_options *opts,
287 struct command_metadata_option **out, size_t *n_out)
288{
289 const struct argconfig_commandline_options *s;
290 struct command_metadata_option *dst;
291 size_t n = 0, i;
292
293 *out = NULL((void*)0);
294 *n_out = 0;
295
296 for (s = opts; s->option; s++)
297 n++;
298
299 if (!n) /* calloc(0) may return NULL, indistinguishable from OOM */
300 return 0;
301
302 dst = calloc(n, sizeof(*dst));
303 if (!dst)
304 return -ENOMEM12;
305
306 /*
307 * Deep-copy: option/meta/help and the opt_val table are valid while the
308 * parser runs but may point at command-local storage that is freed once
309 * the command's fn returns, so duplicate rather than alias them.
310 */
311 for (i = 0; i < n; i++) {
312 if (dup_field(opts[i].option, &dst[i].option) ||
313 dup_field(opts[i].meta, &dst[i].meta) ||
314 dup_field(opts[i].help, &dst[i].help) ||
315 dup_field(opts[i].deprecated, &dst[i].deprecated) ||
316 dup_field(opts[i].old_name, &dst[i].old_name) ||
317 copy_opt_val(opts[i].opt_val, &dst[i].opt_val)) {
318 size_t j;
319
320 for (j = 0; j <= i; j++)
321 free_option_fields(&dst[j]);
322 free(dst);
323 return -ENOMEM12;
324 }
325 dst[i].short_option = opts[i].short_option;
326 dst[i].config_type = opts[i].config_type;
327 dst[i].argument_type = opts[i].argument_type;
328 dst[i].hidden = opts[i].deprecated;
329 dst[i].min = opts[i].min;
330 dst[i].max = opts[i].max;
331 }
332
333 *out = dst;
334 *n_out = n;
335 return 0;
336}
337
338/* The entries of @opts with the included tables in place, see flat_copy() */
339static size_t flat_count(struct argconfig_commandline_options *opts)
340{
341 size_t n = 0;
342
343 for (; opts->option; opts++) {
344 if (opts->config_type == CFG_INCLUDE)
345 n += flat_count(opts->default_value);
346 n++;
347 }
348
349 return n;
350}
351
352/* Copy @opts to @dst, an included table behind a group of its heading */
353static size_t flat_copy(struct argconfig_commandline_options *dst,
354 struct argconfig_commandline_options *opts)
355{
356 size_t n = 0;
357
358 for (; opts->option; opts++) {
359 if (opts->config_type == CFG_INCLUDE) {
360 struct argconfig_commandline_options sep =
361 OPT_GROUP(opts->help){"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)0), ARGCONFIG_ARG_NONE
, opts->help, 0, ((void*)0)}
;
362
363 memcpy(&dst[n++], &sep, sizeof(sep));
364 n += flat_copy(&dst[n], opts->default_value);
365 continue;
366 }
367 memcpy(&dst[n++], opts, sizeof(*opts));
368 }
369
370 return n;
371}
372
373/*
374 * The options of @root, the included tables behind a group of their
375 * heading: json_command() takes the options after "Global options" for the
376 * global ones. The caller frees the returned array; the entries alias the
377 * original tables.
378 */
379static struct argconfig_commandline_options *
380flatten(struct argconfig_commandline_options *root)
381{
382 struct argconfig_commandline_options *all;
383
384 all = calloc(flat_count(root) + 1, sizeof(*all));
385 if (all)
386 flat_copy(all, root);
387
388 return all;
389}
390
391/*
392 * argconfig_parse() hook installed by build_model(): copies the current
393 * command's options, the global ones included, into the model, then
394 * returns the sentinel so the parser unwinds before the command opens a
395 * device.
396 */
397static int metadata_capture_hook(int argc, char **argv, const char *program_desc,
398 struct argconfig_commandline_options *root)
399{
400 (void)argc;
401 (void)argv;
402 (void)program_desc;
403
404 if (command_metadata_cur_command && !command_metadata_cur_command->captured) {
405 __cleanup_free__attribute__((cleanup(shr_freep))) struct argconfig_commandline_options *all =
406 flatten(root);
407 int err = -ENOMEM12;
408
409 if (all)
410 err = copy_options(all,
411 &command_metadata_cur_command->options,
412 &command_metadata_cur_command->num_options);
413 if (err)
414 command_metadata_capture_error = err;
415 command_metadata_cur_command->captured = true1;
416 }
417
418 return METADATA_CAPTURE_SENTINEL(-125);
419}
420
421/* Returns 0 on success, or -ENOMEM if the capture hook failed to allocate. */
422static int capture_command(struct command_metadata_command *mc, struct command *cmd,
423 struct plugin *plugin)
424{
425 /*
426 * argv[1] is a placeholder device; the sentinel returns before it is
427 * ever opened, so it need not (and must not) name a real device.
428 */
429 char *argv[] = { cmd->name, (char *)"metadata-dump-dummy-device", NULL((void*)0) };
430
431 mc->name = cmd->name;
432 mc->alias = cmd->alias;
433 mc->help = cmd->help;
434 mc->device = cmd->device;
435 mc->deprecated = cmd->deprecated;
436 mc->replacement_plugin = cmd->replacement.plugin;
437 mc->replacement_command = cmd->replacement.command;
438 mc->captured = false0;
439
440 /*
441 * Don't invoke the dump command itself: it would re-enter
442 * dump_command_metadata() and recurse forever. It has no
443 * completable options, so leave its options array empty.
444 */
445 if (!strcmp(cmd->name, "dump-command-metadata"))
446 return 0;
447
448 command_metadata_capture_error = 0;
449 command_metadata_cur_command = mc;
450 capture_current_command = cmd->name;
451 command_prepare(cmd);
452 (void)cmd->fn(2, argv, cmd, plugin);
453 capture_current_command = NULL((void*)0);
454 command_metadata_cur_command = NULL((void*)0);
455
456 /*
457 * If the hook never fired, the command returned before reaching the
458 * parser (e.g. gen-hostnqn) and has no completable options; its options
459 * array is simply left empty.
460 */
461 return command_metadata_capture_error;
462}
463
464static size_t count_commands(struct command **commands)
465{
466 size_t n = 0;
467
468 while (commands && commands[n])
469 n++;
470
471 return n;
472}
473
474static size_t count_plugins(struct plugin *p)
475{
476 size_t n = 0;
477
478 for (; p; p = p->next)
479 n++;
480
481 return n;
482}
483
484static struct command_metadata_program *build_model(struct program *prog)
485{
486 struct command_metadata_program *model;
487 struct plugin *plugin;
488 int saved_stdout = -1, saved_stderr = -1, devnull;
489 int err = 0;
490 size_t pi;
491
492 model = calloc(1, sizeof(*model));
2
Memory is allocated
493 if (!model)
3
Assuming 'model' is non-null
4
Taking false branch
494 return NULL((void*)0);
495
496 model->name = prog->name;
497 model->version = prog->version;
498 model->desc = prog->desc;
499 model->num_plugins = count_plugins(prog->extensions);
500 if (model->num_plugins
4.1
Field 'num_plugins' is 1
) {
5
Taking true branch
501 model->plugins = calloc(model->num_plugins, sizeof(*model->plugins));
502 if (!model->plugins) {
6
Assuming field 'plugins' is non-null
7
Taking false branch
503 free(model);
504 return NULL((void*)0);
505 }
506 }
507
508 /*
509 * Suppress stdout/stderr while invoking command fns: some commands
510 * may print before or during option capture, which would corrupt the
511 * JSON output.
512 */
513 fflush(stdoutstdout);
514 fflush(stderrstderr);
515 devnull = open(shr_dev_null(), O_WRONLY01);
516 if (devnull
7.1
'devnull' is < 0
>= 0) {
8
Taking false branch
517 saved_stdout = dup(STDOUT_FILENO1);
518 saved_stderr = dup(STDERR_FILENO2);
519 capture_saved_stderr_fd = saved_stderr;
520 if (saved_stdout >= 0)
521 dup2(devnull, STDOUT_FILENO1);
522 if (saved_stderr >= 0)
523 dup2(devnull, STDERR_FILENO2);
524 }
525
526 install_crash_handlers();
527 argconfig_set_parse_hook(metadata_capture_hook);
528
529 for (pi = 0, plugin = prog->extensions; plugin; plugin = plugin->next, pi++) {
9
Loop condition is true. Entering loop body
530 struct command_metadata_plugin *mp = &model->plugins[pi];
531 size_t ci;
532
533 mp->name = plugin->name;
534 mp->desc = plugin->desc;
535 mp->num_commands = count_commands(plugin->commands);
536 if (!mp->num_commands
9.1
Field 'num_commands' is 1
)
10
Taking false branch
537 continue;
538 mp->commands = calloc(mp->num_commands, sizeof(*mp->commands));
539 if (!mp->commands) {
11
Assuming field 'commands' is null
12
Taking true branch
540 mp->num_commands = 0;
541 err = -ENOMEM12;
542 break;
13
Execution continues on line 555
543 }
544
545 for (ci = 0; ci < mp->num_commands; ci++) {
546 err = capture_command(&mp->commands[ci],
547 plugin->commands[ci], plugin);
548 if (err)
549 break;
550 }
551 if (err)
552 break;
553 }
554
555 argconfig_set_parse_hook(NULL((void*)0));
556 remove_crash_handlers();
557 capture_saved_stderr_fd = -1;
558
559 fflush(stdoutstdout);
560 fflush(stderrstderr);
561 if (saved_stdout
13.1
'saved_stdout' is < 0
>= 0) {
14
Taking false branch
562 dup2(saved_stdout, STDOUT_FILENO1);
563 shr_close(saved_stdout);
564 }
565 if (saved_stderr
14.1
'saved_stderr' is < 0
>= 0) {
15
Taking false branch
566 dup2(saved_stderr, STDERR_FILENO2);
567 shr_close(saved_stderr);
568 }
569 if (devnull
15.1
'devnull' is < 0
>= 0)
16
Taking false branch
570 shr_close(devnull);
571
572 /*
573 * An allocation failure while capturing would leave an incomplete model
574 * that looks complete in the emitted JSON; fail the dump instead. The
575 * partial model is left for process exit to reclaim (it is never freed
576 * on the success path either).
577 */
578 if (err
16.1
'err' is -12
)
17
Taking true branch
579 return NULL((void*)0);
18
Potential leak of memory pointed to by 'model'
580
581 return model;
582}
583
584/* ------------------------------------------------------------------ */
585/* Model helpers shared by emitters */
586/* ------------------------------------------------------------------ */
587
588static bool_Bool opt_is_separator(const struct command_metadata_option *o)
589{
590 return o->config_type == CFG_GROUP_SEPARATOR;
591}
592
593static bool_Bool opt_is_global_separator(const struct command_metadata_option *o)
594{
595 return opt_is_separator(o) && o->help && !strcmp(o->help, "Global options");
596}
597
598/* "none" / "required" / "optional" — how the option consumes its argument. */
599static const char *opt_argument(const struct command_metadata_option *o)
600{
601 switch (o->argument_type) {
602 case ARGCONFIG_ARG_OPTIONAL:
603 return "optional";
604 case ARGCONFIG_ARG_NONE:
605 return "none";
606 default:
607 return "required";
608 }
609}
610
611static bool_Bool opt_takes_value(const struct command_metadata_option *o)
612{
613 return o->argument_type != ARGCONFIG_ARG_NONE;
614}
615
616/*
617 * True for an option that should be emitted: a real, named, non-separator
618 * option. Hidden options are emitted too (tagged "hidden" in the output) so
619 * the dump describes the full set of accepted options; consumers that only
620 * want user-facing options (e.g. completion generators) filter on that tag.
621 */
622static bool_Bool opt_is_emittable(const struct command_metadata_option *o)
623{
624 return !opt_is_separator(o) && o->option && o->option[0];
625}
626
627/* ------------------------------------------------------------------ */
628/* Pass 2: JSON */
629/* ------------------------------------------------------------------ */
630
631/*
632 * The value set for an option, when the generator can derive it. Returns a
633 * json array of strings, or NULL if the option has no known value set. The
634 * caller owns the returned array.
635 *
636 * output-format is special-cased because its values are not represented via an
637 * opt_val table. Its help text lists the formats the command supports
638 * ("Output format: normal|json"), see nvme_args_set_output_formats(). Every
639 * other value set comes from the option's opt_val table: the names it
640 * accepts, a numeric option also takes a number. Options without names
641 * (e.g. output-format-version) have no values array.
642 */
643static struct json_object *json_option_values(const struct command_metadata_option *o)
644{
645 static const char prefix[] = "Output format: ";
646 const struct argconfig_opt_val *v;
647 struct json_object *vals;
648
649 if (!strcmp(o->option, "output-format") && o->help &&
650 !strncmp(o->help, prefix, strlen(prefix))) {
651 __cleanup_free__attribute__((cleanup(shr_freep))) char *formats = strdup(o->help + strlen(prefix));
652 char *p, *name;
653
654 if (!formats)
655 return NULL((void*)0);
656
657 vals = json_create_array()json_object_new_array();
658 p = formats;
659 while ((name = shr_strsep(&p, "|")) != NULL((void*)0))
660 json_array_add_value_string(vals, name);
661 return vals;
662 }
663 if (!o->opt_val)
664 return NULL((void*)0);
665
666 vals = json_create_array()json_object_new_array();
667 for (v = o->opt_val; v->str; v++)
668 json_array_add_value_string(vals, v->str);
669 return vals;
670}
671
672/* The kind of value an option takes, see argconfig_parse_type() */
673static const char *opt_type(const struct command_metadata_option *o)
674{
675 switch (o->config_type) {
676 case CFG_FLAG:
677 return "flag";
678 case CFG_INCREMENT:
679 return "count";
680 case CFG_INT:
681 return "int";
682 case CFG_BYTE:
683 return "u8";
684 case CFG_SHORT:
685 return "u16";
686 case CFG_POSITIVE:
687 return "u32";
688 case CFG_LONG:
689 return "u64";
690 case CFG_LONG_SUFFIX:
691 return "size";
692 case CFG_DOUBLE:
693 return "double";
694 case CFG_STRING:
695 return "string";
696 case CFG_ENUM:
697 return "enum";
698 case CFG_ENUM_LIST:
699 return "enum-list";
700 case CFG_FILE:
701 return "file";
702 case CFG_DIR:
703 return "directory";
704 case CFG_GROUP_SEPARATOR:
705 case CFG_INCLUDE:
706 break;
707 }
708
709 return NULL((void*)0);
710}
711
712/* Build one option as a json object and add it to the given array. */
713static void json_option(struct json_object *arr, const struct command_metadata_option *o,
714 bool_Bool global)
715{
716 struct json_object *jo, *vals;
717 char shortbuf[2] = { o->short_option, '\0' };
718
719 if (!opt_is_emittable(o))
720 return;
721
722 jo = json_create_object()json_object_new_object();
723 json_object_add_value_string(jo, "long", o->option);
724 if (o->short_option)
725 json_object_add_value_string(jo, "short", shortbuf);
726 json_object_add_value_string(jo, "argument", opt_argument(o));
727 if (opt_type(o))
728 json_object_add_value_string(jo, "type", opt_type(o));
729 if (o->max) {
730 struct json_object *range = json_create_object()json_object_new_object();
731
732 json_object_add_value_uint64(range, "min", o->min)json_object_object_add(range, "min", json_object_new_uint64(o
->min))
;
733 json_object_add_value_uint64(range, "max", o->max)json_object_object_add(range, "max", json_object_new_uint64(o
->max))
;
734 json_object_add_value_object(jo, "range", range)json_object_object_add(jo, "range", range);
735 }
736 if (o->meta && opt_takes_value(o))
737 json_object_add_value_string(jo, "metavar", o->meta);
738 if (o->help)
739 json_object_add_value_string(jo, "description", o->help);
740 if (global)
741 json_object_add_value_bool(jo, "global", true)json_object_object_add(jo, "global", json_object_new_boolean(
1))
;
742 if (o->hidden)
743 json_object_add_value_bool(jo, "hidden", true)json_object_object_add(jo, "hidden", json_object_new_boolean(
1))
;
744 if (o->deprecated)
745 json_object_add_value_string(jo, "deprecated", o->deprecated);
746 if (o->old_name)
747 json_object_add_value_string(jo, "old_name", o->old_name);
748
749 vals = json_option_values(o);
750 if (vals)
751 json_object_add_value_array(jo, "values", vals)json_object_object_add(jo, "values", vals);
752
753 json_array_add_value_object(arr, jo)json_object_array_add(arr, jo);
754}
755
756/* Build one command as a json object: name, alias, description, and options. */
757/* The device operand of a command, see enum command_device */
758static const char * const device_names[] = {
759 [COMMAND_DEVICE] = "required",
760 [COMMAND_DEVICE_OPTIONAL] = "optional",
761 [COMMAND_NO_DEVICE] = "none",
762};
763
764static struct json_object *json_command(const struct command_metadata_command *c)
765{
766 struct json_object *jc, *opts;
767 bool_Bool global = false0;
768 size_t i;
769
770 jc = json_create_object()json_object_new_object();
771 json_object_add_value_string(jc, "name", c->name);
772 if (c->alias)
773 json_object_add_value_string(jc, "alias", c->alias);
774 if (c->help)
775 json_object_add_value_string(jc, "description", c->help);
776 json_object_add_value_string(jc, "device", device_names[c->device]);
777 if (c->deprecated)
778 json_object_add_value_bool(jc, "deprecated", true)json_object_object_add(jc, "deprecated", json_object_new_boolean
(1))
;
779 if (c->replacement_plugin && c->replacement_command) {
780 char replacement[0x80];
781
782 snprintf(replacement, sizeof(replacement), "%s %s",
783 c->replacement_plugin, c->replacement_command);
784 json_object_add_value_string(jc, "replacement", replacement);
785 }
786
787 opts = json_create_array()json_object_new_array();
788 for (i = 0; i < c->num_options; i++) {
789 /*
790 * Options after the "Global options" separator are the
791 * global options; flag them so generators can group them.
792 */
793 if (opt_is_global_separator(&c->options[i])) {
794 global = true1;
795 continue;
796 }
797 json_option(opts, &c->options[i], global);
798 }
799 json_object_add_value_array(jc, "options", opts)json_object_object_add(jc, "options", opts);
800
801 return jc;
802}
803
804/* Build one named plugin as a json object: name, description, commands. */
805static struct json_object *json_plugin(const struct command_metadata_plugin *p)
806{
807 struct json_object *jp, *cmds;
808 size_t i;
809
810 jp = json_create_object()json_object_new_object();
811 assert(p->name)((void) sizeof (__assert_single_arg (p->name)), __extension__
({ if (p->name) ; else __assert_fail ("p->name", "../plugins/utils/command-metadata.c"
, 811, __extension__ __PRETTY_FUNCTION__); }))
; /* builtin (NULL-name) is emitted inline by json_program() */
812 json_object_add_value_string(jp, "name", p->name);
813 if (p->desc)
814 json_object_add_value_string(jp, "description", p->desc);
815
816 cmds = json_create_array()json_object_new_array();
817 for (i = 0; i < p->num_commands; i++)
818 json_array_add_value_object(cmds, json_command(&p->commands[i]))json_object_array_add(cmds, json_command(&p->commands[
i]))
;
819 json_object_add_value_array(jp, "commands", cmds)json_object_object_add(jp, "commands", cmds);
820
821 return jp;
822}
823
824static void json_program(const struct command_metadata_program *m)
825{
826 struct json_object *root, *builtin, *plugins;
827 size_t i;
828
829 root = json_create_object()json_object_new_object();
830 json_object_add_value_int(root, "schema_version",json_object_object_add(root, "schema_version", json_object_new_int
(2))
831 COMMAND_METADATA_SCHEMA_VERSION)json_object_object_add(root, "schema_version", json_object_new_int
(2))
;
832 json_object_add_value_string(root, "name", m->name);
833 if (m->version)
834 json_object_add_value_string(root, "version", m->version);
835 if (m->desc)
836 json_object_add_value_string(root, "description", m->desc);
837
838 /*
839 * Builtin (top-level) commands live in their own array; named plugins
840 * go under "plugins" so generators can build the dispatch nesting.
841 */
842 builtin = json_create_array()json_object_new_array();
843 plugins = json_create_array()json_object_new_array();
844 for (i = 0; i < m->num_plugins; i++) {
845 const struct command_metadata_plugin *p = &m->plugins[i];
846 size_t j;
847
848 if (!p->name) {
849 for (j = 0; j < p->num_commands; j++)
850 json_array_add_value_object(builtin,json_object_array_add(builtin, json_command(&p->commands
[j]))
851 json_command(&p->commands[j]))json_object_array_add(builtin, json_command(&p->commands
[j]))
;
852 } else {
853 json_array_add_value_object(plugins, json_plugin(p))json_object_array_add(plugins, json_plugin(p));
854 }
855 }
856 json_object_add_value_array(root, "commands", builtin)json_object_object_add(root, "commands", builtin);
857 json_object_add_value_array(root, "plugins", plugins)json_object_object_add(root, "plugins", plugins);
858
859 json_print_object(root, NULL)printf("%s", json_object_to_json_string_ext(root, (1 <<
1) | (1 << 4)))
;
860 printf("\n");
861 json_free_object(root)json_object_put(root);
862}
863
864/* ------------------------------------------------------------------ */
865/* Entry point */
866/* ------------------------------------------------------------------ */
867
868int dump_command_metadata(struct program *prog)
869{
870 struct command_metadata_program *model;
871
872 model = build_model(prog);
1
Calling 'build_model'
873 if (!model)
874 return -ENOMEM12;
875
876 json_program(model);
877
878 free(model->plugins);
879 free(model);
880 return 0;
881}
882
883#endif /* CONFIG_JSONC */