From 6868cee66fdbc66a4be76224bfd3827649ef256b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:28:50 +0200 Subject: [PATCH 01/23] bootctl: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. The "help" verb was defined as visible but the old help() only printed the named verb groups, so it was never shown; it is now hidden explicitly to keep the output unchanged. --- src/bootctl/bootctl.c | 92 +++---------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 16 insertions(+), 77 deletions(-) diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index d28b0f3f3e..e397b2aab1 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -2,6 +2,7 @@ #include +#include "sd-json.h" #include "sd-varlink.h" #include "blockdev-util.h" @@ -28,18 +29,15 @@ #include "escape.h" #include "fd-util.h" #include "find-esp.h" -#include "format-table.h" #include "image-policy.h" #include "log.h" #include "loop-util.h" #include "main-func.h" #include "mount-util.h" -#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" -#include "pretty-print.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -109,6 +107,13 @@ STATIC_DESTRUCTOR_REGISTER(arg_entry_title, freep); STATIC_DESTRUCTOR_REGISTER(arg_entry_version, freep); STATIC_DESTRUCTOR_REGISTER(arg_extras, strv_freep); +COMMAND( + "bootctl\0", + "Control EFI firmware boot settings and manage boot loader.", + .man_pages = "bootctl.1\0", + .pager_flags = &arg_pager_flags, +); + static const char* const install_source_table[_INSTALL_SOURCE_MAX] = { [INSTALL_SOURCE_IMAGE] = "image", [INSTALL_SOURCE_HOST] = "host", @@ -290,78 +295,7 @@ GracefulMode arg_graceful(void) { return _arg_graceful; } -static int help(void) { - _cleanup_free_ char *link = NULL; - int r; - - pager_open(arg_pager_flags); - - r = terminal_urlify_man("bootctl", "1", &link); - if (r < 0) - return log_oom(); - - static const char *const verb_groups[] = { - "Generic EFI Firmware/Boot Loader Commands", - "Boot Loader Specification Commands", - "Boot Loader Interface Commands", - "systemd-boot Commands", - "Kernel Image Commands", - }; - - static const char *const option_groups[] = { - "Block Device Discovery Commands", - "Options", - }; - - Table *verb_tables[ELEMENTSOF(verb_groups)] = {}; - CLEANUP_ELEMENTS(verb_tables, table_unref_array_clear); - Table *option_tables[ELEMENTSOF(option_groups)] = {}; - CLEANUP_ELEMENTS(option_tables, table_unref_array_clear); - - for (size_t i = 0; i < ELEMENTSOF(verb_groups); i++) { - r = verbs_get_help_table_group(verb_groups[i], &verb_tables[i]); - if (r < 0) - return r; - } - - for (size_t i = 0; i < ELEMENTSOF(option_groups); i++) { - r = option_parser_get_help_table_group(option_groups[i], &option_tables[i]); - if (r < 0) - return r; - } - - (void) table_sync_column_widths(0, - verb_tables[0], verb_tables[1], verb_tables[2], - verb_tables[3], verb_tables[4], - option_tables[0], option_tables[1]); - - printf("%s [OPTIONS...] COMMAND ...\n" - "\n%sControl EFI firmware boot settings and manage boot loader.%s\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal()); - - for (size_t i = 0; i < ELEMENTSOF(verb_groups); i++) { - printf("\n%s%s:%s\n", ansi_underline(), verb_groups[i], ansi_normal()); - - r = table_print_or_warn(verb_tables[i]); - if (r < 0) - return r; - } - - for (size_t i = 0; i < ELEMENTSOF(option_groups); i++) { - printf("\n%s%s:%s\n", ansi_underline(), option_groups[i], ansi_normal()); - - r = table_print_or_warn(option_tables[i]); - if (r < 0) - return r; - } - - printf("\nSee the %s for details.\n", link); - return 0; -} - -VERB_COMMON_HELP(help); +VERB_COMMON_HELP_AUTO_HIDDEN("bootctl"); VERB_GROUP("Generic EFI Firmware/Boot Loader Commands"); @@ -479,7 +413,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_GROUP("Options"): {} OPTION_COMMON_HELP: - return help(); + return command_print_help("bootctl"); OPTION_COMMON_VERSION: return version(); @@ -765,7 +699,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { arg_tries_left = u; break; - }} + } + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(arg_json_format_flags); + } char **args = option_parser_get_args(&opts); diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index b53741aa25..1154e13776 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -11,6 +11,7 @@ export SYSTEMD_PAGER=cat # A smoke test for the introspection code INTROSPECTABLE=( + bootctl busctl coredumpctl homectl From 431e3f7defe5060540ab43a818d2246651fd21b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:29:11 +0200 Subject: [PATCH 02/23] cryptenroll: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. --- src/cryptenroll/cryptenroll.c | 58 +++++-------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 15 insertions(+), 44 deletions(-) diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 32d79077c3..1d087a9b74 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -3,6 +3,7 @@ #include #include "sd-device.h" +#include "sd-json.h" #include "sd-varlink.h" #include "alloc-util.h" @@ -24,15 +25,12 @@ #include "cryptsetup-util.h" #include "dlopen-note.h" #include "extract-word.h" -#include "format-table.h" -#include "help-util.h" #include "initrd-util.h" #include "libfido2-util.h" #include "limits-util.h" #include "log.h" #include "main-func.h" #include "memory-util.h" -#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -45,6 +43,7 @@ #include "time-util.h" #include "tpm2-pcr.h" #include "tpm2-util.h" +#include "verbs.h" #define ARGON2ID_BENCHMARK_DEFAULT_TARGET_MS 2000U #define ARGON2ID_BENCHMARK_MAX_ATTEMPTS 8U @@ -119,6 +118,14 @@ STATIC_DESTRUCTOR_REGISTER(arg_tpm2_pcrlock, freep); STATIC_DESTRUCTOR_REGISTER(arg_node, freep); STATIC_DESTRUCTOR_REGISTER(arg_wipe_slots, freep); +COMMAND( + "systemd-cryptenroll\0", + "Enroll a security token or authentication credential to a LUKS volume.", + .argspec = "[BLOCK-DEVICE]\0", + .man_pages = "systemd-cryptenroll.1\0", + .pager_flags = &arg_pager_flags, +); + static bool wipe_requested(void) { return arg_n_wipe_slots > 0 || arg_wipe_slots_scope != WIPE_EXPLICIT || @@ -345,46 +352,6 @@ static int parse_prompt_suppress(const char *arg) { } } -static int help(void) { - int r; - - static const char* const groups[] = { - NULL, - "Unlocking", - "Simple Enrollment", - "PKCS#11 Enrollment", - "FIDO2 Enrollment", - "TPM2 Enrollment", - }; - - Table *tables[ELEMENTSOF(groups)] = {}; - CLEANUP_ELEMENTS(tables, table_unref_array_clear); - - for (size_t i = 0; i < ELEMENTSOF(groups); i++) { - r = option_parser_get_help_table_group(groups[i], &tables[i]); - if (r < 0) - return r; - } - - (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], tables[3], tables[4], tables[5]); - - pager_open(arg_pager_flags); - - help_cmdline("[OPTIONS...] [BLOCK-DEVICE]"); - help_abstract("Enroll a security token or authentication credential to a LUKS volume."); - - for (size_t i = 0; i < ELEMENTSOF(groups); i++) { - help_section(groups[i] ?: "Options"); - - r = table_print_or_warn(tables[i]); - if (r < 0) - return r; - } - - help_man_page_reference("systemd-cryptenroll", "1"); - return 0; -} - static int parse_argv(int argc, char *argv[]) { bool auto_public_key_pcr_mask = true, auto_pcrlock = true; @@ -398,7 +365,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { OPTION_COMMON_HELP: - return help(); + return command_print_help("systemd-cryptenroll"); OPTION_COMMON_VERSION: return version(); @@ -786,6 +753,9 @@ static int parse_argv(int argc, char *argv[]) { if (arg_tpm2_argon2id_iter_time == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Target time must be non-zero."); break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } if (option_parser_get_n_args(&opts) > 1) diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index 1154e13776..14c8a4b5f9 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -36,6 +36,7 @@ INTROSPECTABLE=( systemd-cgtop systemd-clonesetup systemd-creds + systemd-cryptenroll systemd-cryptsetup systemd-delta systemd-detect-virt From f17f7cc35e2147729ed3fef2e5641eb448ff6240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:29:34 +0200 Subject: [PATCH 03/23] imdsd: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. --- src/imds/imdsd.c | 47 +++++-------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 13 insertions(+), 35 deletions(-) diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c index aecc2feac7..4324864aa5 100644 --- a/src/imds/imdsd.c +++ b/src/imds/imdsd.c @@ -29,10 +29,8 @@ #include "fd-util.h" #include "fileio.h" #include "format-ifname.h" -#include "format-table.h" #include "hash-funcs.h" #include "hashmap.h" -#include "help-util.h" #include "imds-util.h" #include "in-addr-util.h" #include "io-util.h" @@ -41,7 +39,6 @@ #include "log.h" #include "main-func.h" #include "netlink-util.h" -#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" @@ -54,6 +51,7 @@ #include "utf8.h" #include "varlink-io.systemd.InstanceMetadata.h" #include "varlink-util.h" +#include "verbs.h" #include "web-util.h" #include "xattr-util.h" @@ -141,6 +139,13 @@ STATIC_DESTRUCTOR_REGISTER(arg_token_header_name, freep); STATIC_DESTRUCTOR_REGISTER(arg_extra_header, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_well_known_key, imds_well_known_key_free); +COMMAND( + "systemd-imdsd\0", + "Low-level IMDS data acquisition.", + .argspec = "KEY\0", + .man_pages = "systemd-imdsd@.service.8\0", +); + typedef struct Context Context; typedef struct ChildData { @@ -2192,37 +2197,6 @@ static int vl_server(void) { return 0; } -static int help(void) { - _cleanup_(table_unrefp) Table *options = NULL, *endpoint_options = NULL; - int r; - - r = option_parser_get_help_table(&options); - if (r < 0) - return r; - - r = option_parser_get_help_table_group("Manual Endpoint Configuration", &endpoint_options); - if (r < 0) - return r; - - (void) table_sync_column_widths(0, options, endpoint_options); - - help_cmdline("[OPTIONS...] KEY"); - help_abstract("Low-level IMDS data acquisition."); - - help_section("Options"); - r = table_print_or_warn(options); - if (r < 0) - return r; - - help_section("Manual Endpoint Configuration"); - r = table_print_or_warn(endpoint_options); - if (r < 0) - return r; - - help_man_page_reference("systemd-imdsd@.service", "8"); - return 0; -} - static bool http_header_name_valid(const char *a) { return a && ascii_is_valid(a) && !string_has_cc(a, /* ok= */ NULL) && !strchr(a, ':'); } @@ -2239,7 +2213,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { OPTION_COMMON_HELP: - return help(); + return command_print_help("systemd-imdsd"); OPTION_COMMON_VERSION: return version(); @@ -2484,6 +2458,9 @@ static int parse_argv(int argc, char *argv[]) { return r; break; } + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } if (arg_vendor || arg_token_url || arg_refresh_header_name || arg_data_url || arg_data_url_suffix || arg_token_header_name || arg_extra_header) diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index 14c8a4b5f9..c8084a43b0 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -49,6 +49,7 @@ INTROSPECTABLE=( systemd-hwdb systemd-id128 systemd-imds + systemd-imdsd systemd-inhibit systemd-journal-gatewayd systemd-journal-remote From 8aee2501f84fcd01adf4bba2b6497a47c132c707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:30:05 +0200 Subject: [PATCH 04/23] journalctl: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. The description refers to the "journalctl" option namespace; the varlink-mode namespace is not described. --- src/journal/journalctl.c | 61 ++++++------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 18 insertions(+), 44 deletions(-) diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index ce9470cf76..e37ee113fd 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -3,15 +3,14 @@ #include #include "sd-journal.h" +#include "sd-json.h" #include "sd-varlink.h" #include "build.h" #include "dissect-image.h" #include "dlopen-note.h" #include "extract-word.h" -#include "format-table.h" #include "glob-util.h" -#include "help-util.h" #include "id128-print.h" #include "image-policy.h" #include "journalctl.h" @@ -28,7 +27,6 @@ #include "main-func.h" #include "mount-util.h" #include "mountpoint-util.h" -#include "options.h" #include "output-mode.h" #include "pager.h" #include "parse-argument.h" @@ -45,6 +43,7 @@ #include "varlink-io.systemd.JournalAccess.h" #include "varlink-io.systemd.Metrics.h" #include "varlink-util.h" +#include "verbs.h" #define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE) @@ -143,6 +142,17 @@ STATIC_DESTRUCTOR_REGISTER(arg_pattern, freep); STATIC_DESTRUCTOR_REGISTER(arg_compiled_pattern, pcre2_code_freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); +COMMAND( + "journalctl\0", + "Query the journal.", + .argspec = "[MATCH…]\0", + .man_pages = "journalctl.1\0", + .option_namespace = "journalctl", + .pager_flags = &arg_pager_flags, +); + +// TODO: also expose journalctl-varlink interface through COMMAND + static int parse_id_descriptor(const char *x, sd_id128_t *ret_id, int *ret_offset) { sd_id128_t id = SD_ID128_NULL; int off = 0, r; @@ -271,46 +281,6 @@ static int help_facilities(void) { return 0; } -static int help(void) { - static const char *const groups[] = { - "Source Options", - "Filtering Options", - "Output Control Options", - "Pager Control Options", - "Forward Secure Sealing (FSS) Options", - "Commands", - }; - - Table *tables[ELEMENTSOF(groups)] = {}; - CLEANUP_ELEMENTS(tables, table_unref_array_clear); - int r; - - pager_open(arg_pager_flags); - - for (size_t i = 0; i < ELEMENTSOF(groups); i++) { - r = option_parser_get_help_table_full("journalctl", groups[i], &tables[i]); - if (r < 0) - return r; - } - - assert_cc(ELEMENTSOF(tables) == 6); - (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], - tables[3], tables[4], tables[5]); - - help_cmdline("[OPTIONS…] [MATCHES…]"); - help_abstract("Query the journal."); - - for (size_t i = 0; i < ELEMENTSOF(groups); i++) { - help_section(groups[i]); - r = table_print_or_warn(tables[i]); - if (r < 0) - return r; - } - - help_man_page_reference("journalctl", "1"); - return 0; -} - static int vl_server(void) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; int r; @@ -823,7 +793,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION_GROUP("Commands"): {} OPTION_COMMON_HELP: - return help(); + return command_print_help("journalctl"); OPTION_COMMON_VERSION: return version(); @@ -932,6 +902,9 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION_LONG("new-id128", NULL, /* help= */ NULL): arg_action = ACTION_NEW_ID128; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(arg_json_format_flags); } char **args = option_parser_get_args(&opts); diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index c8084a43b0..55f0ff6815 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -17,6 +17,7 @@ INTROSPECTABLE=( homectl hostnamectl importctl + journalctl localectl networkctl oomctl From 1c7253ca8f005c330902ccb4b52f8c8ab9d0ec4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:30:22 +0200 Subject: [PATCH 05/23] loginctl: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. --- src/login/loginctl.c | 63 ++++--------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 14 insertions(+), 50 deletions(-) diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 7d63e6b03b..f7e1b88e10 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -5,6 +5,7 @@ #include "sd-bus.h" #include "sd-journal.h" +#include "sd-json.h" #include "alloc-util.h" #include "ansi-color.h" @@ -20,11 +21,9 @@ #include "dlopen-note.h" #include "format-table.h" #include "format-util.h" -#include "help-util.h" #include "log.h" #include "logs-show.h" #include "main-func.h" -#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -56,6 +55,13 @@ static OutputMode arg_output = OUTPUT_SHORT; STATIC_DESTRUCTOR_REGISTER(arg_property, strv_freep); +COMMAND( + "loginctl\0", + "Send control commands to or query the login manager.", + .man_pages = "loginctl.1\0", + .pager_flags = &arg_pager_flags, +); + typedef struct SessionStatusInfo { const char *id; uid_t uid; @@ -1493,53 +1499,7 @@ static int verb_terminate_seat(int argc, char *argv[], uintptr_t _data, void *us return 0; } -static int help(void) { - static const char *const groups[] = { - "Session Commands", - "User Commands", - "Seat Commands", - }; - - Table *vtables[ELEMENTSOF(groups)] = {}; - CLEANUP_ELEMENTS(vtables, table_unref_array_clear); - _cleanup_(table_unrefp) Table *options = NULL; - int r; - - pager_open(arg_pager_flags); - - for (size_t i = 0; i < ELEMENTSOF(groups); i++) { - r = verbs_get_help_table_group(groups[i], &vtables[i]); - if (r < 0) - return r; - } - - r = option_parser_get_help_table(&options); - if (r < 0) - return r; - - assert_cc(ELEMENTSOF(vtables) == 3); - (void) table_sync_column_widths(0, vtables[0], vtables[1], vtables[2], options); - - help_cmdline("[OPTIONS…] COMMAND …"); - help_abstract("Send control commands to or query the login manager."); - - for (size_t i = 0; i < ELEMENTSOF(groups); i++) { - help_section(groups[i]); - r = table_print_or_warn(vtables[i]); - if (r < 0) - return r; - } - - help_section("Options"); - r = table_print_or_warn(options); - if (r < 0) - return r; - - help_man_page_reference("loginctl", "1"); - return 0; -} - -VERB_COMMON_HELP_HIDDEN(help); +VERB_COMMON_HELP_AUTO_HIDDEN("loginctl"); static int parse_argv(int argc, char *argv[], char ***remaining_args) { int r; @@ -1554,7 +1514,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { switch (c) { OPTION_COMMON_HELP: - return help(); + return command_print_help("loginctl"); OPTION_COMMON_VERSION: return version(); @@ -1652,6 +1612,9 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(arg_json_format_flags); } *remaining_args = option_parser_get_args(&opts); diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index 55f0ff6815..89f9eccb12 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -19,6 +19,7 @@ INTROSPECTABLE=( importctl journalctl localectl + loginctl networkctl oomctl portablectl From facaf8ba45cd772b7bd0462c5b98466e4220ab99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:30:29 +0200 Subject: [PATCH 06/23] machinectl: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. The abstract is now wrapped automatically. --- src/machine/machinectl.c | 68 ++++--------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 14 insertions(+), 55 deletions(-) diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 19ad385733..8275eb6a03 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -8,6 +8,7 @@ #include "sd-bus.h" #include "sd-event.h" #include "sd-journal.h" +#include "sd-json.h" #include "sd-varlink.h" #include "alloc-util.h" @@ -33,7 +34,6 @@ #include "format-ifname.h" #include "format-table.h" #include "format-util.h" -#include "help-util.h" #include "hostname-util.h" #include "import-util.h" #include "in-addr-util.h" @@ -45,7 +45,6 @@ #include "machine-util.h" #include "main-func.h" #include "nulstr-util.h" -#include "options.h" #include "osc-context.h" #include "output-mode.h" #include "pager.h" @@ -117,6 +116,13 @@ static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; STATIC_DESTRUCTOR_REGISTER(arg_property, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_setenv, strv_freep); +COMMAND( + "machinectl\0", + "Send control commands to or query the virtual machine and container registration manager.", + .man_pages = "machinectl.1\0", + .pager_flags = &arg_pager_flags, +); + static OutputFlags get_output_flags(void) { return FLAGS_SET(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY) * OUTPUT_SHOW_ALL | @@ -2370,58 +2376,7 @@ static int chainload_importctl(char **args) { return log_error_errno(r, "Failed to invoke 'importctl': %m"); } -static int help(void) { - static const char* const vgroups[] = { - "Machine Commands", - "Image Commands", - }; - - Table *vtables[ELEMENTSOF(vgroups)] = {}; - CLEANUP_ELEMENTS(vtables, table_unref_array_clear); - _cleanup_(table_unrefp) Table *options = NULL; - int r; - - for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { - r = verbs_get_help_table_group(vgroups[i], &vtables[i]); - if (r < 0) - return r; - } - - r = option_parser_get_help_table(&options); - if (r < 0) - return r; - - assert_cc(ELEMENTSOF(vtables) == 2); - (void) table_sync_column_widths(0, options, vtables[0], vtables[1]); - - pager_open(arg_pager_flags); - - help_cmdline("[OPTIONS…] COMMAND …"); - - printf("\n%1$s%2$sSend control commands to or query the virtual machine and container%3$s\n" - "%1$s%2$sregistration manager.%3$s\n", - ansi_highlight(), - ansi_add_italics(), - ansi_normal()); - - for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { - help_section(vgroups[i]); - r = table_print_or_warn(vtables[i]); - if (r < 0) - return r; - } - - help_section("Options"); - r = table_print_or_warn(options); - if (r < 0) - return r; - - help_man_page_reference("machinectl", "1"); - - return 0; -} - -VERB_COMMON_HELP_HIDDEN(help); +VERB_COMMON_HELP_AUTO_HIDDEN("machinectl"); static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); @@ -2454,7 +2409,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_COMMON_HELP: - return help(); + return command_print_help("machinectl"); OPTION_COMMON_VERSION: return version(); @@ -2620,6 +2575,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION('q', "quiet", NULL, "Suppress output"): arg_quiet = true; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } /* We gathered some positional args in 'args' ourselves. Append the remaining ones. */ diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index 89f9eccb12..ccb1cd4cb3 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -20,6 +20,7 @@ INTROSPECTABLE=( journalctl localectl loginctl + machinectl networkctl oomctl portablectl From 24e24adfe1c2caf476d882be16f6894690eaa45d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:30:51 +0200 Subject: [PATCH 07/23] machine-id-setup: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. An explicit "Commands" option group is added so that the section heading stays the same. --- src/machine-id-setup/machine-id-setup-main.c | 61 +++++-------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 15 insertions(+), 47 deletions(-) diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c index 1eb372be64..d932d00b0d 100644 --- a/src/machine-id-setup/machine-id-setup-main.c +++ b/src/machine-id-setup/machine-id-setup-main.c @@ -2,11 +2,12 @@ #include +#include "sd-json.h" + #include "alloc-util.h" #include "build.h" #include "dissect-image.h" #include "dlopen-note.h" -#include "format-table.h" #include "id128-util.h" #include "image-policy.h" #include "log.h" @@ -14,9 +15,8 @@ #include "machine-id-setup.h" #include "main-func.h" #include "mount-util.h" -#include "options.h" #include "parse-argument.h" -#include "pretty-print.h" +#include "verbs.h" static char *arg_root = NULL; static char *arg_image = NULL; @@ -28,49 +28,11 @@ STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); -static int help(void) { - _cleanup_free_ char *link = NULL; - _cleanup_(table_unrefp) Table *commands = NULL, *options = NULL; - int r; - - r = terminal_urlify_man("systemd-machine-id-setup", "1", &link); - if (r < 0) - return log_oom(); - - r = option_parser_get_help_table(&commands); - if (r < 0) - return r; - - r = option_parser_get_help_table_group("Options", &options); - if (r < 0) - return r; - - (void) table_sync_column_widths(0, commands, options); - - printf("%s [OPTIONS...]\n\n" - "%sInitialize /etc/machine-id from a random source.%s\n" - "\n%sCommands:%s\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - ansi_underline(), - ansi_normal()); - - r = table_print_or_warn(commands); - if (r < 0) - return r; - - printf("\n%sOptions:%s\n", - ansi_underline(), - ansi_normal()); - - r = table_print_or_warn(options); - if (r < 0) - return r; - - printf("\nSee the %s for details.\n", link); - return 0; -} +COMMAND( + "systemd-machine-id-setup\0", + "Initialize /etc/machine-id from a random source.", + .man_pages = "systemd-machine-id-setup.1\0", +); static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); @@ -82,8 +44,10 @@ static int parse_argv(int argc, char *argv[]) { FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { + OPTION_GROUP("Commands"): {} + OPTION_COMMON_HELP: - return help(); + return command_print_help("systemd-machine-id-setup"); OPTION_COMMON_VERSION: return version(); @@ -115,6 +79,9 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("print", NULL, "Print used machine ID"): arg_print = true; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } if (option_parser_get_n_args(&opts) > 0) diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index ccb1cd4cb3..9f2ff0e46b 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -58,6 +58,7 @@ INTROSPECTABLE=( systemd-journal-remote systemd-journal-upload systemd-keyutil + systemd-machine-id-setup systemd-modules-load systemd-mute-console systemd-network-generator From 20cc6d31880df66bb119af121e2a91bb40d8ebdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:31:05 +0200 Subject: [PATCH 08/23] measure: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. --help now goes through the pager, like other commands with --no-pager. --- src/measure/measure-tool.c | 66 ++++--------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 13 insertions(+), 54 deletions(-) diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index bcd20743ed..02bba3866c 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -5,6 +5,7 @@ #include "sd-json.h" #include "alloc-util.h" +#include "ansi-color.h" #include "ask-password-api.h" #include "build.h" #include "crypto-util.h" @@ -13,15 +14,12 @@ #include "efivars.h" #include "fd-util.h" #include "fileio.h" -#include "format-table.h" #include "hexdecoct.h" #include "log.h" #include "main-func.h" -#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" -#include "pretty-print.h" #include "string-util.h" #include "strv.h" #include "tpm2-pcr.h" @@ -67,57 +65,14 @@ static void free_sections(char*(*sections)[_UNIFIED_SECTION_MAX]) { STATIC_DESTRUCTOR_REGISTER(arg_sections, free_sections); -static int help(void) { - _cleanup_free_ char *link = NULL; - _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL, *options2 = NULL; - int r; +COMMAND( + "systemd-measure\0", + "Pre-calculate and sign PCR hash for a unified kernel image (UKI).", + .man_pages = "systemd-measure.1\0", + .pager_flags = &arg_pager_flags, +); - r = terminal_urlify_man("systemd-measure", "1", &link); - if (r < 0) - return log_oom(); - - r = verbs_get_help_table(&verbs); - if (r < 0) - return r; - - r = option_parser_get_help_table(&options); - if (r < 0) - return r; - - r = option_parser_get_help_table_group("UKI PE Section Options", &options2); - if (r < 0) - return r; - - (void) table_sync_column_widths(0, verbs, options, options2); - - printf("%s [OPTIONS...] COMMAND ...\n" - "\n%sPre-calculate and sign PCR hash for a unified kernel image (UKI).%s\n" - "\n%sCommands:%s\n", - program_invocation_short_name, - ansi_highlight(), ansi_normal(), - ansi_underline(), ansi_normal()); - - r = table_print_or_warn(verbs); - if (r < 0) - return r; - - printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal()); - - r = table_print_or_warn(options); - if (r < 0) - return r; - - printf("\n%sUKI PE Section Options:%s\n", ansi_underline(), ansi_normal()); - - r = table_print_or_warn(options2); - if (r < 0) - return r; - - printf("\nSee the %s for details.\n", link); - return 0; -} - -VERB_COMMON_HELP_HIDDEN(help); +VERB_COMMON_HELP_AUTO_HIDDEN("systemd-measure"); static char *normalize_phase(const char *s) { _cleanup_strv_free_ char **l = NULL; @@ -146,7 +101,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { switch (c) { OPTION_COMMON_HELP: - return help(); + return command_print_help("systemd-measure"); OPTION_COMMON_VERSION: return version(); @@ -315,6 +270,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { if (r < 0) return r; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(arg_json_format_flags); } if (arg_public_key && arg_certificate) diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index 9f2ff0e46b..5236631860 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -59,6 +59,7 @@ INTROSPECTABLE=( systemd-journal-upload systemd-keyutil systemd-machine-id-setup + systemd-measure systemd-modules-load systemd-mute-console systemd-network-generator From a9f42ed01515026fecf12233e59076504e532b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:31:38 +0200 Subject: [PATCH 09/23] mstack: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. The "Commands" option group is moved before the other options and an explicit "Options" group is added, so that the sections are printed in the same order as before. --help now goes through the pager, like other commands with --no-pager. The mount.mstack helper interface is described by a second COMMAND(), so that it is exposed by --introspect-cli too. --- src/mstack/mstack-tool.c | 110 ++++++++---------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 17 +-- 2 files changed, 60 insertions(+), 67 deletions(-) diff --git a/src/mstack/mstack-tool.c b/src/mstack/mstack-tool.c index 45c699d574..ea2aa404c3 100644 --- a/src/mstack/mstack-tool.c +++ b/src/mstack/mstack-tool.c @@ -3,6 +3,8 @@ #include #include +#include "sd-json.h" + #include "argv-util.h" #include "build.h" #include "chase.h" @@ -11,15 +13,14 @@ #include "extract-word.h" #include "fd-util.h" #include "format-table.h" -#include "help-util.h" #include "image-policy.h" #include "main-func.h" #include "mount-util.h" #include "mountpoint-util.h" #include "mstack.h" -#include "options.h" #include "parse-argument.h" #include "string-util.h" +#include "verbs.h" static enum { ACTION_INSPECT, @@ -41,38 +42,17 @@ STATIC_DESTRUCTOR_REGISTER(arg_where, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_image_filter, image_filter_freep); -static int help(void) { - _cleanup_(table_unrefp) Table *options = NULL, *commands = NULL; - int r; - - r = option_parser_get_help_table_ns("systemd-mstack", &options); - if (r < 0) - return r; - - r = option_parser_get_help_table_full("systemd-mstack", "Commands", &commands); - if (r < 0) - return r; - - (void) table_sync_column_widths(0, options, commands); - - help_cmdline("[OPTIONS...] WHAT"); - help_cmdline("[OPTIONS...] --mount WHAT WHERE"); - help_cmdline("[OPTIONS...] --umount WHERE"); - help_abstract("Inspect or apply mount stack."); - - help_section("Commands"); - r = table_print_or_warn(commands); - if (r < 0) - return r; - - help_section("Options"); - r = table_print_or_warn(options); - if (r < 0) - return r; - - help_man_page_reference("systemd-mstack", "1"); - return 0; -} +COMMAND( + "systemd-mstack\0", + "Inspect or apply mount stack.", + .argspec = + "WHAT\0" + "--mount WHAT WHERE\0" + "--umount WHERE\0", + .man_pages = "systemd-mstack.1\0", + .option_namespace = "systemd-mstack", + .pager_flags = &arg_pager_flags, +); static int parse_argv(int argc, char *argv[]) { int r; @@ -87,6 +67,34 @@ static int parse_argv(int argc, char *argv[]) { OPTION_NAMESPACE("systemd-mstack"): {} + OPTION_GROUP("Commands"): {} + + OPTION_COMMON_HELP: + return command_print_help("systemd-mstack"); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('m', "mount", NULL, "Mount the mstack to the specified directory"): + arg_action = ACTION_MOUNT; + break; + + OPTION_SHORT('M', NULL, "Shortcut for --mount --mkdir"): + arg_action = ACTION_MOUNT; + arg_mstack_flags |= MSTACK_MKDIR; + break; + + OPTION('u', "umount", NULL, "Unmount the image from the specified directory"): + arg_action = ACTION_UMOUNT; + break; + + OPTION_SHORT('U', NULL, "Shortcut for --umount --rmdir"): + arg_action = ACTION_UMOUNT; + arg_rmdir = true; + break; + + OPTION_GROUP("Options"): {} + OPTION('r', "read-only", NULL, "Mount read-only"): arg_mstack_flags |= MSTACK_RDONLY; break; @@ -130,31 +138,8 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - OPTION_GROUP("Commands"): {} - - OPTION_COMMON_HELP: - return help(); - - OPTION_COMMON_VERSION: - return version(); - - OPTION('m', "mount", NULL, "Mount the mstack to the specified directory"): - arg_action = ACTION_MOUNT; - break; - - OPTION_SHORT('M', NULL, "Shortcut for --mount --mkdir"): - arg_action = ACTION_MOUNT; - arg_mstack_flags |= MSTACK_MKDIR; - break; - - OPTION('u', "umount", NULL, "Unmount the image from the specified directory"): - arg_action = ACTION_UMOUNT; - break; - - OPTION_SHORT('U', NULL, "Shortcut for --umount --rmdir"): - arg_action = ACTION_UMOUNT; - arg_rmdir = true; - break; + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(arg_json_format_flags); } char **args = option_parser_get_args(&opts); @@ -203,6 +188,13 @@ static int parse_argv(int argc, char *argv[]) { return 1; } +COMMAND( + "mount.mstack\0", + "External helper for mount.8 to mount mount stacks.", + .man_pages = "systemd-mstack.1\0", + .option_namespace = "mount.mstack", +); + static int parse_argv_as_mount_helper(int argc, char *argv[]) { const char *mount_options = NULL; bool fake = false; diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index 5236631860..e1b5d023c3 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -61,6 +61,7 @@ INTROSPECTABLE=( systemd-machine-id-setup systemd-measure systemd-modules-load + systemd-mstack systemd-mute-console systemd-network-generator systemd-networkd-wait-online @@ -111,20 +112,20 @@ for i in "${INTROSPECTABLE[@]}"; do fi done -# systemd-hwdb defines verbs, check that they are described +# check verbs and multicall binaries if command -v systemd-hwdb >/dev/null; then systemd-hwdb --introspect-cli | jq -e \ '.commands[0].verbs | map(.names[0]) | sort == ["help", "query", "update"]' fi -# systemd-id128 defines many verbs, check that some are described -systemd-id128 --introspect-cli | jq -e \ - '.commands[0].verbs | map(.names[0]) | contains(["new", "machine-id", "show", "help"])' +resolvectl --introspect-cli | jq -e \ + '[.commands[].names[0]] | sort == ["resolvconf", "resolvectl", "systemd-resolve"]' -# systemd-clonesetup defines verbs, check that they are described systemd-clonesetup --introspect-cli | jq -e \ '.commands[0].verbs | map(.names[0]) | sort == ["add", "remove"]' -# resolvectl is a multicall binary, check that both commands are described -resolvectl --introspect-cli | jq -e \ - '[.commands[].names[0]] | sort == ["resolvconf", "resolvectl", "systemd-resolve"]' +systemd-id128 --introspect-cli | jq -e \ + '.commands[0].verbs | map(.names[0]) | contains(["new", "machine-id", "show", "help"])' + +systemd-mstack --introspect-cli | jq -e \ + '[.commands[].names[0]] | sort == ["mount.mstack", "systemd-mstack"]' From bf08fc692aeaf992e0bac4a66a5c634e5aa6f2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:31:57 +0200 Subject: [PATCH 10/23] nspawn: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. The generic options at the top now get an "Options" heading like the other groups. --- src/nspawn/nspawn.c | 80 ++++--------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 15 insertions(+), 66 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index b2eb41d5ef..3027c90e47 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -16,6 +16,7 @@ #include "sd-daemon.h" #include "sd-event.h" #include "sd-id128.h" +#include "sd-json.h" #include "sd-netlink.h" #include "sd-path.h" #include "sd-varlink.h" @@ -54,7 +55,6 @@ #include "fdset.h" #include "fileio.h" #include "fork-notify.h" -#include "format-table.h" #include "format-util.h" #include "fs-util.h" #include "gpt.h" @@ -93,7 +93,6 @@ #include "nspawn-setuid.h" #include "nspawn-stub-pid1.h" #include "nsresource.h" -#include "options.h" #include "os-util.h" #include "osc-context.h" #include "pager.h" @@ -135,6 +134,7 @@ #include "unit-name.h" #include "user-record.h" #include "user-util.h" +#include "verbs.h" #include "vpick.h" /* The notify socket inside the container it can use to talk to nspawn using the sd_notify(3) protocol */ @@ -318,6 +318,14 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_background, freep); STATIC_DESTRUCTOR_REGISTER(arg_forward_journal, freep); +COMMAND( + "systemd-nspawn\0", + "Spawn a command or OS in a lightweight container.", + .argspec = "[PATH] [ARGUMENTS…]\0", + .man_pages = "systemd-nspawn.1\0", + .pager_flags = &arg_pager_flags, +); + static int parse_private_users( const char *s, UserNamespaceMode *ret_userns_mode, @@ -383,69 +391,6 @@ static int parse_private_users( return 0; } -static int help(void) { - _cleanup_free_ char *link = NULL; - int r; - - pager_open(arg_pager_flags); - - r = terminal_urlify_man("systemd-nspawn", "1", &link); - if (r < 0) - return log_oom(); - - static const char* const groups[] = { - NULL, - "Image", - "Execution", - "System Identity", - "Properties", - "User Namespacing", - "Networking", - "Security", - "Resources", - "Integration", - "Mounts", - "Input/Output", - "Credentials", - "Other", - }; - - Table* tables[ELEMENTSOF(groups)] = {}; - CLEANUP_ELEMENTS(tables, table_unref_array_clear); - - for (size_t i = 0; i < ELEMENTSOF(groups); i++) { - r = option_parser_get_help_table_group(groups[i], &tables[i]); - if (r < 0) - return r; - } - - (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], tables[3], - tables[4], tables[5], tables[6], tables[7], - tables[8], tables[9], tables[10], tables[11], - tables[12], tables[13]); - - printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" - "%sSpawn a command or OS in a lightweight container.%s\n\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal()); - - r = table_print_or_warn(tables[0]); - if (r < 0) - return r; - - for (size_t i = 1; i < ELEMENTSOF(groups); i++) { - printf("\n%s%s:%s\n", ansi_underline(), groups[i], ansi_normal()); - - r = table_print_or_warn(tables[i]); - if (r < 0) - return r; - } - - printf("\nSee the %s for details.\n", link); - return 0; -} - static int custom_mount_check_all(void) { FOREACH_ARRAY(m, arg_custom_mounts, arg_n_custom_mounts) if (path_equal(m->destination, "/") && arg_userns_mode != USER_NAMESPACE_NO) { @@ -614,7 +559,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { OPTION_COMMON_HELP: - return help(); + return command_print_help("systemd-nspawn"); OPTION_COMMON_VERSION: return version(); @@ -1445,6 +1390,9 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("system", NULL, "Run in the system service manager scope"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } } diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index e1b5d023c3..6dd49b596e 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -66,6 +66,7 @@ INTROSPECTABLE=( systemd-network-generator systemd-networkd-wait-online systemd-notify + systemd-nspawn systemd-oomd systemd-path systemd-pcrextend From 19a24f926d95bf73369688519cd26d729434cc88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:32:17 +0200 Subject: [PATCH 11/23] pcrlock: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. --help now goes through the pager, like other commands with --no-pager. --- src/pcrlock/pcrlock.c | 56 ++++--------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 13 insertions(+), 44 deletions(-) diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index d290538aad..f0c92ba197 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -35,7 +35,6 @@ #include "fs-util.h" #include "glyph-util.h" #include "gpt.h" -#include "help-util.h" #include "hexdecoct.h" #include "initrd-util.h" #include "json-util.h" @@ -43,7 +42,6 @@ #include "list.h" #include "main-func.h" #include "mkdir.h" -#include "options.h" #include "ordered-set.h" #include "parse-argument.h" #include "parse-util.h" @@ -105,6 +103,13 @@ STATIC_DESTRUCTOR_REGISTER(arg_location_end, freep); STATIC_DESTRUCTOR_REGISTER(arg_policy_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_entry_token, freep); +COMMAND( + "systemd-pcrlock\0", + "Manage a TPM2 PCR lock.", + .man_pages = "systemd-pcrlock.8\0", + .pager_flags = &arg_pager_flags, +); + #define PCRLOCK_SECUREBOOT_POLICY_PATH "/var/lib/pcrlock.d/240-secureboot-policy.pcrlock.d/generated.pcrlock" #define PCRLOCK_FIRMWARE_CODE_EARLY_PATH "/var/lib/pcrlock.d/250-firmware-code-early.pcrlock.d/generated.pcrlock" #define PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH "/var/lib/pcrlock.d/250-firmware-config-early.pcrlock.d/generated.pcrlock" @@ -5317,50 +5322,10 @@ static int verb_lock_raw(int argc, char *argv[], uintptr_t _data, void *userdata return write_pcrlock(records, NULL); } -static int help(void) { - _cleanup_(table_unrefp) Table *commands = NULL, *protections = NULL, *options = NULL; - int r; - - r = verbs_get_help_table(&commands); - if (r < 0) - return r; - - r = verbs_get_help_table_group("Protections", &protections); - if (r < 0) - return r; - - r = option_parser_get_help_table(&options); - if (r < 0) - return r; - - (void) table_sync_column_widths(0, commands, protections, options); - - help_cmdline("[OPTIONS...] COMMAND ..."); - help_abstract("Manage a TPM2 PCR lock."); - - help_section("Commands"); - r = table_print_or_warn(commands); - if (r < 0) - return r; - - help_section("Protections"); - r = table_print_or_warn(protections); - if (r < 0) - return r; - - help_section("Options"); - r = table_print_or_warn(options); - if (r < 0) - return r; - - help_man_page_reference("systemd-pcrlock", "8"); - return 0; -} - VERB_NOARG(verb_unlock_simple, "unlock-raw", "Remove .pcrlock file for raw data"); -VERB_COMMON_HELP_HIDDEN(help); +VERB_COMMON_HELP_AUTO_HIDDEN("systemd-pcrlock"); static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); @@ -5375,7 +5340,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { switch (c) { OPTION_COMMON_HELP: - return help(); + return command_print_help("systemd-pcrlock"); OPTION_COMMON_VERSION: return version(); @@ -5538,6 +5503,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION('q', "quiet", NULL, "Suppress unnecessary output"): arg_quiet = true; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(arg_json_format_flags); } if (auto_location) { diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index 6dd49b596e..464e0d3627 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -70,6 +70,7 @@ INTROSPECTABLE=( systemd-oomd systemd-path systemd-pcrextend + systemd-pcrlock systemd-pty-forward systemd-random-seed systemd-report From be5ae9fb4072e58546cadd2f333580ec61144c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:32:23 +0200 Subject: [PATCH 12/23] repart: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. --help now goes through the pager, like other commands with --no-pager. --- src/repart/repart.c | 71 ++++--------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 14 insertions(+), 58 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 2c454a54c7..cb48e457dd 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -61,7 +61,6 @@ #include "mount-util.h" #include "mountpoint-util.h" #include "nulstr-util.h" -#include "options.h" #include "parse-argument.h" #include "parse-helpers.h" #include "parse-util.h" @@ -88,6 +87,7 @@ #include "utf8.h" #include "varlink-io.systemd.Repart.h" #include "varlink-util.h" +#include "verbs.h" #include "xattr-util.h" /* If not configured otherwise use a minimal partition size of 10M */ @@ -248,6 +248,14 @@ STATIC_DESTRUCTOR_REGISTER(arg_eltorito_system, freep); STATIC_DESTRUCTOR_REGISTER(arg_eltorito_volume, freep); STATIC_DESTRUCTOR_REGISTER(arg_eltorito_publisher, freep); +COMMAND( + "systemd-repart\0", + "Grow and add partitions to a partition table, and generate disk images (DDIs).", + .argspec = "[DEVICE]\0", + .man_pages = "systemd-repart.8\0", + .pager_flags = &arg_pager_flags, +); + typedef enum ProgressPhase { PROGRESS_LOADING_DEFINITIONS, PROGRESS_LOADING_TABLE, @@ -10167,62 +10175,6 @@ static int parse_join_signature(const char *p, Set **verity_settings_map) { return 0; } -static int help(void) { - _cleanup_free_ char *link = NULL; - int r; - - r = terminal_urlify_man("systemd-repart", "8", &link); - if (r < 0) - return log_oom(); - - static const char *const option_groups[] = { - "Options", - "Operation", - "Output", - "Factory Reset", - "Configuration & Image Control", - "Verity", - "Encryption", - "Partition Control", - "Copying", - "DDI Profile", - "Auxiliary Resource Generation", - "El Torito boot catalog", - }; - - Table *option_tables[ELEMENTSOF(option_groups)] = {}; - CLEANUP_ELEMENTS(option_tables, table_unref_array_clear); - - for (size_t i = 0; i < ELEMENTSOF(option_groups); i++) { - r = option_parser_get_help_table_group(option_groups[i], &option_tables[i]); - if (r < 0) - return r; - } - - (void) table_sync_column_widths(0, - option_tables[0], option_tables[1], option_tables[2], - option_tables[3], option_tables[4], option_tables[5], - option_tables[6], option_tables[7], option_tables[8], - option_tables[9], option_tables[10], option_tables[11]); - - printf("%s [OPTIONS...] [DEVICE]\n" - "\n%sGrow and add partitions to a partition table, and generate disk images (DDIs).%s\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal()); - - for (size_t i = 0; i < ELEMENTSOF(option_groups); i++) { - printf("\n%s%s:%s\n", ansi_underline(), option_groups[i], ansi_normal()); - - r = table_print_or_warn(option_tables[i]); - if (r < 0) - return r; - } - - printf("\nSee the %s for details.\n", link); - return 0; -} - static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); @@ -10237,7 +10189,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_GROUP("Options"): {} OPTION_COMMON_HELP: - return help(); + return command_print_help("systemd-repart"); OPTION_COMMON_VERSION: return version(); @@ -10765,6 +10717,9 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(arg_json_format_flags); } if (option_parser_get_n_args(&opts) > 1) diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index 464e0d3627..c24afc33b1 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -73,6 +73,7 @@ INTROSPECTABLE=( systemd-pcrlock systemd-pty-forward systemd-random-seed + systemd-repart systemd-report systemd-sbsign systemd-sleep From 3a2d5873132a24aaf65d6a15acd02a5f94f306cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:33:16 +0200 Subject: [PATCH 13/23] run: convert to VERB_COMMAND and add --introspect-cli The custom help() and help_sudo_mode() are replaced by COMMAND() descriptions for systemd-run and run0, and --introspect-cli is added to both. run0 --help now goes through the pager, like systemd-run --help. --- src/run/run.c | 98 ++++++------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 5 + 2 files changed, 36 insertions(+), 67 deletions(-) diff --git a/src/run/run.c b/src/run/run.c index 1288dc0d1f..6b22f116b5 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -41,11 +41,9 @@ #include "format-table.h" #include "format-util.h" #include "fs-util.h" -#include "help-util.h" #include "hostname-util.h" #include "log.h" #include "main-func.h" -#include "options.h" #include "osc-context.h" #include "pager.h" #include "parse-argument.h" @@ -68,6 +66,7 @@ #include "unit-def.h" #include "unit-name.h" #include "user-util.h" +#include "verbs.h" #include "virt.h" static bool arg_ask_password = true; @@ -145,68 +144,14 @@ STATIC_DESTRUCTOR_REGISTER(arg_background, freep); STATIC_DESTRUCTOR_REGISTER(arg_shell_prompt_prefix, freep); STATIC_DESTRUCTOR_REGISTER(arg_area, freep); -static int help(void) { - int r; - - pager_open(arg_pager_flags); - - static const char* const groups[] = { - NULL, - "Path options", - "Socket options", - "Timer options", - }; - - Table *tables[ELEMENTSOF(groups)] = {}; - CLEANUP_ELEMENTS(tables, table_unref_array_clear); - - for (size_t i = 0; i < ELEMENTSOF(groups); i++) { - r = option_parser_get_help_table_full("systemd-run", groups[i], &tables[i]); - if (r < 0) - return r; - } - - (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], tables[3]); - - help_cmdline("[OPTIONS...] COMMAND [ARGUMENTS...]"); - help_abstract("Run the specified command in a transient scope or service."); - - for (size_t i = 0; i < ELEMENTSOF(groups); i++) { - help_section(groups[i] ?: "Options"); - - r = table_print_or_warn(tables[i]); - if (r < 0) - return r; - } - - help_man_page_reference("systemd-run", "1"); - return 0; -} - -static int help_sudo_mode(void) { - _cleanup_(table_unrefp) Table *opts_table = NULL; - int r; - - /* NB: Let's not go overboard with short options: we try to keep a modicum of compatibility with - * sudo's short switches, hence please do not introduce new short switches unless they have a roughly - * equivalent purpose on sudo. Use long options for everything private to run0. */ - - r = option_parser_get_help_table_ns("run0", &opts_table); - if (r < 0) - return r; - - help_cmdline("[OPTIONS...] COMMAND [ARGUMENTS...]"); - help_abstract("Elevate privileges interactively."); - - help_section("Options"); - - r = table_print_or_warn(opts_table); - if (r < 0) - return r; - - help_man_page_reference("run0", "1"); - return 0; -} +COMMAND( + "systemd-run\0", + "Run the specified command in a transient scope or service.", + .argspec = "COMMAND [ARGUMENTS…]\0", + .man_pages = "systemd-run.1\0", + .option_namespace = "systemd-run", + .pager_flags = &arg_pager_flags, +); static bool become_root(void) { if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM) @@ -263,7 +208,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_NAMESPACE("systemd-run"): {} OPTION_COMMON_HELP: - return help(); + return command_print_help("systemd-run"); OPTION_COMMON_VERSION: return version(); @@ -592,6 +537,9 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(arg_json_format_flags); } /* If we are talking to the per-user instance PolicyKit isn't going to help */ @@ -806,11 +754,24 @@ static Glyph pty_window_glyph(void) { return GLYPH_YELLOW_CIRCLE; } +COMMAND( + "run0\0", + "Elevate privileges interactively.", + .argspec = "COMMAND [ARGUMENTS…]\0", + .man_pages = "run0.1\0", + .option_namespace = "run0", + .pager_flags = &arg_pager_flags, +); + static int parse_argv_sudo_mode(int argc, char *argv[]) { int r; /* If invoked as "run0" binary, let's expose a more sudo-like interface. We add various extensions - * though (but limit the extension to long options). */ + * though (but limit the extension to long options). + * + * NB: Let's not go overboard with short options: we try to keep a modicum of compatibility with + * sudo's short switches, hence please do not introduce new short switches unless they have a roughly + * equivalent purpose on sudo. Use long options for everything private to run0. */ assert(argc >= 0); assert(argv); @@ -823,7 +784,7 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { OPTION_NAMESPACE("run0"): {} OPTION_COMMON_HELP: - return help_sudo_mode(); + return command_print_help("run0"); OPTION('V', "version", NULL, "Show package version"): return version(); @@ -975,6 +936,9 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { if (r < 0) return r; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(arg_json_format_flags); } if (!arg_working_directory) { diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index c24afc33b1..133723bce6 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -25,6 +25,7 @@ INTROSPECTABLE=( oomctl portablectl resolvectl + run0 systemd-ac-power systemd-analyze systemd-ask-password @@ -75,6 +76,7 @@ INTROSPECTABLE=( systemd-random-seed systemd-repart systemd-report + systemd-run systemd-sbsign systemd-sleep systemd-socket-activate @@ -132,3 +134,6 @@ systemd-id128 --introspect-cli | jq -e \ systemd-mstack --introspect-cli | jq -e \ '[.commands[].names[0]] | sort == ["mount.mstack", "systemd-mstack"]' + +systemd-run --introspect-cli | jq -e \ + '[.commands[].names[0]] | sort == ["run0", "systemd-run"]' From ae6f195d636d6b66d51e9078dcf3c68680301613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:33:30 +0200 Subject: [PATCH 14/23] storagectl: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. --help now goes through the pager, like other commands with --no-pager. The mount.storage helper interface is described by a second COMMAND(), so that it is exposed by --introspect-cli too. --- src/storage/storagectl.c | 65 +++++++------------ .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 4 ++ 2 files changed, 29 insertions(+), 40 deletions(-) diff --git a/src/storage/storagectl.c b/src/storage/storagectl.c index b899513616..51292fc66a 100644 --- a/src/storage/storagectl.c +++ b/src/storage/storagectl.c @@ -1,11 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-varlink.h" - #include #include #include +#include "sd-json.h" +#include "sd-varlink.h" + #include "alloc-util.h" #include "ansi-color.h" #include "argv-util.h" @@ -17,12 +18,10 @@ #include "fd-util.h" #include "format-table.h" #include "format-util.h" -#include "help-util.h" #include "machine-util.h" #include "main-func.h" #include "mount-util.h" #include "namespace-util.h" -#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "path-lookup.h" @@ -46,41 +45,15 @@ static bool arg_legend = true; static bool arg_ask_password = true; static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; -static int help(void) { - int r; +COMMAND( + "storagectl\0", + "Enumerate storage volumes and providers.", + .man_pages = "storagectl.1\0", + .option_namespace = "storagectl", + .pager_flags = &arg_pager_flags, +); - help_cmdline("[OPTIONS...] COMMAND"); - help_abstract("Enumerate storage volumes and providers."); - - _cleanup_(table_unrefp) Table *verbs = NULL; - r = verbs_get_help_table(&verbs); - if (r < 0) - return r; - - _cleanup_(table_unrefp) Table *options = NULL; - r = option_parser_get_help_table_ns("storagectl", &options); - if (r < 0) - return r; - - (void) table_sync_column_widths(0, verbs, options); - - help_section("Commands"); - - r = table_print_or_warn(verbs); - if (r < 0) - return r; - - help_section("Options"); - - r = table_print_or_warn(options); - if (r < 0) - return r; - - help_man_page_reference("storagectl", "1"); - return 0; -} - -VERB_COMMON_HELP_HIDDEN(help); +VERB_COMMON_HELP_AUTO_HIDDEN("storagectl"); static const char *ro_color(int ro) { if (ro > 0) @@ -395,6 +368,15 @@ static int verb_providers(int argc, char *argv[], uintptr_t data, void *userdata return 0; } +/* This COMMAND must be below all the VERBs of the main command */ +COMMAND( + "mount.storage\0", + "External helper for mount.8 to expose storage volumes.", + .man_pages = "storagectl.1\0", + .option_namespace = "mount.storage", + .pager_flags = &arg_pager_flags, +); + static int parse_argv(int argc, char *argv[], char ***remaining_args) { int r; @@ -409,7 +391,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION_NAMESPACE("storagectl"): {} OPTION_COMMON_HELP: - return help(); + return command_print_help("storagectl"); OPTION_COMMON_VERSION: return version(); @@ -439,6 +421,9 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION_COMMON_USER: arg_runtime_scope = RUNTIME_SCOPE_USER; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(arg_json_format_flags); } *remaining_args = option_parser_get_args(&opts); @@ -448,7 +433,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { static int run_as_mount_helper(int argc, char *argv[]) { int r; - /* Implements util-linux "external helper" command line interface, as per mount(8) man page. + /* Implements util-linux "external helper" command line interface, as per mount.8 man page. * * Usage: * diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index 133723bce6..9a4dd5e167 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -26,6 +26,7 @@ INTROSPECTABLE=( portablectl resolvectl run0 + storagectl systemd-ac-power systemd-analyze systemd-ask-password @@ -126,6 +127,9 @@ fi resolvectl --introspect-cli | jq -e \ '[.commands[].names[0]] | sort == ["resolvconf", "resolvectl", "systemd-resolve"]' +storagectl --introspect-cli | jq -e \ + '[.commands[].names[0]] | sort == ["mount.storage", "storagectl"]' + systemd-clonesetup --introspect-cli | jq -e \ '.commands[0].verbs | map(.names[0]) | sort == ["add", "remove"]' From 388eb270181b3d005604ad77cc7b30cbd9d72022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:33:44 +0200 Subject: [PATCH 15/23] sysctl: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. An explicit "Commands" option group is added so that the section heading stays the same. The abstract is changed to the imperative. --help now goes through the pager, like other commands with --no-pager. --- src/sysctl/sysctl.c | 63 +++++-------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 18 insertions(+), 46 deletions(-) diff --git a/src/sysctl/sysctl.c b/src/sysctl/sysctl.c index 8149e755f5..fa95932985 100644 --- a/src/sysctl/sysctl.c +++ b/src/sysctl/sysctl.c @@ -1,26 +1,26 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include +#include "sd-json.h" + #include "alloc-util.h" #include "build.h" #include "conf-files.h" #include "constants.h" #include "creds-util.h" #include "errno-util.h" -#include "format-table.h" #include "glob-util.h" #include "hashmap.h" #include "log.h" #include "main-func.h" -#include "options.h" #include "pager.h" #include "path-util.h" #include "pretty-print.h" #include "string-util.h" #include "strv.h" #include "sysctl-util.h" +#include "verbs.h" static char **arg_prefixes = NULL; static CatFlags arg_cat_flags = CAT_CONFIG_OFF; @@ -31,6 +31,14 @@ static PagerFlags arg_pager_flags = 0; STATIC_DESTRUCTOR_REGISTER(arg_prefixes, strv_freep); +COMMAND( + "systemd-sysctl\0", + "Apply kernel sysctl settings.", + .argspec = "[CONFIGURATION FILE…]\0", + .man_pages = "systemd-sysctl.service.8\0", + .pager_flags = &arg_pager_flags, +); + typedef struct SysctlOption { char *key; char *value; @@ -317,48 +325,6 @@ static int cat_config(char **files) { return cat_files(NULL, files, arg_cat_flags); } -static int help(void) { - _cleanup_free_ char *link = NULL; - _cleanup_(table_unrefp) Table *commands = NULL, *options = NULL; - int r; - - r = terminal_urlify_man("systemd-sysctl.service", "8", &link); - if (r < 0) - return log_oom(); - - r = option_parser_get_help_table(&commands); - if (r < 0) - return r; - - r = option_parser_get_help_table_group("Options", &options); - if (r < 0) - return r; - - (void) table_sync_column_widths(0, commands, options); - - printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n" - "\n%sApplies kernel sysctl settings.%s\n" - "\n%sCommands:%s\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - ansi_underline(), - ansi_normal()); - - r = table_print_or_warn(commands); - if (r < 0) - return r; - - printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal()); - - r = table_print_or_warn(options); - if (r < 0) - return r; - - printf("\nSee the %s for details.\n", link); - return 0; -} - static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); @@ -369,8 +335,10 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { + OPTION_GROUP("Commands"): {} + OPTION_COMMON_HELP: - return help(); + return command_print_help("systemd-sysctl"); OPTION_COMMON_VERSION: return version(); @@ -422,6 +390,9 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { "Treat arguments as configuration lines"): arg_inline = true; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } *remaining_args = option_parser_get_args(&opts); diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index 9a4dd5e167..50d1c50c38 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -87,6 +87,7 @@ INTROSPECTABLE=( systemd-storage-block systemd-storage-fs systemd-storagetm + systemd-sysctl systemd-sysinstall systemd-tpm2-clear systemd-tpm2-setup From adc83f020ac2fe0d344a451844541b5375508331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:34:45 +0200 Subject: [PATCH 16/23] sysupdate: convert to VERB_COMMAND and add --introspect-cli MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. The "Options" group marker after --help/--version is dropped, so that all options are listed in one section after the verbs. The synopsis now uses the generic "[OPTION…] COMMAND …" form: a bare VERSION is not accepted as the first argument, and the man page does not list it either. --help now goes through the pager, like other commands with --no-pager. --- src/sysupdate/sysupdate.c | 58 ++++--------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 13 insertions(+), 46 deletions(-) diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 0e1338140f..49a0f1b37a 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -23,14 +23,12 @@ #include "format-table.h" #include "glyph-util.h" #include "hashmap.h" -#include "help-util.h" #include "hexdecoct.h" #include "image-policy.h" #include "json-util.h" #include "loop-util.h" #include "main-func.h" #include "mount-util.h" -#include "options.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" @@ -83,6 +81,13 @@ STATIC_DESTRUCTOR_REGISTER(arg_component, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_transfer_source, freep); +COMMAND( + "systemd-sysupdate\0", + "Update OS images.", + .man_pages = "systemd-sysupdate.8\0", + .pager_flags = &arg_pager_flags, +); + #define CONTEXT_NULL \ (Context) { \ .component_enabled = true, \ @@ -3301,46 +3306,7 @@ static int verb_enable_component(int argc, char *argv[], uintptr_t _data, void * return 0; } -static int help(void) { - _cleanup_(table_unrefp) Table *common_options = NULL, *options = NULL, *verbs = NULL; - int r; - - r = verbs_get_help_table(&verbs); - if (r < 0) - return r; - - r = option_parser_get_help_table(&common_options); - if (r < 0) - return r; - - r = option_parser_get_help_table_group("Options", &options); - if (r < 0) - return r; - - (void) table_sync_column_widths(0, verbs, common_options, options); - - help_cmdline("[OPTIONS…] [VERSION]"); - help_abstract("Update OS images."); - - help_section("Commands"); - r = table_print_or_warn(verbs); - if (r < 0) - return r; - - r = table_print_or_warn(common_options); - if (r < 0) - return r; - - help_section("Options"); - r = table_print_or_warn(options); - if (r < 0) - return r; - - help_man_page_reference("systemd-sysupdate", "8"); - return 0; -} - -VERB_COMMON_HELP_HIDDEN(help); +VERB_COMMON_HELP_AUTO_HIDDEN("systemd-sysupdate"); static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); @@ -3354,14 +3320,11 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { switch (c) { OPTION_COMMON_HELP: - return help(); + return command_print_help("systemd-sysupdate"); OPTION_COMMON_VERSION: return version(); - OPTION_GROUP("Options"): - break; - OPTION('C', "component", "NAME", "Select component to update"): if (isempty(opts.arg)) { @@ -3496,6 +3459,9 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { return r; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(arg_json_format_flags); } if (arg_image && arg_root) diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index 50d1c50c38..a74346b616 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -89,6 +89,7 @@ INTROSPECTABLE=( systemd-storagetm systemd-sysctl systemd-sysinstall + systemd-sysupdate systemd-tpm2-clear systemd-tpm2-setup systemd-tty-ask-password-agent From 0cbeceaea2b97bc4c206c954cb519a969824a9fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:34:59 +0200 Subject: [PATCH 17/23] updatectl: convert to VERB_COMMAND and add --introspect-cli MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. The odd "Verbs" option group marker is dropped, so that all options are listed in one section after the verbs, with --help and --version first. The synopsis now uses the generic "[OPTION…] COMMAND …" form: a bare VERSION is not accepted as the first argument, and the man page does not list it either. --help now goes through the pager, like other commands with --no-pager. --- src/sysupdate/updatectl.c | 76 ++++--------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 17 insertions(+), 60 deletions(-) diff --git a/src/sysupdate/updatectl.c b/src/sysupdate/updatectl.c index cd41aeb900..70902697cf 100644 --- a/src/sysupdate/updatectl.c +++ b/src/sysupdate/updatectl.c @@ -18,7 +18,6 @@ #include "hashmap.h" #include "json-util.h" #include "main-func.h" -#include "options.h" #include "pager.h" #include "polkit-agent.h" #include "pretty-print.h" @@ -39,6 +38,13 @@ static bool arg_ask_password = true; static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; static const char *arg_host = NULL; +COMMAND( + "updatectl\0", + "Manage system updates.", + .man_pages = "updatectl.1\0", + .pager_flags = &arg_pager_flags, +); + #define SYSUPDATE_HOST_PATH "/org/freedesktop/sysupdate1/target/host" #define SYSUPDATE_TARGET_INTERFACE "org.freedesktop.sysupdate1.Target" @@ -1635,58 +1641,7 @@ static int verb_enable(int argc, char *argv[], uintptr_t _data, void *userdata) return 0; } -static int help(void) { - _cleanup_free_ char *link = NULL; - _cleanup_(table_unrefp) Table *verbs = NULL, *verbs2 = NULL, *options = NULL; - int r; - - r = terminal_urlify_man("updatectl", "1", &link); - if (r < 0) - return log_oom(); - - r = verbs_get_help_table(&verbs); - if (r < 0) - return r; - - r = option_parser_get_help_table_group("Verbs", &verbs2); - if (r < 0) - return r; - - r = option_parser_get_help_table(&options); - if (r < 0) - return r; - - (void) table_sync_column_widths(0, verbs, verbs2, options); - - printf("%s [OPTIONS...] [VERSION]\n" - "\n%sManage system updates.%s\n" - "\n%sCommands:%s\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - ansi_underline(), - ansi_normal()); - - r = table_print_or_warn(verbs); - if (r < 0) - return r; - r = table_print_or_warn(verbs2); - if (r < 0) - return r; - - printf("\n%sOptions:%s\n", - ansi_underline(), - ansi_normal()); - - r = table_print_or_warn(options); - if (r < 0) - return r; - - printf("\nSee the %s for details.\n", link); - return 0; -} - -VERB_COMMON_HELP_HIDDEN(help); +VERB_COMMON_HELP_AUTO_HIDDEN("updatectl"); static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); @@ -1697,6 +1652,12 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { + OPTION_COMMON_HELP: + return command_print_help("updatectl"); + + OPTION_COMMON_VERSION: + return version(); + OPTION_LONG("reboot", NULL, "Reboot after updating to newer version"): arg_reboot = true; break; @@ -1722,17 +1683,12 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { arg_legend = false; break; - OPTION_GROUP("Verbs"): {} - OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - OPTION_COMMON_HELP: - return help(); - - OPTION_COMMON_VERSION: - return version(); + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } *ret_args = option_parser_get_args(&opts); diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index a74346b616..5949861d87 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -97,6 +97,7 @@ INTROSPECTABLE=( systemd-validatefs systemd-veritysetup timedatectl + updatectl userdbctl varlinkctl ) From 6109c2a70719b01ed009034f9e0fa9a7ede531db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:35:21 +0200 Subject: [PATCH 18/23] sysusers: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. An explicit "Commands" option group is added so that the section heading stays the same. The abstract is changed to the imperative. --help now goes through the pager, like other commands with --no-pager. --- src/sysusers/sysusers.c | 59 +++++-------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 17 insertions(+), 43 deletions(-) diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 5cf4c01964..489a2697e9 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -3,6 +3,8 @@ #include #include +#include "sd-json.h" + #include "alloc-util.h" #include "build.h" #include "chase.h" @@ -16,7 +18,6 @@ #include "extract-word.h" #include "fd-util.h" #include "fileio.h" -#include "format-table.h" #include "format-util.h" #include "fs-util.h" #include "hashmap.h" @@ -29,7 +30,6 @@ #include "loop-util.h" #include "main-func.h" #include "mount-util.h" -#include "options.h" #include "pager.h" #include "parse-argument.h" #include "path-util.h" @@ -114,6 +114,14 @@ STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); +COMMAND( + "systemd-sysusers\0", + "Create system user and group accounts.", + .argspec = "[CONFIGURATION FILE…]\0", + .man_pages = "systemd-sysusers.service.8\0", + .pager_flags = &arg_pager_flags, +); + typedef struct Context { int audit_fd; @@ -2094,46 +2102,6 @@ static int cat_config(void) { return cat_files(NULL, files, arg_cat_flags); } -static int help(void) { - _cleanup_free_ char *link = NULL; - _cleanup_(table_unrefp) Table *cmds = NULL, *opts = NULL; - int r; - - r = terminal_urlify_man("systemd-sysusers.service", "8", &link); - if (r < 0) - return log_oom(); - - r = option_parser_get_help_table(&cmds); - if (r < 0) - return r; - - r = option_parser_get_help_table_group("Options", &opts); - if (r < 0) - return r; - - (void) table_sync_column_widths(0, cmds, opts); - - printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n" - "\n%sCreates system user and group accounts.%s\n" - "\nCommands:\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal()); - - r = table_print_or_warn(cmds); - if (r < 0) - return r; - - printf("\nOptions:\n"); - - r = table_print_or_warn(opts); - if (r < 0) - return r; - - printf("\nSee the %s for details.\n", link); - return 0; -} - static int parse_argv(int argc, char *argv[], char ***ret_args) { int r; @@ -2145,6 +2113,8 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { + OPTION_GROUP("Commands"): {} + OPTION_COMMON_CAT_CONFIG: arg_cat_flags = CAT_CONFIG_ON; break; @@ -2154,7 +2124,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_COMMON_HELP: - return help(); + return command_print_help("systemd-sysusers"); OPTION_COMMON_VERSION: return version(); @@ -2201,6 +2171,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } char **args = option_parser_get_args(&opts); diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index 5949861d87..d3b5cc3f51 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -90,6 +90,7 @@ INTROSPECTABLE=( systemd-sysctl systemd-sysinstall systemd-sysupdate + systemd-sysusers systemd-tpm2-clear systemd-tpm2-setup systemd-tty-ask-password-agent From 60dee6613411d4cc76de7c34db0f5a35e7f0a87d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:35:38 +0200 Subject: [PATCH 19/23] tmpfiles: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. An explicit "Commands" option group is added so that the section heading stays the same. The synopsis is now the same as for systemd-sysusers, i.e. the "commands" are the options listed in the Commands section. --help now goes through the pager, like other commands with --no-pager. --- src/tmpfiles/tmpfiles.c | 58 +++++-------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 16 insertions(+), 43 deletions(-) diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 2d362cb9ad..e567bd63f2 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -7,6 +7,7 @@ #include #include +#include "sd-json.h" #include "sd-path.h" #include "acl-util.h" @@ -32,7 +33,6 @@ #include "extract-word.h" #include "fd-util.h" #include "fileio.h" -#include "format-table.h" #include "format-util.h" #include "fs-util.h" #include "glob-util.h" @@ -47,7 +47,6 @@ #include "mount-util.h" #include "mountpoint-util.h" #include "offline-passwd.h" -#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -251,6 +250,14 @@ STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_label_context, mac_label_context_freep); +COMMAND( + "systemd-tmpfiles\0", + "Create, delete, and clean up files and directories.", + .argspec = "[CONFIGURATION FILE…]\0", + .man_pages = "systemd-tmpfiles.8\0", + .pager_flags = &arg_pager_flags, +); + static const char *const creation_mode_verb_table[_CREATION_MODE_MAX] = { [CREATION_NORMAL] = "Created", [CREATION_EXISTING] = "Found existing", @@ -4916,46 +4923,6 @@ static int exclude_default_prefixes(void) { return 0; } -static int help(void) { - _cleanup_free_ char *link = NULL; - _cleanup_(table_unrefp) Table *cmds = NULL, *opts = NULL; - int r; - - r = terminal_urlify_man("systemd-tmpfiles", "8", &link); - if (r < 0) - return log_oom(); - - r = option_parser_get_help_table(&cmds); - if (r < 0) - return r; - - r = option_parser_get_help_table_group("Options", &opts); - if (r < 0) - return r; - - (void) table_sync_column_widths(0, cmds, opts); - - printf("%s COMMAND [OPTIONS...] [CONFIGURATION FILE...]\n" - "\n%sCreate, delete, and clean up files and directories.%s\n" - "\nCommands:\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal()); - - r = table_print_or_warn(cmds); - if (r < 0) - return r; - - printf("\nOptions:\n"); - - r = table_print_or_warn(opts); - if (r < 0) - return r; - - printf("\nSee the %s for details.\n", link); - return 0; -} - static int parse_argv(int argc, char *argv[], char ***ret_args) { int r; @@ -4967,6 +4934,8 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { + OPTION_GROUP("Commands"): {} + OPTION_LONG("create", NULL, "Create and adjust files and directories"): arg_operation |= OPERATION_CREATE; break; @@ -4994,7 +4963,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; OPTION_COMMON_HELP: - return help(); + return command_print_help("systemd-tmpfiles"); OPTION_COMMON_VERSION: return version(); @@ -5074,6 +5043,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } char **args = option_parser_get_args(&opts); diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index d3b5cc3f51..f67d178b7c 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -91,6 +91,7 @@ INTROSPECTABLE=( systemd-sysinstall systemd-sysupdate systemd-sysusers + systemd-tmpfiles systemd-tpm2-clear systemd-tpm2-setup systemd-tty-ask-password-agent From b4199e7376ef4e07e1dd16ae7823b5b19a79fe50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:35:57 +0200 Subject: [PATCH 20/23] vmspawn: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. --- src/vmspawn/vmspawn.c | 66 ++++--------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 14 insertions(+), 53 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 725e90dbec..4708b976ab 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -13,6 +13,7 @@ #include "sd-daemon.h" #include "sd-event.h" #include "sd-id128.h" +#include "sd-json.h" #include "sd-varlink.h" #include "alloc-util.h" @@ -37,12 +38,10 @@ #include "fd-util.h" #include "fileio.h" #include "fork-notify.h" -#include "format-table.h" #include "format-util.h" #include "fs-util.h" #include "gpt.h" #include "group-record.h" -#include "help-util.h" #include "hexdecoct.h" #include "hostname-setup.h" #include "hostname-util.h" @@ -59,7 +58,6 @@ #include "namespace-util.h" #include "netif-util.h" #include "nsresource.h" -#include "options.h" #include "osc-context.h" #include "pager.h" #include "parse-argument.h" @@ -92,6 +90,7 @@ #include "user-record.h" #include "user-util.h" #include "utf8.h" +#include "verbs.h" #include "vmspawn-bind-volume.h" #include "vmspawn-mount.h" #include "vmspawn-qemu-config.h" @@ -233,55 +232,13 @@ STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_shell, freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_groups, strv_freep); -static int help(void) { - int r; - - pager_open(arg_pager_flags); - - static const char* const groups[] = { - NULL, - "Image", - "Host Configuration", - "Networking", - "Execution", - "System Identity", - "Properties", - "User Namespacing", - "Mounts", - "Logging", - "SSH", - "Input/Output", - "Credentials", - }; - - Table* tables[ELEMENTSOF(groups)] = {}; - CLEANUP_ELEMENTS(tables, table_unref_array_clear); - - for (size_t i = 0; i < ELEMENTSOF(groups); i++) { - r = option_parser_get_help_table_group(groups[i], &tables[i]); - if (r < 0) - return r; - } - - (void) table_sync_column_widths( - 0, tables[0], tables[1], tables[2], tables[3], tables[4], - tables[5], tables[6], tables[7], tables[8], tables[9], tables[10], - tables[11], tables[12]); - - help_cmdline("[OPTIONS...] [ARGUMENTS...]"); - help_abstract("Spawn a command or OS in a virtual machine."); - - for (size_t i = 0; i < ELEMENTSOF(groups); i++) { - help_section(groups[i] ?: "Options"); - - r = table_print_or_warn(tables[i]); - if (r < 0) - return r; - } - - help_man_page_reference("systemd-vmspawn", "1"); - return 0; -} +COMMAND( + "systemd-vmspawn\0", + "Spawn a command or OS in a virtual machine.", + .argspec = "[ARGUMENTS…]\0", + .man_pages = "systemd-vmspawn.1\0", + .pager_flags = &arg_pager_flags, +); static int parse_environment(void) { const char *e; @@ -354,7 +311,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { OPTION_COMMON_HELP: - return help(); + return command_print_help("systemd-vmspawn"); OPTION_COMMON_VERSION: return version(); @@ -957,6 +914,9 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } /* Drop duplicate --bind-user= and --bind-user-group= entries */ diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index f67d178b7c..59b63fec5f 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -98,6 +98,7 @@ INTROSPECTABLE=( systemd-update-done systemd-validatefs systemd-veritysetup + systemd-vmspawn timedatectl updatectl userdbctl From 38d854b1144ee13d12535ccf769a3f712a8878f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 01:36:07 +0200 Subject: [PATCH 21/23] vpick: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, and --introspect-cli is added. An explicit "Lookup Keys" option group is added so that the section heading stays the same. --- src/vpick/vpick-tool.c | 58 +++++-------------- .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 17 insertions(+), 42 deletions(-) diff --git a/src/vpick/vpick-tool.c b/src/vpick/vpick-tool.c index 6ce3ef944e..fb1c4d0329 100644 --- a/src/vpick/vpick-tool.c +++ b/src/vpick/vpick-tool.c @@ -2,20 +2,21 @@ #include +#include "sd-json.h" + #include "alloc-util.h" #include "architecture.h" #include "build.h" #include "format-table.h" #include "log.h" #include "main-func.h" -#include "options.h" #include "parse-util.h" #include "path-util.h" -#include "pretty-print.h" #include "stat-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" +#include "verbs.h" #include "vpick.h" typedef enum { @@ -42,6 +43,13 @@ STATIC_DESTRUCTOR_REGISTER(arg_filter_basename, freep); STATIC_DESTRUCTOR_REGISTER(arg_filter_version, freep); STATIC_DESTRUCTOR_REGISTER(arg_filter_suffix, freep); +COMMAND( + "systemd-vpick\0", + "Pick entry from versioned directory.", + .argspec = "PATH…\0", + .man_pages = "systemd-vpick.1\0", +); + static const char *print_table[_PRINT_MAX] = { [PRINT_PATH] = "path", [PRINT_FILENAME] = "filename", @@ -54,45 +62,6 @@ static const char *print_table[_PRINT_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(print, Print); -static int help(void) { - _cleanup_free_ char *link = NULL; - _cleanup_(table_unrefp) Table *lookup_keys = NULL, *output = NULL; - int r; - - r = terminal_urlify_man("systemd-vpick", "1", &link); - if (r < 0) - return log_oom(); - - r = option_parser_get_help_table(&lookup_keys); - if (r < 0) - return r; - - r = option_parser_get_help_table_group("Output", &output); - if (r < 0) - return r; - - (void) table_sync_column_widths(0, lookup_keys, output); - - printf("%s [OPTIONS...] PATH...\n" - "\n%sPick entry from versioned directory.%s\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal()); - - printf("\n%sLookup Keys:%s\n", ansi_underline(), ansi_normal()); - r = table_print_or_warn(lookup_keys); - if (r < 0) - return r; - - printf("\n%sOutput:%s\n", ansi_underline(), ansi_normal()); - r = table_print_or_warn(output); - if (r < 0) - return r; - - printf("\nSee the %s for details.\n", link); - return 0; -} - static int parse_argv(int argc, char *argv[], char ***ret_args) { int r; @@ -104,6 +73,8 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { + OPTION_GROUP("Lookup Keys"): {} + OPTION('B', "basename", "BASENAME", "Look for specified basename"): if (!filename_part_is_valid(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid basename string: %s", opts.arg); @@ -172,7 +143,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_GROUP("Output"): {} OPTION_COMMON_HELP: - return help(); + return command_print_help("systemd-vpick"); OPTION_COMMON_VERSION: return version(); @@ -208,6 +179,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { SET_FLAG(arg_flags, PICK_RESOLVE, r); break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } if (arg_print < 0) diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index 59b63fec5f..fc6e7db1ac 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -99,6 +99,7 @@ INTROSPECTABLE=( systemd-validatefs systemd-veritysetup systemd-vmspawn + systemd-vpick timedatectl updatectl userdbctl From da26e6f744f42bf8418c9de5dce3db0ec6545eea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 12:27:21 +0200 Subject: [PATCH 22/23] run0: add --no-pager Since run0 --help now goes through the pager, provide a way to turn that off from the command line, like all other commands that page --help. The option is documented the same way as for systemd-run. --- man/run0.xml | 10 ++++++++++ src/run/run.c | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/man/run0.xml b/man/run0.xml index c2e891149f..1eb9905b57 100644 --- a/man/run0.xml +++ b/man/run0.xml @@ -373,6 +373,16 @@ + + + + + Do not pipe output into a pager. This currently only applies to + . (The pager is not started during normal operation.) + + + + All command line arguments after the first non-option argument become part of the command line of diff --git a/src/run/run.c b/src/run/run.c index 6b22f116b5..63994775a8 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -797,6 +797,10 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { arg_ask_password = false; break; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + OPTION_LONG("machine", "CONTAINER", "Operate on local container"): r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) From 92a97a2b9d0110c1fe9d67b2feab33a4b9b51ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 18 Aug 2026 13:00:33 +0200 Subject: [PATCH 23/23] dissect: describe the mount.ddi helper interface Add a second COMMAND() for the mount.ddi helper namespace, like for mount.storage and mount.mstack, so that it is exposed by --introspect-cli too. Fixes 1a54ce154c9c5485b77c866327c530d9a1457094 + 66246c98b5435be57a8cfd01641183bb4e6cedea. --- src/dissect/dissect.c | 7 +++++++ test/units/TEST-74-AUX-UTILS.introspect-cli.sh | 3 +++ 2 files changed, 10 insertions(+) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index ed0182950d..55f36b1d86 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -712,6 +712,13 @@ static int parse_argv(int argc, char *argv[]) { return 1; } +COMMAND( + "mount.ddi\0", + "External helper for mount.8 to mount Discoverable Disk Images (DDIs).", + .man_pages = "systemd-dissect.1\0", + .option_namespace = "mount.ddi", +); + static int parse_argv_as_mount_helper(int argc, char *argv[]) { const char *options = NULL; bool fake = false; diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index fc6e7db1ac..8da538ef12 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -140,6 +140,9 @@ storagectl --introspect-cli | jq -e \ systemd-clonesetup --introspect-cli | jq -e \ '.commands[0].verbs | map(.names[0]) | sort == ["add", "remove"]' +systemd-dissect --introspect-cli | jq -e \ + '[.commands[].names[0]] | sort == ["mount.ddi", "systemd-dissect"]' + systemd-id128 --introspect-cli | jq -e \ '.commands[0].verbs | map(.names[0]) | contains(["new", "machine-id", "show", "help"])'