Skip to content

nvme: add dump-command-metadata command - #3603

Merged
igaw merged 1 commit into
linux-nvme:masterfrom
Micron-TPG-OSS:dump-command-metadata
Jul 30, 2026
Merged

nvme: add dump-command-metadata command#3603
igaw merged 1 commit into
linux-nvme:masterfrom
Micron-TPG-OSS:dump-command-metadata

Conversation

@jimmunn

@jimmunn jimmunn commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Add nvme dump-command-metadata, which walks the live plugin/command tree and emits every command and its options as JSON describing the CLI surface. The command needs no device and is gated on CONFIG_JSONC.

This is the first step toward generating shell completion scripts: the JSON output is intended to be consumed by a generator that produces completions for bash, zsh, and PowerShell.

To capture options, dump-command-metadata invokes each command's fn, which calls argconfig_parse(). A hook there copies the command's options array and returns a sentinel so the command unwinds before opening a device, rather than actually running it.

Includes command-metadata-schema.json and a Python unit test (unit-py/) that validates the output against the schema and cross-checks it against --help and nvme help.

@jimmunn
jimmunn force-pushed the dump-command-metadata branch from f84edd3 to edb77d4 Compare July 21, 2026 20:59
Comment thread util/argconfig.c
void argconfig_set_parse_hook(argconfig_parse_hook_fn hook)
{
argconfig_parse_hook = hook;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change is a bit big and there are a few things which could go in first before the main feature. That would make it a bit simpler to review. The hook here could go independent (the commit message should explain what it is for).

Comment thread util/argconfig.h Outdated
* running getopt or touching any state. Used by dump-command-metadata to
* capture each command's options array. NULL (the default) means normal
* parsing.
*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LLMs tend to overdocument stuff in the source code. This information is good for the commit message, I don't think we should have it here. It's kind of obvious from the name of the function. I a big a fan of self explaining code. Comments are the last resort IMO.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed comment in other PR that has been merged.

Comment thread util/json.h
Comment thread plugins/utils/command-metadata.c
@@ -0,0 +1,129 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/linux-nvme/nvme-cli/command-metadata-schema.json",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we place this file somewhere else? I still want to cleanup the root directory eventually, it's crowded with code, configuration files, compliance files, project documentation, etc.

For the config-schema.json.in is currently in libnvme/doc/ what about having it in Documentation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to new plugin directory.

Comment thread .gitignore
* json-c support: nvme-builtin.h does not register it and nvme.c does not
* define its handler, so dump_command_metadata() is never referenced.
*/
#ifdef CONFIG_JSONC

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to make this command a build option instead coupling it to CONFIG_JSONC being around. This way we can disable in default builds and only enable when updating the tab completion files.

Comment thread plugins/utils/command-metadata.h
Comment thread command-metadata.c Outdated
/*
* Suppress stdout/stderr while invoking command fns: a few commands
* print before they reach the parser (e.g. gen-hostnqn), and some emit
* parse-error diagnostics in reaction to the capture sentinel.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking at gen-hostnqn (and another one) recently, I a noticed it doesn't use the arg parser. I think we should add this to those commands. Every command should invoke the arg parser otherwise the globals argument options are not supported.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few other commands that don't use the arg parser. I think this is the definitive list:

  • gen-hostnqn
  • show-hostnqn
  • zns list
  • micron plugin-version
  • micron cloud-SSD-plugin-version
  • seagate plugin-version
  • seagate cloud-SSD-plugin-version

Do we want all of these to call the argument parser. I am pretty sure at the very least the --output-format will work on all of them. If so, this should probably be in it's own PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I modified this comment to make it more generic. Once we change these commands, the comment would have been stale.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think we should fix those commands up to use the nvme arg parser. I agree this should go into a separate PR.

Comment thread fabrics.c
Comment thread meson.build Outdated
if json_c_dep.found()
sources += [
'nvme-print-json.c',
'command-metadata.c',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a meson build option for this feature.

@igaw

igaw commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Looks good overall.

I'd like to have some of the stuff as preparation patches. And making an dedicated build option, this is not useful for normal users.

@jimmunn

jimmunn commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Looks good overall.

I'd like to have some of the stuff as preparation patches. And making an dedicated build option, this is not useful for normal users.

I think this could be useful for normal users. It would allow consuming scripts to determine if a newer command exists, or change how it calls a command based on changes to its options. I've seen too much code in other projects that checks for version numbers and make decisions based on that. This provides usees a machine readable structured representation of the API to use to make decisions within their scripts.

@igaw

igaw commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Fair enough. I haven't thought about the runtime introspection use case. I suppose this would be useful when wrapping a GUI on top?

What about moving the command into a plugin. I'd like to avoid cluttering the top name space and gives the user the opportunity decided if it's enabled or not. Furthermore, it would bundle the code in one directory.

@jimmunn
jimmunn force-pushed the dump-command-metadata branch from edb77d4 to 340815c Compare July 23, 2026 15:21
@jimmunn

jimmunn commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current master. The prep changes this depended on are now merged upstream, so the remaining diff is just the command, schema, and test. Also reworded the subject to the nvme: prefix.

@jimmunn jimmunn changed the title feat: add dump-command-metadata command nvme: add dump-command-metadata command Jul 23, 2026
@jimmunn
jimmunn force-pushed the dump-command-metadata branch from 340815c to 3ee848e Compare July 23, 2026 16:50
@jimmunn

jimmunn commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Added the missing copyright lines in command-metadata.c and command-metadata.h.

@jimmunn
jimmunn force-pushed the dump-command-metadata branch from 3ee848e to 0564252 Compare July 23, 2026 22:14
@jimmunn

jimmunn commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

What about moving the command into a plugin. I'd like to avoid cluttering the top name space and gives the user the opportunity decided if it's enabled or not. Furthermore, it would bundle the code in one directory.

I like the idea of a plugin. Now comes the hardest part of programming, naming. We bounced around some ideas here and we came up with "utils", "cli" and "cli-utils" (plus a few others that didn't make the cut). So, it would look like one of these:

nvme utils dump-command-metadata
nvme cli dump-command-metadata
nvme cli-utils dump-command-metadata

I don't know what other commands, if any, may end up in this plugin. The concern with "utils" is that it could become a junk drawer of miscellaneous commands, but that may be OK. I am leaning towards "utils" but am fine with the others. Do you have a preference?

Do you want the creation of the new plugin in its own PR, or I could just create a new commit in this PR that just adds the plugin then another commit to move the dump-command-metadata command and related files to the new plugin.

@igaw

igaw commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Naming is difficult. I didn't propose any names in my original comment because I could only think of bad ones, and I didn't want to influence your suggestions. My initial idea was meta, which is obviously not a great name.

Both utils and cli sound fine to me. I think utils is a bit more self-explanatory and user-friendly.

It's fine if you create the plugin within this PR or as a separate PR. It's both fine. What is simpler for you works for me.

@jimmunn
jimmunn force-pushed the dump-command-metadata branch 2 times, most recently from ccb53c0 to 2a4b0f5 Compare July 24, 2026 22:04
@igaw

igaw commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

I was about to merge the PR but then I noticed that the Windows build fails:

=================================== 31/31 ====================================
test:         nvme-cli:nvme-cli - command-metadata-schema
start time:   22:07:39
duration:     0.21s
result:       exit status 1
command:      MALLOC_PERTURB_=36 MSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1 MESON_TEST_ITERATION=1 UBSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1 ASAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1 PATH=D:/a/nvme-cli/nvme-cli/.build-ci/libnvme/src;D:/a/nvme-cli/nvme-cli/.build-ci/;D:\a\_temp\msys64\ucrt64\bin;D:\a\_temp\msys64\usr\local\bin;D:\a\_temp\msys64\usr\bin;D:\a\_temp\msys64\usr\bin;C:\Windows\System32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;D:\a\_temp\msys64\usr\bin\site_perl;D:\a\_temp\msys64\usr\bin\vendor_perl;D:\a\_temp\msys64\usr\bin\core_perl D:\a\_temp\msys64\ucrt64\bin/python3.EXE D:/a/nvme-cli/nvme-cli/.build-ci/../unit-py/test_command_metadata_schema.py ./nvme.exe D:/a/nvme-cli/nvme-cli/plugins/utils/command-metadata-schema.json
----------------------------------- stderr -----------------------------------
E
======================================================================
ERROR: setUpClass (__main__.TestCommandMetadataSchema)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/a/nvme-cli/nvme-cli/.build-ci/../unit-py/test_command_metadata_schema.py", line 133, in setUpClass
    raise AssertionError(
        "dump-command-metadata produced no output (built with json-c)")
AssertionError: dump-command-metadata produced no output (built with json-c)

----------------------------------------------------------------------

From the build logs, it looks like there is json-c support enabled. So not clear what's going on here.

BTW, I think you can squash all three patches together. I would have done that anyway :)

@jimmunn

jimmunn commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

@igaw , the Windows build was failing due to memblaze perf-stats-print-x crashing on Windows. PR #3654 fixes that. I also added some better error handling to this PR to provide visibility to a crash like that. Once PR #3654 is merged, the Windows build for this PR should pass.

@igaw igaw added this to the 3.0 milestone Jul 28, 2026
Comment thread plugins/utils/command-metadata.c Outdated
(void)write(fd, &"0123456789"[sig / 100], 1);
if (sig >= 10)
(void)write(fd, &"0123456789"[(sig / 10) % 10], 1);
(void)write(fd, &"0123456789"[sig % 10], 1);

@igaw igaw Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uff... please use something like

static void write_uint(int fd, unsigned int n)
{
	char buf[3];
	int i = sizeof(buf);

	do {
		buf[--i] = '0' + (n % 10);
		n /= 10;
	} while (n && i);

	write(fd, buf + i, sizeof(buf) - i);
}

write_str(fd, "' crashed during option capture (signal ");
write_uint(fd, sig);
write_str(fd, ")\n");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added your write_uint function. I also added a corresponding write_hex function to follow the same pattern.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added write_raw to avoid a build failure.

@igaw

igaw commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

I'm going to merge the first part of PR, then you can rebase and update the last patch.

@jimmunn
jimmunn force-pushed the dump-command-metadata branch 2 times, most recently from ed79109 to 3542a1c Compare July 28, 2026 17:21
@igaw
igaw requested a review from Copilot July 29, 2026 12:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds infrastructure to expose nvme-cli’s full command/option surface as machine-readable JSON, intended as an input for generating shell completion scripts and for drift checks/validation in CI.

Changes:

  • Improve resilience/diagnostics around metadata capture by installing crash/exception handlers and preserving a usable stderr for fatal messages.
  • Enhance the Python schema test’s failure reporting by returning stderr alongside parsed JSON and including it in assertion messages.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
unit-py/test_command_metadata_schema.py Improves test diagnostics by propagating stderr from dump-command-metadata into assertion output.
plugins/utils/command-metadata.c Adds crash/exception handling and stderr preservation during option-capture to avoid silent failures and corrupted JSON output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +88 to +105
static void write_str(int fd, const char *s)
{
if (s)
write_raw(fd, s, strlen(s));
}

static void write_uint(int fd, unsigned int n)
{
char buf[3];
int i = sizeof(buf);

do {
buf[--i] = '0' + (n % 10);
n /= 10;
} while (n && i);

write_raw(fd, buf + i, sizeof(buf) - i);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know that strlen is not safe to use in the signal handler...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I addressed the Copilot reported issues in the latest push:

  • write_str now computes the length inline instead of calling strlen, which isn't async-signal-safe.
  • write_uint's buffer is widened to 10 bytes so any 32-bit unsigned int formats without dropping leading digits.

write_hex already sized its buffer correctly, so it's unchanged.

@igaw

igaw commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

I've tried this on Linux and it doesn't produce any output:

For this test I added a signal(SIGTERM); to smart-log

› meson test -C .build -v "nvme-cli:nvme-cli - command-metadata-schema"
ninja: Entering directory `/home/wagi/work/nvme-cli-upstream/.build'
ninja: no work to do.
1/1 nvme-cli:nvme-cli - command-metadata-schema RUNNING
>>> UBSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1 ASAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1 LD_LIBRARY_PATH=/home/wagi/work/nvme-cli-upstream/.build/libnvme/src MSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1 MALLOC_PERTURB_=110 MESON_TEST_ITERATION=1 /usr/bin/python3 /home/wagi/work/nvme-cli-upstream/.build/../unit-py/test_command_metadata_schema.py ./nvme /home/wagi/work/nvme-cli-upstream/plugins/utils/command-metadata-schema.json
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ✀  ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
E
======================================================================
ERROR: setUpClass (__main__.TestCommandMetadataSchema)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/wagi/work/nvme-cli-upstream/.build/../unit-py/test_command_metadata_schema.py", line 136, in setUpClass
    raise AssertionError(msg)
AssertionError: dump-command-metadata produced no output (built with json-c)

----------------------------------------------------------------------
Ran 0 tests in 0.004s

FAILED (errors=1)

@jimmunn

jimmunn commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

I've tried this on Linux and it doesn't produce any output:

For this test I added a signal(SIGTERM); to smart-log

The lack of output here is by design and expected. If a command crashes while capturing its options, then we didn't capture them — so any output would be silently incomplete. Rather than emit a partial/misleading dump, we emit nothing.

Also, SIGTERM isn't a good signal for testing this. SIGTERM can't be raised by the running code itself — it's delivered from outside the process (a kill, a timeout, a service manager). So we don't handle it, and we don't print that a command crashed, because one didn't: the process was terminated externally.

To exercise the crash path, inject raise(SIGSEGV) or abort() (SIGABRT) instead — those are the fault signals the capture handler catches, and you'll see the dump-command-metadata: fatal: '' crashed during option capture diagnostic.

@jimmunn
jimmunn force-pushed the dump-command-metadata branch 2 times, most recently from 101578f to 78587da Compare July 29, 2026 17:35
@igaw

igaw commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

ah okay, that explains it. btw, copilot was unhappy about the signal, raise sequence: #3686 (comment)

haven't really spend time on trying to make sense of it. if you say it is bogus, I'll merge this version.

Print a diagnostic naming the offending command if it crashes during the
capture pass.

Surface that diagnostic in the schema test's failure output so a capture
crash is diagnosable from CI logs.

Signed-off-by: Jim Munn <jlmunn@micron.com>
@jimmunn
jimmunn force-pushed the dump-command-metadata branch from 78587da to 03c0597 Compare July 29, 2026 22:31
@jimmunn

jimmunn commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

ah okay, that explains it. btw, copilot was unhappy about the signal, raise sequence: #3686 (comment)

haven't really spend time on trying to make sense of it. if you say it is bogus, I'll merge this version.

Yeah, it's bogus. Both signal() and raise() are explicitly listed as async-signal-safe in signal-safety(7). The diagnostic already uses write(2), and the reset-to-SIG_DFL-then-raise() is intentional — it preserves the core dump and the correct exit status.

Copilot's other comment on that branch was legit, though: install_crash_handlers() didn't guard against signal() returning SIG_ERR, which could then get passed back to signal() on restore. I've pushed a fix that normalizes SIG_ERR to SIG_DFL at install time (via a small set_crash_handler() helper), so restoration is always valid. That's the only delta since you last looked.

@igaw

igaw commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Alright, thanks for looking into it. I try to stay away from signals, this is such a stupid and horrible API.

@igaw
igaw merged commit 436a787 into linux-nvme:master Jul 30, 2026
31 of 32 checks passed
@igaw

igaw commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Thanks a lot!

@jimmunn
jimmunn deleted the dump-command-metadata branch July 30, 2026 14:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants