Bug Summary

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

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 shared -I ../shared -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=gnu99 -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-08-08-045408-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 "command-metadata.h"
34#include "common.h"
35#include "nvme.h"
36#include "nvme-json.h"
37
38/*
39 * The whole command is JSON-only, so it is compiled out entirely without
40 * json-c support: the utils plugin does not register it and does not define
41 * its handler, so dump_command_metadata() is never referenced.
42 */
43#ifdef CONFIG_JSONC
44
45/*
46 * Returned by the capture hook so argconfig_parse() unwinds before the
47 * command's fn opens a device.
48 */
49#define METADATA_CAPTURE_SENTINEL(-125) (-ECANCELED125)
50
51/*
52 * Version of the emitted JSON schema, bumped on any breaking change to the
53 * output structure (renamed/removed keys, changed value semantics). Additive
54 * changes that keep existing keys stable do not require a bump. Consumers
55 * should reject a major version they do not understand.
56 */
57#define COMMAND_METADATA_SCHEMA_VERSION1 1
58
59/*
60 * The command currently being captured; set by capture_command()
61 * before it invokes the command fn, read by metadata_capture_hook().
62 */
63static struct command_metadata_command *command_metadata_cur_command;
64
65/*
66 * Capture OOM, reported out-of-band because the hook's return value is reserved
67 * for the parser-unwind sentinel; checked after each command fn returns.
68 */
69static int command_metadata_capture_error;
70
71/*
72 * capture_saved_stderr_fd holds a dup of stderr from before the NUL redirect,
73 * so crash messages still reach the user.
74 */
75static const char *capture_current_command;
76static int capture_saved_stderr_fd = -1;
77
78/*
79 * (void)write() does not suppress GCC's warn_unused_result under
80 * _FORTIFY_SOURCE; assigning to a discarded variable does.
81 */
82static void write_raw(int fd, const char *buf, size_t len)
83{
84 ssize_t ret = write(fd, buf, len);
85 (void)ret;
86}
87
88/* strlen() is not async-signal-safe; measure inline for the crash handler. */
89static void write_str(int fd, const char *s)
90{
91 size_t len = 0;
92
93 if (!s)
94 return;
95
96 while (s[len])
97 len++;
98
99 write_raw(fd, s, len);
100}
101
102static void write_uint(int fd, unsigned int n)
103{
104 char buf[10]; /* enough for any 32-bit unsigned int */
105 int i = sizeof(buf);
106
107 do {
108 buf[--i] = '0' + (n % 10);
109 n /= 10;
110 } while (n && i);
111
112 write_raw(fd, buf + i, sizeof(buf) - i);
113}
114
115static void capture_crash_handler(int sig)
116{
117 int fd = capture_saved_stderr_fd >= 0 ? capture_saved_stderr_fd : STDERR_FILENO2;
118
119 write_str(fd, "dump-command-metadata: fatal: '");
120 write_str(fd, capture_current_command ? capture_current_command : "(unknown)");
121 write_str(fd, "' crashed during option capture (signal ");
122 write_uint(fd, sig);
123 write_str(fd, ")\n");
124
125 signal(sig, SIG_DFL((__sighandler_t) 0));
126 raise(sig);
127}
128
129/*
130 * On Windows, OS-level exceptions (stack overflow, access violations) bypass
131 * the C signal() mechanism entirely and terminate the process silently.
132 * SetUnhandledExceptionFilter intercepts these so we can print the same
133 * diagnostic before dying.
134 */
135#ifdef _WIN32
136static void write_hex(int fd, unsigned int n)
137{
138 char buf[8];
139 int i = sizeof(buf);
140
141 do {
142 buf[--i] = "0123456789abcdef"[n & 0xf];
143 n >>= 4;
144 } while (n && i);
145
146 write_raw(fd, buf + i, sizeof(buf) - i);
147}
148
149static LONG WINAPI capture_exception_filter(EXCEPTION_POINTERS *ep)
150{
151 int fd = capture_saved_stderr_fd >= 0 ? capture_saved_stderr_fd : STDERR_FILENO2;
152
153 write_str(fd, "dump-command-metadata: fatal: '");
154 write_str(fd, capture_current_command ? capture_current_command : "(unknown)");
155 write_str(fd, "' crashed during option capture (exception 0x");
156 write_hex(fd, ep->ExceptionRecord->ExceptionCode);
157 write_str(fd, ")\n");
158
159 return EXCEPTION_CONTINUE_SEARCH;
160}
161
162static LPTOP_LEVEL_EXCEPTION_FILTER capture_prev_filter;
163#endif
164
165static void (*capture_prev_sigsegv)(int);
166static void (*capture_prev_sigabrt)(int);
167#ifdef SIGBUS7
168static void (*capture_prev_sigbus)(int);
169#endif
170
171/* Normalize SIG_ERR to SIG_DFL so restoration is always a valid disposition. */
172static void (*set_crash_handler(int sig))(int)
173{
174 void (*prev)(int) = signal(sig, capture_crash_handler);
175
176 return prev == SIG_ERR((__sighandler_t) -1) ? SIG_DFL((__sighandler_t) 0) : prev;
177}
178
179static void install_crash_handlers(void)
180{
181 capture_prev_sigsegv = set_crash_handler(SIGSEGV11);
182 capture_prev_sigabrt = set_crash_handler(SIGABRT6);
183#ifdef SIGBUS7
184 capture_prev_sigbus = set_crash_handler(SIGBUS7);
185#endif
186#ifdef _WIN32
187 capture_prev_filter = SetUnhandledExceptionFilter(capture_exception_filter);
188#endif
189}
190
191static void remove_crash_handlers(void)
192{
193 signal(SIGSEGV11, capture_prev_sigsegv);
194 signal(SIGABRT6, capture_prev_sigabrt);
195#ifdef SIGBUS7
196 signal(SIGBUS7, capture_prev_sigbus);
197#endif
198#ifdef _WIN32
199 SetUnhandledExceptionFilter(capture_prev_filter);
200 capture_prev_filter = NULL((void*)0);
201#endif
202}
203
204/* ------------------------------------------------------------------ */
205/* Pass 1: capture */
206/* ------------------------------------------------------------------ */
207
208static char *xstrdup(const char *s)
209{
210 return s ? strdup(s) : NULL((void*)0);
211}
212
213/*
214 * Deep-copy an opt_val table into *out. A NULL src is not an error: *out is set
215 * to NULL, the "no value table" sentinel consumers expect. Returns -ENOMEM on
216 * allocation failure so the caller can abort rather than silently drop values.
217 */
218static int copy_opt_val(const struct argconfig_opt_val *src, const struct argconfig_opt_val **out)
219{
220 struct argconfig_opt_val *dst;
221 size_t n = 0, i;
222
223 *out = NULL((void*)0);
224 if (!src)
225 return 0;
226
227 for (; src[n].str; n++)
228 ;
229
230 dst = calloc(n + 1, sizeof(*dst));
231 if (!dst)
232 return -ENOMEM12;
233
234 for (i = 0; i < n; i++) {
235 dst[i] = src[i];
236 /* src[i].str is non-NULL for i < n, so NULL here means OOM. */
237 dst[i].str = strdup(src[i].str);
238 if (!dst[i].str)
239 return -ENOMEM12;
240 }
241 dst[n].str = NULL((void*)0);
242
243 *out = dst;
244 return 0;
245}
246
247/*
248 * Duplicate a possibly-NULL string: NULL src succeeds (copies to NULL); a
249 * non-NULL src that fails to duplicate returns -ENOMEM.
250 */
251static int dup_field(const char *src, const char **dst)
252{
253 *dst = xstrdup(src);
254 if (src && !*dst)
255 return -ENOMEM12;
256 return 0;
257}
258
259/*
260 * Deep-copy an options array into *out (a heap array of *n_out entries).
261 * Returns -ENOMEM on failure; the partial allocation is left for process exit
262 * to reclaim, as the model is never explicitly freed.
263 */
264static int copy_options(const struct argconfig_commandline_options *opts,
265 struct command_metadata_option **out, size_t *n_out)
266{
267 const struct argconfig_commandline_options *s;
268 struct command_metadata_option *dst;
269 size_t n = 0, i;
270
271 *out = NULL((void*)0);
272 *n_out = 0;
273
274 for (s = opts; s->option; s++)
5
Loop condition is true. Entering loop body
6
Loop condition is false. Execution continues on line 277
275 n++;
276
277 if (!n
6.1
'n' is 1
) /* calloc(0) may return NULL, indistinguishable from OOM */
7
Taking false branch
278 return 0;
279
280 dst = calloc(n, sizeof(*dst));
8
Memory is allocated
281 if (!dst)
9
Assuming 'dst' is non-null
10
Taking false branch
282 return -ENOMEM12;
283
284 /*
285 * Deep-copy: option/meta/help and the opt_val table are valid while the
286 * parser runs but may point at command-local storage that is freed once
287 * the command's fn returns, so duplicate rather than alias them.
288 */
289 for (i = 0; i < n; i++) {
290 if (dup_field(opts[i].option, &dst[i].option) ||
291 dup_field(opts[i].meta, &dst[i].meta) ||
292 dup_field(opts[i].help, &dst[i].help) ||
293 copy_opt_val(opts[i].opt_val, &dst[i].opt_val))
294 return -ENOMEM12;
11
Potential leak of memory pointed to by 'dst'
295 dst[i].short_option = opts[i].short_option;
296 dst[i].config_type = opts[i].config_type;
297 dst[i].argument_type = opts[i].argument_type;
298 dst[i].hidden = opts[i].hidden;
299 }
300
301 *out = dst;
302 *n_out = n;
303 return 0;
304}
305
306/*
307 * argconfig_parse() hook installed by build_model(): copies the current
308 * command's options into the model, then returns the sentinel so the parser
309 * unwinds before the command opens a device.
310 */
311static int metadata_capture_hook(int argc, char **argv, const char *program_desc,
312 struct argconfig_commandline_options *options)
313{
314 (void)argc;
315 (void)argv;
316 (void)program_desc;
317
318 if (command_metadata_cur_command && !command_metadata_cur_command->captured) {
1
Assuming 'command_metadata_cur_command' is non-null
2
Assuming field 'captured' is false
3
Taking true branch
319 int err = copy_options(options,
4
Calling 'copy_options'
320 &command_metadata_cur_command->options,
321 &command_metadata_cur_command->num_options);
322 if (err)
323 command_metadata_capture_error = err;
324 command_metadata_cur_command->captured = true1;
325 }
326
327 return METADATA_CAPTURE_SENTINEL(-125);
328}
329
330/* Returns 0 on success, or -ENOMEM if the capture hook failed to allocate. */
331static int capture_command(struct command_metadata_command *mc, struct command *cmd,
332 struct plugin *plugin)
333{
334 /*
335 * argv[1] is a placeholder device; the sentinel returns before it is
336 * ever opened, so it need not (and must not) name a real device.
337 */
338 char *argv[] = { cmd->name, (char *)"metadata-dump-dummy-device", NULL((void*)0) };
339
340 mc->name = cmd->name;
341 mc->alias = cmd->alias;
342 mc->help = cmd->help;
343 mc->captured = false0;
344
345 /*
346 * Don't invoke the dump command itself: it would re-enter
347 * dump_command_metadata() and recurse forever. It has no
348 * completable options, so leave its options array empty.
349 */
350 if (!strcmp(cmd->name, "dump-command-metadata"))
351 return 0;
352
353 command_metadata_capture_error = 0;
354 command_metadata_cur_command = mc;
355 capture_current_command = cmd->name;
356 (void)cmd->fn(2, argv, cmd, plugin);
357 capture_current_command = NULL((void*)0);
358 command_metadata_cur_command = NULL((void*)0);
359
360 /*
361 * If the hook never fired, the command returned before reaching the
362 * parser (e.g. gen-hostnqn) and has no completable options; its options
363 * array is simply left empty.
364 */
365 return command_metadata_capture_error;
366}
367
368static size_t count_commands(struct command **commands)
369{
370 size_t n = 0;
371
372 while (commands && commands[n])
373 n++;
374
375 return n;
376}
377
378static size_t count_plugins(struct plugin *p)
379{
380 size_t n = 0;
381
382 for (; p; p = p->next)
383 n++;
384
385 return n;
386}
387
388static struct command_metadata_program *build_model(struct program *prog)
389{
390 struct command_metadata_program *model;
391 struct plugin *plugin;
392 int saved_stdout = -1, saved_stderr = -1, devnull;
393 int err = 0;
394 size_t pi;
395
396 model = calloc(1, sizeof(*model));
397 if (!model)
398 return NULL((void*)0);
399
400 model->name = prog->name;
401 model->version = prog->version;
402 model->desc = prog->desc;
403 model->num_plugins = count_plugins(prog->extensions);
404 if (model->num_plugins) {
405 model->plugins = calloc(model->num_plugins, sizeof(*model->plugins));
406 if (!model->plugins) {
407 free(model);
408 return NULL((void*)0);
409 }
410 }
411
412 /*
413 * Suppress stdout/stderr while invoking command fns: some commands
414 * may print before or during option capture, which would corrupt the
415 * JSON output.
416 */
417 fflush(stdoutstdout);
418 fflush(stderrstderr);
419 devnull = open(DEV_NULL"/dev/null", O_WRONLY01);
420 if (devnull >= 0) {
421 saved_stdout = dup(STDOUT_FILENO1);
422 saved_stderr = dup(STDERR_FILENO2);
423 capture_saved_stderr_fd = saved_stderr;
424 if (saved_stdout >= 0)
425 dup2(devnull, STDOUT_FILENO1);
426 if (saved_stderr >= 0)
427 dup2(devnull, STDERR_FILENO2);
428 }
429
430 install_crash_handlers();
431 argconfig_set_parse_hook(metadata_capture_hook);
432
433 for (pi = 0, plugin = prog->extensions; plugin; plugin = plugin->next, pi++) {
434 struct command_metadata_plugin *mp = &model->plugins[pi];
435 size_t ci;
436
437 mp->name = plugin->name;
438 mp->desc = plugin->desc;
439 mp->num_commands = count_commands(plugin->commands);
440 if (!mp->num_commands)
441 continue;
442 mp->commands = calloc(mp->num_commands, sizeof(*mp->commands));
443 if (!mp->commands) {
444 mp->num_commands = 0;
445 err = -ENOMEM12;
446 break;
447 }
448
449 for (ci = 0; ci < mp->num_commands; ci++) {
450 err = capture_command(&mp->commands[ci],
451 plugin->commands[ci], plugin);
452 if (err)
453 break;
454 }
455 if (err)
456 break;
457 }
458
459 argconfig_set_parse_hook(NULL((void*)0));
460 remove_crash_handlers();
461 capture_saved_stderr_fd = -1;
462
463 fflush(stdoutstdout);
464 fflush(stderrstderr);
465 if (saved_stdout >= 0) {
466 dup2(saved_stdout, STDOUT_FILENO1);
467 close(saved_stdout);
468 }
469 if (saved_stderr >= 0) {
470 dup2(saved_stderr, STDERR_FILENO2);
471 close(saved_stderr);
472 }
473 if (devnull >= 0)
474 close(devnull);
475
476 /*
477 * An allocation failure while capturing would leave an incomplete model
478 * that looks complete in the emitted JSON; fail the dump instead. The
479 * partial model is left for process exit to reclaim (it is never freed
480 * on the success path either).
481 */
482 if (err)
483 return NULL((void*)0);
484
485 return model;
486}
487
488/* ------------------------------------------------------------------ */
489/* Model helpers shared by emitters */
490/* ------------------------------------------------------------------ */
491
492static bool_Bool opt_is_separator(const struct command_metadata_option *o)
493{
494 return o->config_type == CFG_GROUP_SEPARATOR;
495}
496
497static bool_Bool opt_is_global_separator(const struct command_metadata_option *o)
498{
499 return opt_is_separator(o) && o->help && !strcmp(o->help, "Global options");
500}
501
502/* "none" / "required" / "optional" — how the option consumes its argument. */
503static const char *opt_argument(const struct command_metadata_option *o)
504{
505 switch (o->argument_type) {
506 case optional_argument2:
507 return "optional";
508 case no_argument0:
509 return "none";
510 default:
511 return "required";
512 }
513}
514
515static bool_Bool opt_takes_value(const struct command_metadata_option *o)
516{
517 return o->argument_type != no_argument0;
518}
519
520/*
521 * True for an option that should be emitted: a real, named, non-separator
522 * option. Hidden options are emitted too (tagged "hidden" in the output) so
523 * the dump describes the full set of accepted options; consumers that only
524 * want user-facing options (e.g. completion generators) filter on that tag.
525 */
526static bool_Bool opt_is_emittable(const struct command_metadata_option *o)
527{
528 return !opt_is_separator(o) && o->option && o->option[0];
529}
530
531/* ------------------------------------------------------------------ */
532/* Pass 2: JSON */
533/* ------------------------------------------------------------------ */
534
535/*
536 * The value set for an option, when the generator can derive it. Returns a
537 * json array of strings, or NULL if the option has no known value set. The
538 * caller owns the returned array.
539 *
540 * output-format is special-cased because its values are not represented via an
541 * opt_val table; keep the hard-coded list below in sync with
542 * validate_output_format() / DESC_OUTPUT_FORMAT. Since the whole command is
543 * compiled out without json-c, "json" is always a valid value here. Every
544 * other value set comes from the option's opt_val table, which is the set the
545 * parser actually enforces; options whose value is unconstrained (e.g. any
546 * OPT_UINT such as output-format-version) have no values array.
547 */
548static struct json_object *json_option_values(const struct command_metadata_option *o)
549{
550 const struct argconfig_opt_val *v;
551 struct json_object *vals;
552
553 if (!strcmp(o->option, "output-format")) {
554 vals = json_create_array()json_object_new_array();
555 json_array_add_value_string(vals, "normal");
556 json_array_add_value_string(vals, "json");
557 json_array_add_value_string(vals, "binary");
558 json_array_add_value_string(vals, "tabular");
559 return vals;
560 }
561 if (!o->opt_val)
562 return NULL((void*)0);
563
564 vals = json_create_array()json_object_new_array();
565 for (v = o->opt_val; v->str; v++)
566 json_array_add_value_string(vals, v->str);
567 return vals;
568}
569
570/* Build one option as a json object and add it to the given array. */
571static void json_option(struct json_object *arr, const struct command_metadata_option *o,
572 bool_Bool global)
573{
574 struct json_object *jo, *vals;
575 char shortbuf[2] = { o->short_option, '\0' };
576
577 if (!opt_is_emittable(o))
578 return;
579
580 jo = json_create_object()json_object_new_object();
581 json_object_add_value_string(jo, "long", o->option);
582 if (o->short_option)
583 json_object_add_value_string(jo, "short", shortbuf);
584 json_object_add_value_string(jo, "argument", opt_argument(o));
585 if (o->meta && opt_takes_value(o))
586 json_object_add_value_string(jo, "metavar", o->meta);
587 if (o->help)
588 json_object_add_value_string(jo, "description", o->help);
589 if (global)
590 json_object_add_value_bool(jo, "global", true)json_object_object_add(jo, "global", json_object_new_boolean(
1))
;
591 if (o->hidden)
592 json_object_add_value_bool(jo, "hidden", true)json_object_object_add(jo, "hidden", json_object_new_boolean(
1))
;
593
594 vals = json_option_values(o);
595 if (vals)
596 json_object_add_value_array(jo, "values", vals)json_object_object_add(jo, "values", vals);
597
598 json_array_add_value_object(arr, jo)json_object_array_add(arr, jo);
599}
600
601/* Build one command as a json object: name, alias, description, and options. */
602static struct json_object *json_command(const struct command_metadata_command *c)
603{
604 struct json_object *jc, *opts;
605 bool_Bool global = false0;
606 size_t i;
607
608 jc = json_create_object()json_object_new_object();
609 json_object_add_value_string(jc, "name", c->name);
610 if (c->alias)
611 json_object_add_value_string(jc, "alias", c->alias);
612 if (c->help)
613 json_object_add_value_string(jc, "description", c->help);
614
615 opts = json_create_array()json_object_new_array();
616 for (i = 0; i < c->num_options; i++) {
617 /*
618 * Options after the "Global options" separator are the shared
619 * NVME_ARGS globals; flag them so generators can group them.
620 */
621 if (opt_is_global_separator(&c->options[i])) {
622 global = true1;
623 continue;
624 }
625 json_option(opts, &c->options[i], global);
626 }
627 json_object_add_value_array(jc, "options", opts)json_object_object_add(jc, "options", opts);
628
629 return jc;
630}
631
632/* Build one named plugin as a json object: name, description, commands. */
633static struct json_object *json_plugin(const struct command_metadata_plugin *p)
634{
635 struct json_object *jp, *cmds;
636 size_t i;
637
638 jp = json_create_object()json_object_new_object();
639 assert(p->name)((void) sizeof (__assert_single_arg (p->name)), __extension__
({ if (p->name) ; else __assert_fail ("p->name", "../plugins/utils/command-metadata.c"
, 639, __extension__ __PRETTY_FUNCTION__); }))
; /* builtin (NULL-name) is emitted inline by json_program() */
640 json_object_add_value_string(jp, "name", p->name);
641 if (p->desc)
642 json_object_add_value_string(jp, "description", p->desc);
643
644 cmds = json_create_array()json_object_new_array();
645 for (i = 0; i < p->num_commands; i++)
646 json_array_add_value_object(cmds, json_command(&p->commands[i]))json_object_array_add(cmds, json_command(&p->commands[
i]))
;
647 json_object_add_value_array(jp, "commands", cmds)json_object_object_add(jp, "commands", cmds);
648
649 return jp;
650}
651
652static void json_program(const struct command_metadata_program *m)
653{
654 struct json_object *root, *builtin, *plugins;
655 size_t i;
656
657 root = json_create_object()json_object_new_object();
658 json_object_add_value_int(root, "schema_version",json_object_object_add(root, "schema_version", json_object_new_int
(1))
659 COMMAND_METADATA_SCHEMA_VERSION)json_object_object_add(root, "schema_version", json_object_new_int
(1))
;
660 json_object_add_value_string(root, "name", m->name);
661 if (m->version)
662 json_object_add_value_string(root, "version", m->version);
663 if (m->desc)
664 json_object_add_value_string(root, "description", m->desc);
665
666 /*
667 * Builtin (top-level) commands live in their own array; named plugins
668 * go under "plugins" so generators can build the dispatch nesting.
669 */
670 builtin = json_create_array()json_object_new_array();
671 plugins = json_create_array()json_object_new_array();
672 for (i = 0; i < m->num_plugins; i++) {
673 const struct command_metadata_plugin *p = &m->plugins[i];
674 size_t j;
675
676 if (!p->name) {
677 for (j = 0; j < p->num_commands; j++)
678 json_array_add_value_object(builtin,json_object_array_add(builtin, json_command(&p->commands
[j]))
679 json_command(&p->commands[j]))json_object_array_add(builtin, json_command(&p->commands
[j]))
;
680 } else {
681 json_array_add_value_object(plugins, json_plugin(p))json_object_array_add(plugins, json_plugin(p));
682 }
683 }
684 json_object_add_value_array(root, "commands", builtin)json_object_object_add(root, "commands", builtin);
685 json_object_add_value_array(root, "plugins", plugins)json_object_object_add(root, "plugins", plugins);
686
687 json_print_object(root, NULL)printf("%s", json_object_to_json_string_ext(root, (1 <<
1) | (1 << 4)))
;
688 printf("\n");
689 json_free_object(root)json_object_put(root);
690}
691
692/* ------------------------------------------------------------------ */
693/* Entry point */
694/* ------------------------------------------------------------------ */
695
696int dump_command_metadata(struct program *prog)
697{
698 struct command_metadata_program *model;
699
700 model = build_model(prog);
701 if (!model)
702 return -ENOMEM12;
703
704 json_program(model);
705
706 free(model->plugins);
707 free(model);
708 return 0;
709}
710
711#endif /* CONFIG_JSONC */