Bug Summary

File:.build-ci/../plugins/utils/command-metadata.c
Warning:line 509, 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-09-23-073103-589-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 via NVME_ARGS.
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 <getopt.h>
24#include <signal.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <unistd.h>
29#ifdef _WIN32
30#include <windows.h>
31#endif
32
33#include <shared/fs-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_VERSION1 1
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 if (o->opt_val) {
271 const struct argconfig_opt_val *v;
272
273 for (v = o->opt_val; v->str; v++)
274 free((char *)v->str);
275 free((void *)o->opt_val);
276 }
277}
278
279/*
280 * Deep-copy an options array into *out (a heap array of *n_out entries).
281 * Returns -ENOMEM on failure; the partial allocation is left for process exit
282 * to reclaim, as the model is never explicitly freed.
283 */
284static int copy_options(const struct argconfig_commandline_options *opts,
285 struct command_metadata_option **out, size_t *n_out)
286{
287 const struct argconfig_commandline_options *s;
288 struct command_metadata_option *dst;
289 size_t n = 0, i;
290
291 *out = NULL((void*)0);
292 *n_out = 0;
293
294 for (s = opts; s->option; s++)
295 n++;
296
297 if (!n) /* calloc(0) may return NULL, indistinguishable from OOM */
298 return 0;
299
300 dst = calloc(n, sizeof(*dst));
301 if (!dst)
302 return -ENOMEM12;
303
304 /*
305 * Deep-copy: option/meta/help and the opt_val table are valid while the
306 * parser runs but may point at command-local storage that is freed once
307 * the command's fn returns, so duplicate rather than alias them.
308 */
309 for (i = 0; i < n; i++) {
310 if (dup_field(opts[i].option, &dst[i].option) ||
311 dup_field(opts[i].meta, &dst[i].meta) ||
312 dup_field(opts[i].help, &dst[i].help) ||
313 copy_opt_val(opts[i].opt_val, &dst[i].opt_val)) {
314 size_t j;
315
316 for (j = 0; j <= i; j++)
317 free_option_fields(&dst[j]);
318 free(dst);
319 return -ENOMEM12;
320 }
321 dst[i].short_option = opts[i].short_option;
322 dst[i].config_type = opts[i].config_type;
323 dst[i].argument_type = opts[i].argument_type;
324 dst[i].hidden = opts[i].hidden;
325 }
326
327 *out = dst;
328 *n_out = n;
329 return 0;
330}
331
332/*
333 * argconfig_parse() hook installed by build_model(): copies the current
334 * command's options into the model, then returns the sentinel so the parser
335 * unwinds before the command opens a device.
336 */
337static int metadata_capture_hook(int argc, char **argv, const char *program_desc,
338 struct argconfig_commandline_options *options)
339{
340 (void)argc;
341 (void)argv;
342 (void)program_desc;
343
344 if (command_metadata_cur_command && !command_metadata_cur_command->captured) {
345 int err = copy_options(options,
346 &command_metadata_cur_command->options,
347 &command_metadata_cur_command->num_options);
348 if (err)
349 command_metadata_capture_error = err;
350 command_metadata_cur_command->captured = true1;
351 }
352
353 return METADATA_CAPTURE_SENTINEL(-125);
354}
355
356/* Returns 0 on success, or -ENOMEM if the capture hook failed to allocate. */
357static int capture_command(struct command_metadata_command *mc, struct command *cmd,
358 struct plugin *plugin)
359{
360 /*
361 * argv[1] is a placeholder device; the sentinel returns before it is
362 * ever opened, so it need not (and must not) name a real device.
363 */
364 char *argv[] = { cmd->name, (char *)"metadata-dump-dummy-device", NULL((void*)0) };
365
366 mc->name = cmd->name;
367 mc->alias = cmd->alias;
368 mc->help = cmd->help;
369 mc->captured = false0;
370
371 /*
372 * Don't invoke the dump command itself: it would re-enter
373 * dump_command_metadata() and recurse forever. It has no
374 * completable options, so leave its options array empty.
375 */
376 if (!strcmp(cmd->name, "dump-command-metadata"))
377 return 0;
378
379 command_metadata_capture_error = 0;
380 command_metadata_cur_command = mc;
381 capture_current_command = cmd->name;
382 (void)cmd->fn(2, argv, cmd, plugin);
383 capture_current_command = NULL((void*)0);
384 command_metadata_cur_command = NULL((void*)0);
385
386 /*
387 * If the hook never fired, the command returned before reaching the
388 * parser (e.g. gen-hostnqn) and has no completable options; its options
389 * array is simply left empty.
390 */
391 return command_metadata_capture_error;
392}
393
394static size_t count_commands(struct command **commands)
395{
396 size_t n = 0;
397
398 while (commands && commands[n])
399 n++;
400
401 return n;
402}
403
404static size_t count_plugins(struct plugin *p)
405{
406 size_t n = 0;
407
408 for (; p; p = p->next)
409 n++;
410
411 return n;
412}
413
414static struct command_metadata_program *build_model(struct program *prog)
415{
416 struct command_metadata_program *model;
417 struct plugin *plugin;
418 int saved_stdout = -1, saved_stderr = -1, devnull;
419 int err = 0;
420 size_t pi;
421
422 model = calloc(1, sizeof(*model));
2
Memory is allocated
423 if (!model)
3
Assuming 'model' is non-null
4
Taking false branch
424 return NULL((void*)0);
425
426 model->name = prog->name;
427 model->version = prog->version;
428 model->desc = prog->desc;
429 model->num_plugins = count_plugins(prog->extensions);
430 if (model->num_plugins
4.1
Field 'num_plugins' is 2
) {
5
Taking true branch
431 model->plugins = calloc(model->num_plugins, sizeof(*model->plugins));
432 if (!model->plugins) {
6
Assuming field 'plugins' is non-null
7
Taking false branch
433 free(model);
434 return NULL((void*)0);
435 }
436 }
437
438 /*
439 * Suppress stdout/stderr while invoking command fns: some commands
440 * may print before or during option capture, which would corrupt the
441 * JSON output.
442 */
443 fflush(stdoutstdout);
444 fflush(stderrstderr);
445 devnull = open(shr_dev_null(), O_WRONLY01);
446 if (devnull
7.1
'devnull' is < 0
>= 0) {
8
Taking false branch
447 saved_stdout = dup(STDOUT_FILENO1);
448 saved_stderr = dup(STDERR_FILENO2);
449 capture_saved_stderr_fd = saved_stderr;
450 if (saved_stdout >= 0)
451 dup2(devnull, STDOUT_FILENO1);
452 if (saved_stderr >= 0)
453 dup2(devnull, STDERR_FILENO2);
454 }
455
456 install_crash_handlers();
457 argconfig_set_parse_hook(metadata_capture_hook);
458
459 for (pi = 0, plugin = prog->extensions; plugin; plugin = plugin->next, pi++) {
9
Loop condition is true. Entering loop body
460 struct command_metadata_plugin *mp = &model->plugins[pi];
461 size_t ci;
462
463 mp->name = plugin->name;
464 mp->desc = plugin->desc;
465 mp->num_commands = count_commands(plugin->commands);
466 if (!mp->num_commands)
10
Assuming field 'num_commands' is not equal to 0
11
Taking false branch
467 continue;
468 mp->commands = calloc(mp->num_commands, sizeof(*mp->commands));
469 if (!mp->commands) {
12
Assuming field 'commands' is null
13
Taking true branch
470 mp->num_commands = 0;
471 err = -ENOMEM12;
472 break;
14
Execution continues on line 485
473 }
474
475 for (ci = 0; ci < mp->num_commands; ci++) {
476 err = capture_command(&mp->commands[ci],
477 plugin->commands[ci], plugin);
478 if (err)
479 break;
480 }
481 if (err)
482 break;
483 }
484
485 argconfig_set_parse_hook(NULL((void*)0));
486 remove_crash_handlers();
487 capture_saved_stderr_fd = -1;
488
489 fflush(stdoutstdout);
490 fflush(stderrstderr);
491 if (saved_stdout
14.1
'saved_stdout' is < 0
>= 0) {
15
Taking false branch
492 dup2(saved_stdout, STDOUT_FILENO1);
493 shr_close(saved_stdout);
494 }
495 if (saved_stderr
15.1
'saved_stderr' is < 0
>= 0) {
16
Taking false branch
496 dup2(saved_stderr, STDERR_FILENO2);
497 shr_close(saved_stderr);
498 }
499 if (devnull
16.1
'devnull' is < 0
>= 0)
17
Taking false branch
500 shr_close(devnull);
501
502 /*
503 * An allocation failure while capturing would leave an incomplete model
504 * that looks complete in the emitted JSON; fail the dump instead. The
505 * partial model is left for process exit to reclaim (it is never freed
506 * on the success path either).
507 */
508 if (err
17.1
'err' is -12
)
18
Taking true branch
509 return NULL((void*)0);
19
Potential leak of memory pointed to by 'model'
510
511 return model;
512}
513
514/* ------------------------------------------------------------------ */
515/* Model helpers shared by emitters */
516/* ------------------------------------------------------------------ */
517
518static bool_Bool opt_is_separator(const struct command_metadata_option *o)
519{
520 return o->config_type == CFG_GROUP_SEPARATOR;
521}
522
523static bool_Bool opt_is_global_separator(const struct command_metadata_option *o)
524{
525 return opt_is_separator(o) && o->help && !strcmp(o->help, "Global options");
526}
527
528/* "none" / "required" / "optional" — how the option consumes its argument. */
529static const char *opt_argument(const struct command_metadata_option *o)
530{
531 switch (o->argument_type) {
532 case optional_argument2:
533 return "optional";
534 case no_argument0:
535 return "none";
536 default:
537 return "required";
538 }
539}
540
541static bool_Bool opt_takes_value(const struct command_metadata_option *o)
542{
543 return o->argument_type != no_argument0;
544}
545
546/*
547 * True for an option that should be emitted: a real, named, non-separator
548 * option. Hidden options are emitted too (tagged "hidden" in the output) so
549 * the dump describes the full set of accepted options; consumers that only
550 * want user-facing options (e.g. completion generators) filter on that tag.
551 */
552static bool_Bool opt_is_emittable(const struct command_metadata_option *o)
553{
554 return !opt_is_separator(o) && o->option && o->option[0];
555}
556
557/* ------------------------------------------------------------------ */
558/* Pass 2: JSON */
559/* ------------------------------------------------------------------ */
560
561/*
562 * The value set for an option, when the generator can derive it. Returns a
563 * json array of strings, or NULL if the option has no known value set. The
564 * caller owns the returned array.
565 *
566 * output-format is special-cased because its values are not represented via an
567 * opt_val table; keep the hard-coded list below in sync with
568 * validate_output_format() / DESC_OUTPUT_FORMAT. Since the whole command is
569 * compiled out without json-c, "json" is always a valid value here. Every
570 * other value set comes from the option's opt_val table, which is the set the
571 * parser actually enforces; options whose value is unconstrained (e.g. any
572 * OPT_UINT such as output-format-version) have no values array.
573 */
574static struct json_object *json_option_values(const struct command_metadata_option *o)
575{
576 const struct argconfig_opt_val *v;
577 struct json_object *vals;
578
579 if (!strcmp(o->option, "output-format")) {
580 vals = json_create_array()json_object_new_array();
581 json_array_add_value_string(vals, "normal");
582 json_array_add_value_string(vals, "json");
583 json_array_add_value_string(vals, "binary");
584 json_array_add_value_string(vals, "tabular");
585 return vals;
586 }
587 if (!o->opt_val)
588 return NULL((void*)0);
589
590 vals = json_create_array()json_object_new_array();
591 for (v = o->opt_val; v->str; v++)
592 json_array_add_value_string(vals, v->str);
593 return vals;
594}
595
596/* Build one option as a json object and add it to the given array. */
597static void json_option(struct json_object *arr, const struct command_metadata_option *o,
598 bool_Bool global)
599{
600 struct json_object *jo, *vals;
601 char shortbuf[2] = { o->short_option, '\0' };
602
603 if (!opt_is_emittable(o))
604 return;
605
606 jo = json_create_object()json_object_new_object();
607 json_object_add_value_string(jo, "long", o->option);
608 if (o->short_option)
609 json_object_add_value_string(jo, "short", shortbuf);
610 json_object_add_value_string(jo, "argument", opt_argument(o));
611 if (o->meta && opt_takes_value(o))
612 json_object_add_value_string(jo, "metavar", o->meta);
613 if (o->help)
614 json_object_add_value_string(jo, "description", o->help);
615 if (global)
616 json_object_add_value_bool(jo, "global", true)json_object_object_add(jo, "global", json_object_new_boolean(
1))
;
617 if (o->hidden)
618 json_object_add_value_bool(jo, "hidden", true)json_object_object_add(jo, "hidden", json_object_new_boolean(
1))
;
619
620 vals = json_option_values(o);
621 if (vals)
622 json_object_add_value_array(jo, "values", vals)json_object_object_add(jo, "values", vals);
623
624 json_array_add_value_object(arr, jo)json_object_array_add(arr, jo);
625}
626
627/* Build one command as a json object: name, alias, description, and options. */
628static struct json_object *json_command(const struct command_metadata_command *c)
629{
630 struct json_object *jc, *opts;
631 bool_Bool global = false0;
632 size_t i;
633
634 jc = json_create_object()json_object_new_object();
635 json_object_add_value_string(jc, "name", c->name);
636 if (c->alias)
637 json_object_add_value_string(jc, "alias", c->alias);
638 if (c->help)
639 json_object_add_value_string(jc, "description", c->help);
640
641 opts = json_create_array()json_object_new_array();
642 for (i = 0; i < c->num_options; i++) {
643 /*
644 * Options after the "Global options" separator are the shared
645 * NVME_ARGS globals; flag them so generators can group them.
646 */
647 if (opt_is_global_separator(&c->options[i])) {
648 global = true1;
649 continue;
650 }
651 json_option(opts, &c->options[i], global);
652 }
653 json_object_add_value_array(jc, "options", opts)json_object_object_add(jc, "options", opts);
654
655 return jc;
656}
657
658/* Build one named plugin as a json object: name, description, commands. */
659static struct json_object *json_plugin(const struct command_metadata_plugin *p)
660{
661 struct json_object *jp, *cmds;
662 size_t i;
663
664 jp = json_create_object()json_object_new_object();
665 assert(p->name)((void) sizeof (__assert_single_arg (p->name)), __extension__
({ if (p->name) ; else __assert_fail ("p->name", "../plugins/utils/command-metadata.c"
, 665, __extension__ __PRETTY_FUNCTION__); }))
; /* builtin (NULL-name) is emitted inline by json_program() */
666 json_object_add_value_string(jp, "name", p->name);
667 if (p->desc)
668 json_object_add_value_string(jp, "description", p->desc);
669
670 cmds = json_create_array()json_object_new_array();
671 for (i = 0; i < p->num_commands; i++)
672 json_array_add_value_object(cmds, json_command(&p->commands[i]))json_object_array_add(cmds, json_command(&p->commands[
i]))
;
673 json_object_add_value_array(jp, "commands", cmds)json_object_object_add(jp, "commands", cmds);
674
675 return jp;
676}
677
678static void json_program(const struct command_metadata_program *m)
679{
680 struct json_object *root, *builtin, *plugins;
681 size_t i;
682
683 root = json_create_object()json_object_new_object();
684 json_object_add_value_int(root, "schema_version",json_object_object_add(root, "schema_version", json_object_new_int
(1))
685 COMMAND_METADATA_SCHEMA_VERSION)json_object_object_add(root, "schema_version", json_object_new_int
(1))
;
686 json_object_add_value_string(root, "name", m->name);
687 if (m->version)
688 json_object_add_value_string(root, "version", m->version);
689 if (m->desc)
690 json_object_add_value_string(root, "description", m->desc);
691
692 /*
693 * Builtin (top-level) commands live in their own array; named plugins
694 * go under "plugins" so generators can build the dispatch nesting.
695 */
696 builtin = json_create_array()json_object_new_array();
697 plugins = json_create_array()json_object_new_array();
698 for (i = 0; i < m->num_plugins; i++) {
699 const struct command_metadata_plugin *p = &m->plugins[i];
700 size_t j;
701
702 if (!p->name) {
703 for (j = 0; j < p->num_commands; j++)
704 json_array_add_value_object(builtin,json_object_array_add(builtin, json_command(&p->commands
[j]))
705 json_command(&p->commands[j]))json_object_array_add(builtin, json_command(&p->commands
[j]))
;
706 } else {
707 json_array_add_value_object(plugins, json_plugin(p))json_object_array_add(plugins, json_plugin(p));
708 }
709 }
710 json_object_add_value_array(root, "commands", builtin)json_object_object_add(root, "commands", builtin);
711 json_object_add_value_array(root, "plugins", plugins)json_object_object_add(root, "plugins", plugins);
712
713 json_print_object(root, NULL)printf("%s", json_object_to_json_string_ext(root, (1 <<
1) | (1 << 4)))
;
714 printf("\n");
715 json_free_object(root)json_object_put(root);
716}
717
718/* ------------------------------------------------------------------ */
719/* Entry point */
720/* ------------------------------------------------------------------ */
721
722int dump_command_metadata(struct program *prog)
723{
724 struct command_metadata_program *model;
725
726 model = build_model(prog);
1
Calling 'build_model'
727 if (!model)
728 return -ENOMEM12;
729
730 json_program(model);
731
732 free(model->plugins);
733 free(model);
734 return 0;
735}
736
737#endif /* CONFIG_JSONC */