| File: | .build-ci/../plugins/utils/command-metadata.c |
| Warning: | line 294, column 12 Potential leak of memory pointed to by 'dst' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 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 | */ | |||
| 63 | static 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 | */ | |||
| 69 | static 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 | */ | |||
| 75 | static const char *capture_current_command; | |||
| 76 | static 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 | */ | |||
| 82 | static 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. */ | |||
| 89 | static 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 | ||||
| 102 | static 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 | ||||
| 115 | static 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 | |||
| 136 | static 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 | ||||
| 149 | static 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 | ||||
| 162 | static LPTOP_LEVEL_EXCEPTION_FILTER capture_prev_filter; | |||
| 163 | #endif | |||
| 164 | ||||
| 165 | static void (*capture_prev_sigsegv)(int); | |||
| 166 | static void (*capture_prev_sigabrt)(int); | |||
| 167 | #ifdef SIGBUS7 | |||
| 168 | static void (*capture_prev_sigbus)(int); | |||
| 169 | #endif | |||
| 170 | ||||
| 171 | /* Normalize SIG_ERR to SIG_DFL so restoration is always a valid disposition. */ | |||
| 172 | static 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 | ||||
| 179 | static 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 | ||||
| 191 | static 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 | ||||
| 208 | static 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 | */ | |||
| 218 | static 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 | */ | |||
| 251 | static 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 | */ | |||
| 264 | static 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++) | |||
| 275 | n++; | |||
| 276 | ||||
| 277 | if (!n
| |||
| 278 | return 0; | |||
| 279 | ||||
| 280 | dst = calloc(n, sizeof(*dst)); | |||
| 281 | if (!dst) | |||
| 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; | |||
| ||||
| 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 | */ | |||
| 311 | static 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) { | |||
| ||||
| 319 | int err = copy_options(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. */ | |||
| 331 | static 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 | ||||
| 368 | static 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 | ||||
| 378 | static 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 | ||||
| 388 | static 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 | ||||
| 492 | static bool_Bool opt_is_separator(const struct command_metadata_option *o) | |||
| 493 | { | |||
| 494 | return o->config_type == CFG_GROUP_SEPARATOR; | |||
| 495 | } | |||
| 496 | ||||
| 497 | static 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. */ | |||
| 503 | static 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 | ||||
| 515 | static 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 | */ | |||
| 526 | static 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 | */ | |||
| 548 | static 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. */ | |||
| 571 | static 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. */ | |||
| 602 | static 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. */ | |||
| 633 | static 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 | ||||
| 652 | static 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 | ||||
| 696 | int 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 */ |