Bug Summary

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

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-11-02-081605-642-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, with nothing left allocated.
284 */
285static int copy_options(const struct argconfig_commandline_options *opts,
286 struct command_metadata_option **out, size_t *n_out)
287{
288 const struct argconfig_commandline_options *s;
289 struct command_metadata_option *dst;
290 size_t n = 0, i;
291
292 *out = NULL((void*)0);
293 *n_out = 0;
294
295 for (s = opts; s->option; s++)
11
Loop condition is false. Execution continues on line 298
296 n++;
297
298 if (!n
11.1
'n' is 0
) /* calloc(0) may return NULL, indistinguishable from OOM */
12
Taking true branch
299 return 0;
13
Returning without deallocating memory or storing the pointer for later deallocation
300
301 dst = calloc(n, sizeof(*dst));
302 if (!dst)
303 return -ENOMEM12;
304
305 /*
306 * Deep-copy: option/meta/help and the opt_val table are valid while the
307 * parser runs but may point at command-local storage that is freed once
308 * the command's fn returns, so duplicate rather than alias them.
309 */
310 for (i = 0; i < n; i++) {
311 if (dup_field(opts[i].option, &dst[i].option) ||
312 dup_field(opts[i].meta, &dst[i].meta) ||
313 dup_field(opts[i].help, &dst[i].help) ||
314 dup_field(opts[i].deprecated, &dst[i].deprecated) ||
315 dup_field(opts[i].old_name, &dst[i].old_name) ||
316 copy_opt_val(opts[i].opt_val, &dst[i].opt_val)) {
317 size_t j;
318
319 for (j = 0; j <= i; j++)
320 free_option_fields(&dst[j]);
321 free(dst);
322 return -ENOMEM12;
323 }
324 dst[i].short_option = opts[i].short_option;
325 dst[i].config_type = opts[i].config_type;
326 dst[i].argument_type = opts[i].argument_type;
327 dst[i].hidden = opts[i].deprecated;
328 dst[i].min = opts[i].min;
329 dst[i].max = opts[i].max;
330 }
331
332 *out = dst;
333 *n_out = n;
334 return 0;
335}
336
337/* The entries of @opts with the included tables in place, see flat_copy() */
338static size_t flat_count(struct argconfig_commandline_options *opts)
339{
340 size_t n = 0;
341
342 for (; opts->option; opts++) {
343 if (opts->config_type == CFG_INCLUDE)
344 n += flat_count(opts->default_value);
345 n++;
346 }
347
348 return n;
349}
350
351/* Copy @opts to @dst, an included table behind a group of its heading */
352static size_t flat_copy(struct argconfig_commandline_options *dst,
353 struct argconfig_commandline_options *opts)
354{
355 size_t n = 0;
356
357 for (; opts->option; opts++) {
358 if (opts->config_type == CFG_INCLUDE) {
359 struct argconfig_commandline_options sep =
360 OPT_GROUP(opts->help){"", 0, ((void*)0), CFG_GROUP_SEPARATOR, ((void*)0), ARGCONFIG_ARG_NONE
, opts->help, 0, ((void*)0)}
;
361
362 memcpy(&dst[n++], &sep, sizeof(sep));
363 n += flat_copy(&dst[n], opts->default_value);
364 continue;
365 }
366 memcpy(&dst[n++], opts, sizeof(*opts));
367 }
368
369 return n;
370}
371
372/*
373 * The options of @root, the included tables behind a group of their
374 * heading: json_command() takes the options after "Global options" for the
375 * global ones. The caller frees the returned array; the entries alias the
376 * original tables.
377 */
378static struct argconfig_commandline_options *
379flatten(struct argconfig_commandline_options *root)
380{
381 struct argconfig_commandline_options *all;
382
383 all = calloc(flat_count(root) + 1, sizeof(*all));
5
Memory is allocated
384 if (all)
6
Assuming 'all' is non-null
7
Taking true branch
385 flat_copy(all, root);
386
387 return all;
388}
389
390/*
391 * argconfig_parse() hook installed by build_model(): copies the current
392 * command's options, the global ones included, into the model, then
393 * returns the sentinel so the parser unwinds before the command opens a
394 * device.
395 */
396static int metadata_capture_hook(int argc, char **argv, const char *program_desc,
397 struct argconfig_commandline_options *root)
398{
399 (void)argc;
400 (void)argv;
401 (void)program_desc;
402
403 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
404 __cleanup_free__attribute__((cleanup(shr_freep))) struct argconfig_commandline_options *all =
405 flatten(root);
4
Calling 'flatten'
8
Returned allocated memory
406 int err = -ENOMEM12;
407
408 if (all
8.1
'all' is non-null
)
9
Taking true branch
409 err = copy_options(all,
10
Calling 'copy_options'
14
Returning from 'copy_options'
15
Potential leak of memory pointed to by 'all'
410 &command_metadata_cur_command->options,
411 &command_metadata_cur_command->num_options);
412 if (err)
413 command_metadata_capture_error = err;
414 command_metadata_cur_command->captured = true1;
415 }
416
417 return METADATA_CAPTURE_SENTINEL(-125);
418}
419
420/* Returns 0 on success, or -ENOMEM if the capture hook failed to allocate. */
421static int capture_command(struct command_metadata_command *mc, struct command *cmd,
422 struct plugin *plugin)
423{
424 /*
425 * argv[1] is a placeholder device; the sentinel returns before it is
426 * ever opened, so it need not (and must not) name a real device.
427 */
428 char *argv[] = { cmd->name, (char *)"metadata-dump-dummy-device", NULL((void*)0) };
429
430 mc->name = cmd->name;
431 mc->alias = cmd->alias;
432 mc->help = cmd->help;
433 mc->device = cmd->device;
434 mc->deprecated = cmd->deprecated;
435 mc->replacement_plugin = cmd->replacement.plugin;
436 mc->replacement_command = cmd->replacement.command;
437 mc->captured = false0;
438
439 /*
440 * Don't invoke the dump command itself: it would re-enter
441 * dump_command_metadata() and recurse forever. It has no
442 * completable options, so leave its options array empty.
443 */
444 if (!strcmp(cmd->name, "dump-command-metadata"))
445 return 0;
446
447 command_metadata_capture_error = 0;
448 command_metadata_cur_command = mc;
449 capture_current_command = cmd->name;
450 command_prepare(cmd);
451 (void)cmd->fn(2, argv, cmd, plugin);
452 capture_current_command = NULL((void*)0);
453 command_metadata_cur_command = NULL((void*)0);
454
455 /*
456 * If the hook never fired, the command returned before reaching the
457 * parser (e.g. gen-hostnqn) and has no completable options; its options
458 * array is simply left empty.
459 */
460 return command_metadata_capture_error;
461}
462
463static size_t count_commands(struct command **commands)
464{
465 size_t n = 0;
466
467 while (commands && commands[n])
468 n++;
469
470 return n;
471}
472
473static size_t count_plugins(struct plugin *p)
474{
475 size_t n = 0;
476
477 for (; p; p = p->next)
478 n++;
479
480 return n;
481}
482
483/* Free @model and everything it holds (safe on a partial model). */
484static void free_model(struct command_metadata_program *model)
485{
486 size_t pi, ci, oi;
487
488 for (pi = 0; pi < model->num_plugins; pi++) {
489 struct command_metadata_plugin *mp = &model->plugins[pi];
490
491 for (ci = 0; ci < mp->num_commands; ci++) {
492 struct command_metadata_command *mc = &mp->commands[ci];
493
494 for (oi = 0; oi < mc->num_options; oi++)
495 free_option_fields(&mc->options[oi]);
496 free(mc->options);
497 }
498 free(mp->commands);
499 }
500 free(model->plugins);
501 free(model);
502}
503
504static DEFINE_CLEANUP_FUNC(cleanup_model, struct command_metadata_program *,void cleanup_model(struct command_metadata_program * *__p) { if
(*__p) free_model(*__p); }
505 free_model)void cleanup_model(struct command_metadata_program * *__p) { if
(*__p) free_model(*__p); }
506#define __cleanup_model__attribute__((cleanup(cleanup_model))) __cleanup(cleanup_model)__attribute__((cleanup(cleanup_model)))
507
508static struct command_metadata_program *build_model(struct program *prog)
509{
510 __cleanup_model__attribute__((cleanup(cleanup_model))) struct command_metadata_program *model = NULL((void*)0);
511 struct command_metadata_program *ret;
512 struct plugin *plugin;
513 int saved_stdout = -1, saved_stderr = -1, devnull;
514 int err = 0;
515 size_t pi;
516
517 model = calloc(1, sizeof(*model));
518 if (!model)
519 return NULL((void*)0);
520
521 model->name = prog->name;
522 model->version = prog->version;
523 model->desc = prog->desc;
524 model->num_plugins = count_plugins(prog->extensions);
525 if (model->num_plugins) {
526 model->plugins = calloc(model->num_plugins, sizeof(*model->plugins));
527 if (!model->plugins)
528 return NULL((void*)0);
529 }
530
531 /*
532 * Suppress stdout/stderr while invoking command fns: some commands
533 * may print before or during option capture, which would corrupt the
534 * JSON output.
535 */
536 fflush(stdoutstdout);
537 fflush(stderrstderr);
538 devnull = open(shr_dev_null(), O_WRONLY01);
539 if (devnull >= 0) {
540 saved_stdout = dup(STDOUT_FILENO1);
541 saved_stderr = dup(STDERR_FILENO2);
542 capture_saved_stderr_fd = saved_stderr;
543 if (saved_stdout >= 0)
544 dup2(devnull, STDOUT_FILENO1);
545 if (saved_stderr >= 0)
546 dup2(devnull, STDERR_FILENO2);
547 }
548
549 install_crash_handlers();
550 argconfig_set_parse_hook(metadata_capture_hook);
551
552 for (pi = 0, plugin = prog->extensions; plugin; plugin = plugin->next, pi++) {
553 struct command_metadata_plugin *mp = &model->plugins[pi];
554 size_t ci;
555
556 mp->name = plugin->name;
557 mp->desc = plugin->desc;
558 mp->num_commands = count_commands(plugin->commands);
559 if (!mp->num_commands)
560 continue;
561 mp->commands = calloc(mp->num_commands, sizeof(*mp->commands));
562 if (!mp->commands) {
563 mp->num_commands = 0;
564 err = -ENOMEM12;
565 break;
566 }
567
568 for (ci = 0; ci < mp->num_commands; ci++) {
569 err = capture_command(&mp->commands[ci],
570 plugin->commands[ci], plugin);
571 if (err)
572 break;
573 }
574 if (err)
575 break;
576 }
577
578 argconfig_set_parse_hook(NULL((void*)0));
579 remove_crash_handlers();
580 capture_saved_stderr_fd = -1;
581
582 fflush(stdoutstdout);
583 fflush(stderrstderr);
584 if (saved_stdout >= 0) {
585 dup2(saved_stdout, STDOUT_FILENO1);
586 shr_close(saved_stdout);
587 }
588 if (saved_stderr >= 0) {
589 dup2(saved_stderr, STDERR_FILENO2);
590 shr_close(saved_stderr);
591 }
592 if (devnull >= 0)
593 shr_close(devnull);
594
595 /*
596 * An allocation failure while capturing would leave an incomplete model
597 * that looks complete in the emitted JSON; fail the dump instead.
598 */
599 if (err)
600 return NULL((void*)0);
601
602 /* The caller owns the model now. */
603 ret = model;
604 model = NULL((void*)0);
605 return ret;
606}
607
608/* ------------------------------------------------------------------ */
609/* Model helpers shared by emitters */
610/* ------------------------------------------------------------------ */
611
612static bool_Bool opt_is_separator(const struct command_metadata_option *o)
613{
614 return o->config_type == CFG_GROUP_SEPARATOR;
615}
616
617static bool_Bool opt_is_global_separator(const struct command_metadata_option *o)
618{
619 return opt_is_separator(o) && o->help && !strcmp(o->help, "Global options");
620}
621
622/* "none" / "required" / "optional" — how the option consumes its argument. */
623static const char *opt_argument(const struct command_metadata_option *o)
624{
625 switch (o->argument_type) {
626 case ARGCONFIG_ARG_OPTIONAL:
627 return "optional";
628 case ARGCONFIG_ARG_NONE:
629 return "none";
630 default:
631 return "required";
632 }
633}
634
635static bool_Bool opt_takes_value(const struct command_metadata_option *o)
636{
637 return o->argument_type != ARGCONFIG_ARG_NONE;
638}
639
640/*
641 * True for an option that should be emitted: a real, named, non-separator
642 * option. Hidden options are emitted too (tagged "hidden" in the output) so
643 * the dump describes the full set of accepted options; consumers that only
644 * want user-facing options (e.g. completion generators) filter on that tag.
645 */
646static bool_Bool opt_is_emittable(const struct command_metadata_option *o)
647{
648 return !opt_is_separator(o) && o->option && o->option[0];
649}
650
651/* ------------------------------------------------------------------ */
652/* Pass 2: JSON */
653/* ------------------------------------------------------------------ */
654
655/*
656 * The value set for an option, when the generator can derive it. Returns a
657 * json array of strings, or NULL if the option has no known value set. The
658 * caller owns the returned array.
659 *
660 * output-format is special-cased because its values are not represented via an
661 * opt_val table. Its help text lists the formats the command supports
662 * ("Output format: normal|json"), see nvme_args_set_output_formats(). Every
663 * other value set comes from the option's opt_val table: the names it
664 * accepts, a numeric option also takes a number. Options without names
665 * (e.g. output-format-version) have no values array.
666 */
667static struct json_object *json_option_values(const struct command_metadata_option *o)
668{
669 static const char prefix[] = "Output format: ";
670 const struct argconfig_opt_val *v;
671 struct json_object *vals;
672
673 if (!strcmp(o->option, "output-format") && o->help &&
674 !strncmp(o->help, prefix, strlen(prefix))) {
675 __cleanup_free__attribute__((cleanup(shr_freep))) char *formats = strdup(o->help + strlen(prefix));
676 char *p, *name;
677
678 if (!formats)
679 return NULL((void*)0);
680
681 vals = json_create_array()json_object_new_array();
682 p = formats;
683 while ((name = shr_strsep(&p, "|")) != NULL((void*)0))
684 json_array_add_value_string(vals, name);
685 return vals;
686 }
687 if (!o->opt_val)
688 return NULL((void*)0);
689
690 vals = json_create_array()json_object_new_array();
691 for (v = o->opt_val; v->str; v++)
692 json_array_add_value_string(vals, v->str);
693 return vals;
694}
695
696/* The kind of value an option takes, see argconfig_parse_type() */
697static const char *opt_type(const struct command_metadata_option *o)
698{
699 switch (o->config_type) {
700 case CFG_FLAG:
701 return "flag";
702 case CFG_INCREMENT:
703 return "count";
704 case CFG_INT:
705 return "int";
706 case CFG_BYTE:
707 return "u8";
708 case CFG_SHORT:
709 return "u16";
710 case CFG_POSITIVE:
711 return "u32";
712 case CFG_LONG:
713 return "u64";
714 case CFG_LONG_SUFFIX:
715 return "size";
716 case CFG_DOUBLE:
717 return "double";
718 case CFG_STRING:
719 return "string";
720 case CFG_ENUM:
721 return "enum";
722 case CFG_ENUM_LIST:
723 return "enum-list";
724 case CFG_FILE:
725 return "file";
726 case CFG_DIR:
727 return "directory";
728 case CFG_GROUP_SEPARATOR:
729 case CFG_INCLUDE:
730 break;
731 }
732
733 return NULL((void*)0);
734}
735
736/* Build one option as a json object and add it to the given array. */
737static void json_option(struct json_object *arr, const struct command_metadata_option *o,
738 bool_Bool global)
739{
740 struct json_object *jo, *vals;
741 char shortbuf[2] = { o->short_option, '\0' };
742
743 if (!opt_is_emittable(o))
744 return;
745
746 jo = json_create_object()json_object_new_object();
747 json_object_add_value_string(jo, "long", o->option);
748 if (o->short_option)
749 json_object_add_value_string(jo, "short", shortbuf);
750 json_object_add_value_string(jo, "argument", opt_argument(o));
751 if (opt_type(o))
752 json_object_add_value_string(jo, "type", opt_type(o));
753 if (o->max) {
754 struct json_object *range = json_create_object()json_object_new_object();
755
756 json_object_add_value_uint64(range, "min", o->min)json_object_object_add(range, "min", json_object_new_uint64(o
->min))
;
757 json_object_add_value_uint64(range, "max", o->max)json_object_object_add(range, "max", json_object_new_uint64(o
->max))
;
758 json_object_add_value_object(jo, "range", range)json_object_object_add(jo, "range", range);
759 }
760 if (o->meta && opt_takes_value(o))
761 json_object_add_value_string(jo, "metavar", o->meta);
762 if (o->help)
763 json_object_add_value_string(jo, "description", o->help);
764 if (global)
765 json_object_add_value_bool(jo, "global", true)json_object_object_add(jo, "global", json_object_new_boolean(
1))
;
766 if (o->hidden)
767 json_object_add_value_bool(jo, "hidden", true)json_object_object_add(jo, "hidden", json_object_new_boolean(
1))
;
768 if (o->deprecated)
769 json_object_add_value_string(jo, "deprecated", o->deprecated);
770 if (o->old_name)
771 json_object_add_value_string(jo, "old_name", o->old_name);
772
773 vals = json_option_values(o);
774 if (vals)
775 json_object_add_value_array(jo, "values", vals)json_object_object_add(jo, "values", vals);
776
777 json_array_add_value_object(arr, jo)json_object_array_add(arr, jo);
778}
779
780/* Build one command as a json object: name, alias, description, and options. */
781/* The device operand of a command, see enum command_device */
782static const char * const device_names[] = {
783 [COMMAND_DEVICE] = "required",
784 [COMMAND_DEVICE_OPTIONAL] = "optional",
785 [COMMAND_NO_DEVICE] = "none",
786};
787
788static struct json_object *json_command(const struct command_metadata_command *c)
789{
790 struct json_object *jc, *opts;
791 bool_Bool global = false0;
792 size_t i;
793
794 jc = json_create_object()json_object_new_object();
795 json_object_add_value_string(jc, "name", c->name);
796 if (c->alias)
797 json_object_add_value_string(jc, "alias", c->alias);
798 if (c->help)
799 json_object_add_value_string(jc, "description", c->help);
800 json_object_add_value_string(jc, "device", device_names[c->device]);
801 if (c->deprecated)
802 json_object_add_value_bool(jc, "deprecated", true)json_object_object_add(jc, "deprecated", json_object_new_boolean
(1))
;
803 if (c->replacement_plugin && c->replacement_command) {
804 char replacement[0x80];
805
806 snprintf(replacement, sizeof(replacement), "%s %s",
807 c->replacement_plugin, c->replacement_command);
808 json_object_add_value_string(jc, "replacement", replacement);
809 }
810
811 opts = json_create_array()json_object_new_array();
812 for (i = 0; i < c->num_options; i++) {
813 /*
814 * Options after the "Global options" separator are the
815 * global options; flag them so generators can group them.
816 */
817 if (opt_is_global_separator(&c->options[i])) {
818 global = true1;
819 continue;
820 }
821 json_option(opts, &c->options[i], global);
822 }
823 json_object_add_value_array(jc, "options", opts)json_object_object_add(jc, "options", opts);
824
825 return jc;
826}
827
828/* Build one named plugin as a json object: name, description, commands. */
829static struct json_object *json_plugin(const struct command_metadata_plugin *p)
830{
831 struct json_object *jp, *cmds;
832 size_t i;
833
834 jp = json_create_object()json_object_new_object();
835 assert(p->name)((void) sizeof (__assert_single_arg (p->name)), __extension__
({ if (p->name) ; else __assert_fail ("p->name", "../plugins/utils/command-metadata.c"
, 835, __extension__ __PRETTY_FUNCTION__); }))
; /* builtin (NULL-name) is emitted inline by json_program() */
836 json_object_add_value_string(jp, "name", p->name);
837 if (p->desc)
838 json_object_add_value_string(jp, "description", p->desc);
839
840 cmds = json_create_array()json_object_new_array();
841 for (i = 0; i < p->num_commands; i++)
842 json_array_add_value_object(cmds, json_command(&p->commands[i]))json_object_array_add(cmds, json_command(&p->commands[
i]))
;
843 json_object_add_value_array(jp, "commands", cmds)json_object_object_add(jp, "commands", cmds);
844
845 return jp;
846}
847
848static void json_program(const struct command_metadata_program *m)
849{
850 struct json_object *root, *builtin, *plugins;
851 size_t i;
852
853 root = json_create_object()json_object_new_object();
854 json_object_add_value_int(root, "schema_version",json_object_object_add(root, "schema_version", json_object_new_int
(2))
855 COMMAND_METADATA_SCHEMA_VERSION)json_object_object_add(root, "schema_version", json_object_new_int
(2))
;
856 json_object_add_value_string(root, "name", m->name);
857 if (m->version)
858 json_object_add_value_string(root, "version", m->version);
859 if (m->desc)
860 json_object_add_value_string(root, "description", m->desc);
861
862 /*
863 * Builtin (top-level) commands live in their own array; named plugins
864 * go under "plugins" so generators can build the dispatch nesting.
865 */
866 builtin = json_create_array()json_object_new_array();
867 plugins = json_create_array()json_object_new_array();
868 for (i = 0; i < m->num_plugins; i++) {
869 const struct command_metadata_plugin *p = &m->plugins[i];
870 size_t j;
871
872 if (!p->name) {
873 for (j = 0; j < p->num_commands; j++)
874 json_array_add_value_object(builtin,json_object_array_add(builtin, json_command(&p->commands
[j]))
875 json_command(&p->commands[j]))json_object_array_add(builtin, json_command(&p->commands
[j]))
;
876 } else {
877 json_array_add_value_object(plugins, json_plugin(p))json_object_array_add(plugins, json_plugin(p));
878 }
879 }
880 json_object_add_value_array(root, "commands", builtin)json_object_object_add(root, "commands", builtin);
881 json_object_add_value_array(root, "plugins", plugins)json_object_object_add(root, "plugins", plugins);
882
883 json_print_object(root, NULL)printf("%s", json_object_to_json_string_ext(root, (1 <<
1) | (1 << 4)))
;
884 printf("\n");
885 json_free_object(root)json_object_put(root);
886}
887
888/* ------------------------------------------------------------------ */
889/* Entry point */
890/* ------------------------------------------------------------------ */
891
892int dump_command_metadata(struct program *prog)
893{
894 __cleanup_model__attribute__((cleanup(cleanup_model))) struct command_metadata_program *model = NULL((void*)0);
895
896 model = build_model(prog);
897 if (!model)
898 return -ENOMEM12;
899
900 json_program(model);
901
902 return 0;
903}
904
905#endif /* CONFIG_JSONC */