From 57a8bac815c546619b82e0e3d3e932eba73d5213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 27 Aug 2026 13:03:06 +0200 Subject: [PATCH 01/11] shared/service-util: adjust comment Fixup for 65510d4cbad88e4c0c39f22510cedce5a4ddf030. --- src/shared/service-util.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shared/service-util.h b/src/shared/service-util.h index e63f8fe9b0..e35b7838b5 100644 --- a/src/shared/service-util.h +++ b/src/shared/service-util.h @@ -13,9 +13,9 @@ int _service_parse_argv( /* The service is expected to define a COMMAND() with .option_namespace = "service" and the option * groups matching the features it supports: "Options" always, "Bus introspection" iff bus_objects - * is passed, "Runtime scope" iff runtime_scope is passed. The macro captures the calling binary's - * verb section, where that COMMAND is found, while the options are defined in - * service_parse_argv_full() itself. */ + * is passed, "Runtime scope" iff runtime_scope is passed. The macro inserts the calling binary's + * verb section, where that COMMAND is found, while the options are defined in _service_parse_argv(). + */ #define service_parse_argv(bus_objects, runtime_scope, argc, argv) \ _service_parse_argv( \ __start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, \ From a677a39c1e62c94ff25d2d088a958f683a109d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 27 Aug 2026 15:26:39 +0200 Subject: [PATCH 02/11] test-journal-append: use DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE Negative return values, e.g. from option parsing, were propagated verbatim as the exit status of main() and truncated to meaningless values (-EINVAL became 234). The positive-failure variant is needed because the binary may exit with EXIT_TEST_SKIP. Also propagate EXIT_TEST_SKIP instead of suppressing it. --- src/libsystemd/sd-journal/test-journal-append.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libsystemd/sd-journal/test-journal-append.c b/src/libsystemd/sd-journal/test-journal-append.c index c71240660d..1905c570f2 100644 --- a/src/libsystemd/sd-journal/test-journal-append.c +++ b/src/libsystemd/sd-journal/test-journal-append.c @@ -9,6 +9,7 @@ #include "iovec-util.h" #include "journal-file-util.h" #include "log.h" +#include "main-func.h" #include "mmap-cache.h" #include "options.h" #include "parse-util.h" @@ -141,7 +142,7 @@ static int journal_corrupt_and_append(uint64_t start_offset, uint64_t step) { return 0; } -int main(int argc, char *argv[]) { +static int run(int argc, char *argv[]) { uint64_t start_offset = UINT64_MAX; uint64_t iterations = 100; uint64_t iteration_step = 1; @@ -235,12 +236,11 @@ int main(int argc, char *argv[]) { offset = (start_offset == UINT64_MAX ? 0 : start_offset) + i * iteration_step; r = journal_corrupt_and_append(offset, corrupt_step); - if (r < 0) - return EXIT_FAILURE; - if (r > 0) - /* Reached the end of the journal file */ - break; + if (r != 0) /* Either error or EXIT_TEST_SKIP */ + return r; } - return EXIT_SUCCESS; + return 0; } + +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From 274d366eb35854b887edc978d552bbf4ad8eca2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 27 Aug 2026 13:08:19 +0200 Subject: [PATCH 03/11] report-sign-tpm2: convert to VERB_COMMAND and add --introspect-cli The custom help() is replaced by a COMMAND() description, matching the other report-sign services. The abstract is now wrapped at 80 columns in the help output. --- src/report/report-sign-tpm2.c | 37 +++++++------------ .../units/TEST-74-AUX-UTILS.introspect-cli.sh | 1 + 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/report/report-sign-tpm2.c b/src/report/report-sign-tpm2.c index a742639336..10fa62f31b 100644 --- a/src/report/report-sign-tpm2.c +++ b/src/report/report-sign-tpm2.c @@ -9,9 +9,7 @@ #include "dlopen-note.h" #include "fd-util.h" #include "fileio.h" -#include "format-table.h" #include "fs-util.h" -#include "help-util.h" #include "iovec-util.h" #include "json-util.h" #include "log.h" @@ -25,6 +23,14 @@ #include "tpm2-util.h" #include "varlink-io.systemd.Report.Signer.h" #include "varlink-util.h" +#include "verbs.h" + +COMMAND( + "systemd-report-sign-tpm2\0", + "Get an attestation report from a Trusted Platform Module (TPM) " + "that includes the hash of the system report.", + .man_pages = "systemd-report-sign-tpm2@.service(8)\0", +); #define REPORT_SIGN_TPM2_PERSISTENT_DIR "/var/lib/systemd/report.sign.tpm2" #define REPORT_SIGN_TPM2_RUNTIME_DIR "/run/systemd/report.sign.tpm2" @@ -715,27 +721,6 @@ static int vl_server(void) { return 0; } -static int help(void) { - _cleanup_(table_unrefp) Table *options = NULL; - int r; - - r = option_parser_get_help_table(&options); - if (r < 0) - return r; - - help_cmdline("[OPTIONS...]"); - help_abstract("Get an attestation report from a Trusted Platform Module (TPM) " - "that includes the hash of the system report."); - help_section("Options"); - - r = table_print_or_warn(options); - if (r < 0) - return r; - - help_man_page_reference("systemd-report-sign-tpm2@.service", "8"); - return 0; -} - static int parse_argv(int argc, char *argv[]) { int r; @@ -747,9 +732,13 @@ static int parse_argv(int argc, char *argv[]) { FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: - return help(); + return command_print_help(); + OPTION_COMMON_VERSION: return version(); + + 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 8c6e0e0ad3..00bde6b6b7 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -111,6 +111,7 @@ INTROSPECTABLE=( systemd-report-cgroup systemd-report-files systemd-report-sign-plain + systemd-report-sign-tpm2 systemd-report-sign-tsm systemd-resolved systemd-run From 37900a62656a2a027d4a9c4ddf48efa6a7229e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 27 Aug 2026 13:13:15 +0200 Subject: [PATCH 04/11] tests: convert manual test binaries to VERB_COMMAND and --introspect-cli The last five hand-written help() functions are replaced by COMMAND() descriptions: test-homectl-prompts, test-libudev, test-modem-manager-mock, test-chase-manual, and test-journal-append (which open-coded the help inside the option parsing loop). None of the binaries are installed, so no man pages are referenced and they are not added to the introspection smoke test. --- src/home/test-homectl-prompts.c | 38 +++++-------------- .../sd-journal/test-journal-append.c | 32 +++++++--------- src/libudev/test-libudev.c | 29 ++++++-------- src/network/test-modem-manager-mock.c | 27 +++++-------- src/test/test-chase-manual.c | 32 ++++++---------- 5 files changed, 56 insertions(+), 102 deletions(-) diff --git a/src/home/test-homectl-prompts.c b/src/home/test-homectl-prompts.c index 5429eaaa5e..8e65c864e0 100644 --- a/src/home/test-homectl-prompts.c +++ b/src/home/test-homectl-prompts.c @@ -1,8 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-json.h" + #include "alloc-util.h" -#include "format-table.h" -#include "help-util.h" #include "homectl-prompts.h" #include "main-func.h" #include "options.h" @@ -11,31 +11,10 @@ #include "tests.h" #include "verbs.h" -static int help(void) { - _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; - int r; - - r = option_parser_get_help_table(&options); - if (r < 0) - return r; - - r = verbs_get_help_table(&verbs); - if (r < 0) - return r; - - (void) table_sync_column_widths(0, options, verbs); - - help_cmdline("[OPTIONS...] VERB [USERNAME]"); - help_abstract("Exercise homectl prompt functions in isolation."); - - help_section("Verbs"); - r = table_print_or_warn(verbs); - if (r < 0) - return r; - - help_section("Options"); - return table_print_or_warn(options); -} +COMMAND( + "test-homectl-prompts\0", + "Exercise homectl prompt functions in isolation.", +); VERB(verb_groups, "groups", "[USER]\0", VERB_ANY, 2, 0, "Select groups"); static int verb_groups(int argc, char *argv[], uintptr_t _data, void *userdata) { @@ -83,7 +62,10 @@ static int parse_argv(int argc, char **argv, char ***remaining_args) { switch (c) { OPTION_COMMON_HELP: - return help(); + return command_print_help(); + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } *remaining_args = option_parser_get_args(&opts); diff --git a/src/libsystemd/sd-journal/test-journal-append.c b/src/libsystemd/sd-journal/test-journal-append.c index 1905c570f2..abdb06b9ef 100644 --- a/src/libsystemd/sd-journal/test-journal-append.c +++ b/src/libsystemd/sd-journal/test-journal-append.c @@ -4,8 +4,9 @@ #include #include +#include "sd-json.h" + #include "chattr-util.h" -#include "format-table.h" #include "iovec-util.h" #include "journal-file-util.h" #include "log.h" @@ -18,6 +19,12 @@ #include "tests.h" #include "time-util.h" #include "tmpfile-util.h" +#include "verbs.h" + +COMMAND( + "test-journal-append\0", + "Append entries to journal files and check corruption handling.", +); static int journal_append_message(JournalFile *mj, const char *message) { struct iovec iovec; @@ -157,24 +164,8 @@ static int run(int argc, char *argv[]) { FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - OPTION_COMMON_HELP: { - _cleanup_(table_unrefp) Table *options = NULL; - - r = option_parser_get_help_table(&options); - if (r < 0) - return r; - - printf("Syntax:\n" - " %s [OPTION...]\n" - "\nOptions:\n", - program_invocation_short_name); - - r = table_print_or_warn(options); - if (r < 0) - return r; - - return 0; - } + OPTION_COMMON_HELP: + return command_print_help(); OPTION_LONG("start-offset", "OFFSET", "Offset at which to start corrupting the journal " @@ -221,6 +212,9 @@ static int run(int argc, char *argv[]) { run_one = true; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } if (run_one) diff --git a/src/libudev/test-libudev.c b/src/libudev/test-libudev.c index 06feb1ffbc..2c09e59d62 100644 --- a/src/libudev/test-libudev.c +++ b/src/libudev/test-libudev.c @@ -3,9 +3,10 @@ #include #include +#include "sd-json.h" + #include "devnum-util.h" #include "fd-util.h" -#include "format-table.h" #include "libudev-list-internal.h" #include "log.h" #include "main-func.h" @@ -13,6 +14,7 @@ #include "options.h" #include "string-util.h" #include "tests.h" +#include "verbs.h" #include "version.h" static bool arg_monitor = false; @@ -405,22 +407,10 @@ static void test_list(void) { assert_se(!udev_list_entry_get_by_name(e, "ccc")); } -static int help(void) { - _cleanup_(table_unrefp) Table *options = NULL; - int r; - - r = option_parser_get_help_table(&options); - if (r < 0) - return r; - - printf("%s [OPTIONS...]\n\n", program_invocation_short_name); - - r = table_print_or_warn(options); - if (r < 0) - return r; - - return 0; -} +COMMAND( + "test-libudev\0", + "Test libudev functions.", +); static int parse_args(int argc, char *argv[], const char **syspath, const char **subsystem) { assert(argc >= 0); @@ -434,7 +424,7 @@ static int parse_args(int argc, char *argv[], const char **syspath, const char * switch (c) { OPTION_COMMON_HELP: - return help(); + return command_print_help(); OPTION('V', "version", NULL, "Show package version"): printf("%s\n", GIT_VERSION); @@ -455,6 +445,9 @@ static int parse_args(int argc, char *argv[], const char **syspath, const char * OPTION('m', "monitor", NULL, "Run monitor test"): arg_monitor = true; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } return 1; diff --git a/src/network/test-modem-manager-mock.c b/src/network/test-modem-manager-mock.c index c38a7a43cc..820e22c640 100644 --- a/src/network/test-modem-manager-mock.c +++ b/src/network/test-modem-manager-mock.c @@ -13,16 +13,16 @@ #include "sd-bus.h" #include "sd-daemon.h" #include "sd-event.h" +#include "sd-json.h" #include "alloc-util.h" #include "build.h" -#include "format-table.h" -#include "help-util.h" #include "log.h" #include "main-func.h" #include "options.h" #include "parse-util.h" #include "string-util.h" +#include "verbs.h" static char *arg_ifname = NULL; static char *arg_ipv4_address = NULL; @@ -364,20 +364,10 @@ static int filter_handler(sd_bus_message *m, void *userdata, sd_bus_error *error return 0; } -static int help(void) { - _cleanup_(table_unrefp) Table *options = NULL; - int r; - - r = option_parser_get_help_table(&options); - if (r < 0) - return r; - - help_cmdline("[OPTIONS...]"); - help_abstract("Mock ModemManager D-Bus service for testing."); - - help_section("Options"); - return table_print_or_warn(options); -} +COMMAND( + "test-modem-manager-mock\0", + "Mock ModemManager D-Bus service for testing.", +); static int parse_argv(int argc, char *argv[]) { int r; @@ -391,7 +381,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { OPTION_COMMON_HELP: - return help(); + return command_print_help(); OPTION_COMMON_VERSION: return version(); @@ -432,6 +422,9 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to parse IPv6 prefix length: %m"); break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } if (!arg_ifname) diff --git a/src/test/test-chase-manual.c b/src/test/test-chase-manual.c index 410522ceb1..e3d83a6026 100644 --- a/src/test/test-chase-manual.c +++ b/src/test/test-chase-manual.c @@ -1,36 +1,25 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-json.h" + #include "chase.h" #include "fd-util.h" -#include "format-table.h" #include "log.h" #include "main-func.h" #include "options.h" #include "strv.h" #include "tests.h" +#include "verbs.h" static const char *arg_root = NULL; static int arg_flags = 0; static bool arg_open = false; -static int help(void) { - _cleanup_(table_unrefp) Table *options = NULL; - int r; - - r = option_parser_get_help_table(&options); - if (r < 0) - return r; - - printf("%s [OPTIONS...] path...\n" - "\nExercise chase() function on specified paths.\n\n", - program_invocation_short_name); - - r = table_print_or_warn(options); - if (r < 0) - return r; - - return 0; -} +COMMAND( + "test-chase-manual\0", + "Exercise chase() function on specified paths.", + .argspec = "PATH…\0", +); static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); @@ -43,7 +32,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { switch (c) { OPTION_COMMON_HELP: - return help(); + return command_print_help(); OPTION_LONG("root", "PATH", "Operate below specified root directory"): arg_root = opts.arg; @@ -64,6 +53,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG_DATA("warn", NULL, CHASE_WARN, "Emit a warning on error"): arg_flags |= opts.opt->data; break; + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } *ret_args = option_parser_get_args(&opts); From e348b263cea57a64a0c27e6288cc935b65a8a6e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 27 Aug 2026 16:25:00 +0200 Subject: [PATCH 05/11] shared/format-table: drop unused table_sync_column_widths() The variadic pair-wise variant was only used by the hand-written help() functions to synchronize their verb and option tables. Those are all gone, and the verbs machinery uses table_sync_all_column_widths(). --- src/shared/format-table.c | 32 -------------------------------- src/shared/format-table.h | 2 -- 2 files changed, 34 deletions(-) diff --git a/src/shared/format-table.c b/src/shared/format-table.c index b0c15ca806..24dcddab1a 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -2218,38 +2218,6 @@ int table_set_column_width(Table *t, size_t column, size_t width) { return r; } -int _table_sync_column_widths(size_t column, Table *a, ...) { - size_t max = 0; - va_list ap; - int r = 0; - - assert(a); - - /* Make the specified column have the same width in the tables. */ - - va_start(ap, a); - for (Table *t = a; t; t = va_arg(ap, Table*)) { - size_t w; - - r = table_data_requested_width(t, column, &w); - if (r < 0) - break; - - max = MAX(max, w); - } - va_end(ap); - if (r < 0) - return log_error_errno(r, "Failed to query table column width: %m"); - - r = 0; - va_start(ap, a); - for (Table *t = a; t; t = va_arg(ap, Table*)) - RET_GATHER(r, table_set_column_width(t, column, max)); - va_end(ap); - - return r; -} - int table_sync_all_column_widths(size_t column, Table **a) { size_t max = 0; int r; diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 8087abfb9e..ebf447ed09 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -147,8 +147,6 @@ int table_hide_column_from_display_internal(Table *t, ...); int table_data_requested_width(Table *table, size_t column, size_t *ret); int table_set_column_width(Table *t, size_t column, size_t width); -int _table_sync_column_widths(size_t column, Table *a, ...); -#define table_sync_column_widths(column, a, ...) _table_sync_column_widths(column, a, __VA_ARGS__, NULL) int table_sync_all_column_widths(size_t column, Table **a); int table_print_full(Table *t, FILE *f, bool flush); From e90de9e2dbf683c441ed3e70435fde7b3b7da4f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 27 Aug 2026 17:16:43 +0200 Subject: [PATCH 06/11] shared/options: drop option_parser_get_help_table() and its wrappers The hand-written help() functions were the only users of the namespace+group help table builder, and they are all gone. The one remaining caller in the verbs machinery is ported to options_find_namespace() + options_get_help_table_group(), which build the identical table. Also drop OPTION_COMMON_INTROSPECT_CLI_ENTRY: it has no users, and references the no-longer-existing _OPTION_ENTRY() macro, so it could not even be used. --- src/shared/options.c | 80 -------------------------------------------- src/shared/options.h | 19 ----------- src/shared/verbs.c | 9 +++-- 3 files changed, 7 insertions(+), 101 deletions(-) diff --git a/src/shared/options.c b/src/shared/options.c index 72fc93245f..905307f88d 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -516,86 +516,6 @@ const Option* options_find_namespace( return NULL; /* not found :/ */ } -int _option_parser_get_help_table_full( - const Option options[], - const Option options_end[], - const char *namespace, - const char *group, - Table **ret) { - int r; - - // TODO: drop this function and all its wrappers - - assert(ret); - - _cleanup_(table_unrefp) Table *table = table_new("names", "help"); - if (!table) - return log_oom(); - - bool in_ns = namespace == NULL; /* Are we currently in the section of the array that forms namespace - * ? The first part is the default unnamed namespace, so - * if the namespace was not specified, we are in it. */ - - bool in_group = group == NULL; /* Are we currently in the section of the array that forms group - * ? The first part is the default group, so if the group was - * not specified, we are in it. */ - - const Option *opt; - for (opt = options; opt < options_end; opt++) { - bool ns_marker = FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER); - if (!in_ns) { - in_ns = ns_marker && streq(namespace, opt->long_code); - continue; - } - if (ns_marker) - break; /* End of namespace */ - - bool group_marker = FLAGS_SET(opt->flags, OPTION_GROUP_MARKER); - if (!in_group) { - in_group = group_marker && streq(group, opt->long_code); - continue; - } - if (group_marker) - break; /* End of group */ - - if (!opt->help) - /* No help string — we do not show the option */ - continue; - - _cleanup_free_ char *s = option_get_synopsis(opt, " ", /* show_metavar= */ true); - if (!s) - return log_oom(); - - /* We indent the option string by two spaces. We could set the minimum cell width and - * right-align for a similar result, but that'd be more work. This is only used for - * display. */ - bool shift_left = opt->short_code != 0 || FLAGS_SET(opt->flags, OPTION_HELP_ENTRY_VERBATIM); - const char *prefix = shift_left ? " " : " "; - _cleanup_free_ char *t = strjoin(prefix, s); - if (!t) - return log_oom(); - - r = table_add_many(table, TABLE_STRING, t); - if (r < 0) - return table_log_add_error(r); - - _cleanup_strv_free_ char **split = strv_split(opt->help, /* separators= */ NULL); - if (!split) - return log_oom(); - - r = table_add_many(table, TABLE_STRV_WRAPPED, split); - if (r < 0) - return table_log_add_error(r); - } - - assert(!table_isempty(table)); /* The namespace or group were not found. Something is off. */ - - table_set_header(table, false); - *ret = TAKE_PTR(table); - assert(opt - options < INT_MAX); - return opt - options; -} - int options_get_help_table_group( const Option options[], const Option options_end[], diff --git a/src/shared/options.h b/src/shared/options.h index 5b971879c8..83e72188b4 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -99,10 +99,6 @@ assert_cc(sizeof(Option) % sizeof(void*) == 0); /* This option is internal-only for now and not shown in --help */ \ OPTION_LONG("introspect-cli", NULL, /* help= */ NULL) -/* For programs which do not do option parsing, but check the argv array directly. */ -#define OPTION_COMMON_INTROSPECT_CLI_ENTRY \ - _OPTION_ENTRY(__COUNTER__, /* fl= */ 0, /* sc= */ 0, "introspect-cli", /* mv= */ NULL, /* d= */ 0u, /* h= */ NULL) - #define OPTION_COMMON_NO_PAGER \ OPTION_LONG("no-pager", NULL, "Do not start a pager") @@ -281,21 +277,6 @@ const Option* options_find_namespace( const Option options_end[], const char *namespace); -int _option_parser_get_help_table_full( - const Option options[], - const Option options_end[], - const char *namespace, - const char *group, - Table **ret); -#define option_parser_get_help_table_full(namespace, group, ret) \ - _option_parser_get_help_table_full(__start_SYSTEMD_OPTIONS, __stop_SYSTEMD_OPTIONS, namespace, group, ret) -#define option_parser_get_help_table_ns(ns, ret) \ - option_parser_get_help_table_full(ns, /* group= */ NULL, ret) -#define option_parser_get_help_table_group(group, ret) \ - option_parser_get_help_table_full(/* namespace= */ NULL, group, ret) -#define option_parser_get_help_table(ret) \ - option_parser_get_help_table_group(/* group= */ NULL, ret) - int options_get_help_table_group( const Option options[], const Option options_end[], diff --git a/src/shared/verbs.c b/src/shared/verbs.c index 920b4e0458..7d6c324259 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -617,9 +617,14 @@ int _command_print_verb_help( } if (verb->option_namespace) { + const Option *optns = options_find_namespace(options, options_end, verb->option_namespace); + if (!optns) + return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), + "Option namespace %s not found.", verb->option_namespace); + _cleanup_(table_unrefp) Table *table = NULL; - r = _option_parser_get_help_table_full(options, options_end, verb->option_namespace, - /* group= */ NULL, &table); + const char *group; + r = options_get_help_table_group(optns, options_end, /* option_groups= */ NULL, &table, &group); if (r < 0) return r; From 8313fbb859b9849fc07e9c54104fd13a6f0adc1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 27 Aug 2026 17:19:14 +0200 Subject: [PATCH 07/11] shared/verbs: drop help helpers obsoleted by the COMMAND conversions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All programs print their help through the COMMAND machinery now: - the VERB_COMMON_HELP() and VERB_COMMON_HELP_HIDDEN() macros which wired the help verb to a hand-written help() implementation are unused — everything uses the VERB_COMMON_HELP_AUTO*() family, - the verbs help table is only built internally, so the function becomes static, as the TODO promised, - help_abstract() and help_man_page_reference() are unused — the generated help renders the abstract via print_wrapped() and the man page links via print_man_links(). --- src/shared/help-util.c | 47 ++++-------------------------------------- src/shared/help-util.h | 4 ---- src/shared/verbs.c | 13 ++++-------- src/shared/verbs.h | 23 --------------------- 4 files changed, 8 insertions(+), 79 deletions(-) diff --git a/src/shared/help-util.c b/src/shared/help-util.c index 8968ba1377..268f51bb74 100644 --- a/src/shared/help-util.c +++ b/src/shared/help-util.c @@ -1,31 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include -#include "alloc-util.h" #include "ansi-color.h" #include "help-util.h" #include "path-util.h" -#include "pretty-print.h" -/* These are helpers for putting together --help texts in a uniform way with a common output style. Each - * function generates a separate part of the --help text: - * - * 1. help_cmdline() outputs a brief summary of the command line syntax. This shall be used at least once, - * in some cases multiple times. This generally comes first in the output. - * - * 2. help_abstract() outputs a brief prose abstract of the command, should carry a single line of text - * that gives the user a hint what this tool does. Use only once, right after the last help_cmdline(). - * - * 3. help_section() can be used to format multiple sections of the --help text. It should be used at least - * once for an "Options:" section, but can be used more than once, for programs with many - * options/verbs. The first invocation should come right after help_abstract(). - * - * 4. Finally, help_man_page_reference() adds a final line linking the man page of the tool. This should be - * used only once, and terminates the --help text. - * - * Switches and verbs documentation should be inserted after each help_section(). For that ideally use - * options.[ch] APIs. */ +/* These are helpers for putting together --help texts in a uniform way with a common output style: + * help_cmdline() outputs a brief summary of the command line syntax, and help_section() formats the + * headings of the individual sections of the --help text. */ void help_cmdline(const char *arguments) { const char *progname = @@ -41,16 +25,6 @@ void help_cmdline(const char *arguments) { arguments); } -void help_abstract(const char *text) { - assert(text); - - printf("\n%s%s%s%s\n", - ansi_highlight(), - ansi_add_italics(), - text, - ansi_normal()); -} - void help_section(const char *title) { assert(title); @@ -59,16 +33,3 @@ void help_section(const char *title) { title, ansi_normal()); } - -void help_man_page_reference(const char *page, const char *section) { - assert(page); - assert(section); - - /* Displaying --help texts generally should not fail, hence let's fall back to a simple string in - * case of OOM. */ - _cleanup_free_ char *link = NULL; - if (terminal_urlify_man(page, section, &link) < 0) - printf("\nSee the %s(%s) man page for details.\n", page, section); - else - printf("\nSee the %s for details.\n", link); -} diff --git a/src/shared/help-util.h b/src/shared/help-util.h index 380487213f..9b1072c720 100644 --- a/src/shared/help-util.h +++ b/src/shared/help-util.h @@ -3,8 +3,4 @@ void help_cmdline(const char *arguments); -void help_abstract(const char *text); - void help_section(const char *title); - -void help_man_page_reference(const char *page, const char *section); diff --git a/src/shared/verbs.c b/src/shared/verbs.c index 7d6c324259..d63e069bae 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -270,7 +270,7 @@ const Verb* _verbs_find_command( return NULL; } -int _verbs_get_help_table( +static int verbs_get_help_table( const Verb verbs[], const Verb verbs_end[], const char *group, @@ -292,13 +292,8 @@ int _verbs_get_help_table( const Verb *verb; for (verb = verbs; verb < verbs_end; verb++) { - /* Binaries with one or more VERB_COMMAND entries call this function - * through print_verb_option_help() which provides a verbs slice that - * starts after the VERB_COMMAND entry. - * - * In unconverted binaries, this function is called directly. */ - // TODO: make this function static after everything has been converted. - + /* This function is called through print_verb_option_help(), which provides a verbs + * slice that starts after the VERB_COMMAND entry. */ if (FLAGS_SET(verb->flags, VERB_COMMAND_MARKER)) break; @@ -364,7 +359,7 @@ static int print_verb_option_help( const char *group = FLAGS_SET(verb->flags, VERB_GROUP_MARKER) ? verb->verb : NULL; _cleanup_(table_unrefp) Table *table = NULL; - r = _verbs_get_help_table(verb, verbs_end, group, &table); + r = verbs_get_help_table(verb, verbs_end, group, &table); if (r < 0) return r; diff --git a/src/shared/verbs.h b/src/shared/verbs.h index 3453c5e72a..ccfc403bab 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -140,29 +140,6 @@ int _dispatch_verb(char **args, const Verb verbs[], const Verb verbs_end[], void #define dispatch_verb(args, userdata) \ _dispatch_verb(args, __start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, userdata) -int _verbs_get_help_table( - const Verb verbs[], - const Verb verbs_end[], - const char *group, - Table **ret); -#define verbs_get_help_table_group(group, ret) \ - _verbs_get_help_table(__start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, group, ret) -#define verbs_get_help_table(ret) \ - verbs_get_help_table_group(/* group= */ NULL, ret) - -#define _VERB_COMMON_HELP_IMPL(impl) \ - static int verb_help(int argc, char **argv, uintptr_t data, void *userdata) { \ - return impl(); \ - } - -#define VERB_COMMON_HELP(impl) \ - VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, "Show this help"); \ - _VERB_COMMON_HELP_IMPL(impl) - -#define VERB_COMMON_HELP_HIDDEN(impl) \ - VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, NULL); \ - _VERB_COMMON_HELP_IMPL(impl) - #define VERB_COMMON_HELP_AUTO_FULL(program, help) \ VERB_FULL(verb_help_auto, "help", NULL, VERB_ANY, VERB_ANY, 0, /* dat= */ (uintptr_t) program, /* help= */ help) #define VERB_COMMON_HELP_AUTO_PROGRAM(program) VERB_COMMON_HELP_AUTO_FULL(program, "Show this help") From d04d0b0627108b9c397ef3a766441c72794b1df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 28 Aug 2026 13:08:33 +0200 Subject: [PATCH 08/11] shared: fold help-util.c into verbs.c Only one trivial function needs to be non-static and that is not enough to justify two separate files. --- src/shared/help-util.c | 35 --------------------------------- src/shared/help-util.h | 6 ------ src/shared/meson.build | 1 - src/shared/verbs.c | 25 ++++++++++++++++++++++- src/shared/verbs.h | 2 ++ src/udev/udevadm-test-builtin.c | 1 - 6 files changed, 26 insertions(+), 44 deletions(-) delete mode 100644 src/shared/help-util.c delete mode 100644 src/shared/help-util.h diff --git a/src/shared/help-util.c b/src/shared/help-util.c deleted file mode 100644 index 268f51bb74..0000000000 --- a/src/shared/help-util.c +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include - -#include "ansi-color.h" -#include "help-util.h" -#include "path-util.h" - -/* These are helpers for putting together --help texts in a uniform way with a common output style: - * help_cmdline() outputs a brief summary of the command line syntax, and help_section() formats the - * headings of the individual sections of the --help text. */ - -void help_cmdline(const char *arguments) { - const char *progname = - last_path_component(secure_getenv("SYSTEMD_INVOKED_AS")) - ?: program_invocation_short_name; - - assert(arguments); - - printf("%s>%s %s %s\n", - ansi_grey(), - ansi_normal(), - progname, - arguments); -} - -void help_section(const char *title) { - assert(title); - - printf("\n%s%s:%s\n", - ansi_underline(), - title, - ansi_normal()); -} diff --git a/src/shared/help-util.h b/src/shared/help-util.h deleted file mode 100644 index 9b1072c720..0000000000 --- a/src/shared/help-util.h +++ /dev/null @@ -1,6 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -void help_cmdline(const char *arguments); - -void help_section(const char *title); diff --git a/src/shared/meson.build b/src/shared/meson.build index ee395ffbe1..a8cea5b931 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -95,7 +95,6 @@ shared_sources = files( 'gnutls-util.c', 'gpt.c', 'group-record.c', - 'help-util.c', 'hibernate-util.c', 'hostname-setup.c', 'hwdb-util.c', diff --git a/src/shared/verbs.c b/src/shared/verbs.c index d63e069bae..1a1a2d9e59 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -6,11 +6,11 @@ #include "env-util.h" #include "extract-word.h" #include "format-table.h" -#include "help-util.h" #include "log.h" #include "nulstr-util.h" #include "options.h" #include "pager.h" +#include "path-util.h" #include "pretty-print.h" #include "string-util.h" #include "strv.h" @@ -513,6 +513,29 @@ static const Verb* verbs_find_cmd_verbs(const Verb *cmdverb, const Verb verbs_en return actual; } +static void help_cmdline(const char *arguments) { + const char *progname = + last_path_component(secure_getenv("SYSTEMD_INVOKED_AS")) + ?: program_invocation_short_name; + + assert(arguments); + + printf("%s>%s %s %s\n", + ansi_grey(), + ansi_normal(), + progname, + arguments); +} + +void help_section(const char *title) { + assert(title); + + printf("\n%s%s:%s\n", + ansi_underline(), + title, + ansi_normal()); +} + int _command_print_help_full( const Verb verbs[], const Verb verbs_end[], diff --git a/src/shared/verbs.h b/src/shared/verbs.h index ccfc403bab..a1eb671490 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -140,6 +140,8 @@ int _dispatch_verb(char **args, const Verb verbs[], const Verb verbs_end[], void #define dispatch_verb(args, userdata) \ _dispatch_verb(args, __start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, userdata) +void help_section(const char *title); + #define VERB_COMMON_HELP_AUTO_FULL(program, help) \ VERB_FULL(verb_help_auto, "help", NULL, VERB_ANY, VERB_ANY, 0, /* dat= */ (uintptr_t) program, /* help= */ help) #define VERB_COMMON_HELP_AUTO_PROGRAM(program) VERB_COMMON_HELP_AUTO_FULL(program, "Show this help") diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c index cd8985cae7..384c53a8b7 100644 --- a/src/udev/udevadm-test-builtin.c +++ b/src/udev/udevadm-test-builtin.c @@ -4,7 +4,6 @@ #include "device-private.h" #include "device-util.h" -#include "help-util.h" #include "log.h" #include "options.h" #include "udev-builtin.h" From 05fb4c6e274589794f3737059dd640ca932fd505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 28 Aug 2026 13:10:00 +0200 Subject: [PATCH 09/11] Ignore $SYSTEMD_INVOKED_AS if empty Let's treat unset SYSTEMD_INVOKED_AS and SYSTEMD_INVOKED_AS='' the same. I encountered this when testing various --help outputs. It is often more convenient to override a variable with VAR= instead of removing it, in particular on the commandline. But we'd then print help with the program name missing. We look at the variable in two places, so adjust both as in $subject. --- src/basic/argv-util.c | 2 +- src/shared/verbs.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/basic/argv-util.c b/src/basic/argv-util.c index b0381a6096..2796d5c0b6 100644 --- a/src/basic/argv-util.c +++ b/src/basic/argv-util.c @@ -28,7 +28,7 @@ void save_argc_argv(int argc, char **argv) { } bool invoked_as(char *argv[], const char *token) { - const char *progname = secure_getenv("SYSTEMD_INVOKED_AS"); + const char *progname = empty_to_null(secure_getenv("SYSTEMD_INVOKED_AS")); if (!progname && argv) progname = argv[0]; diff --git a/src/shared/verbs.c b/src/shared/verbs.c index 1a1a2d9e59..d152fdb7d9 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -514,12 +514,12 @@ static const Verb* verbs_find_cmd_verbs(const Verb *cmdverb, const Verb verbs_en } static void help_cmdline(const char *arguments) { - const char *progname = - last_path_component(secure_getenv("SYSTEMD_INVOKED_AS")) - ?: program_invocation_short_name; - assert(arguments); + const char *progname = + empty_to_null(last_path_component(secure_getenv("SYSTEMD_INVOKED_AS"))) + ?: program_invocation_short_name; + printf("%s>%s %s %s\n", ansi_grey(), ansi_normal(), From 52c063713eb24020f4863a1e643296bac1cfe40d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 28 Aug 2026 14:42:38 +0200 Subject: [PATCH 10/11] shared/verbs: drop unused variable Also add a note that the function does not support multiple groups. I think that's fair, it doesn't make sense to generalize the functionality when we're unlikely to ever use that. (Verb options are only used by udevadm right now.) --- src/shared/options.c | 4 ++-- src/shared/verbs.c | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/shared/options.c b/src/shared/options.c index 905307f88d..f92eabc1e6 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -525,7 +525,6 @@ int options_get_help_table_group( int r; assert(ret); - assert(ret_group); _cleanup_(table_unrefp) Table *table = NULL; @@ -594,7 +593,8 @@ int options_get_help_table_group( } *ret = TAKE_PTR(table); - *ret_group = group; + if (ret_group) + *ret_group = group; assert(opt - options < INT_MAX); return opt - options; diff --git a/src/shared/verbs.c b/src/shared/verbs.c index d152fdb7d9..c79659467b 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -641,11 +641,16 @@ int _command_print_verb_help( "Option namespace %s not found.", verb->option_namespace); _cleanup_(table_unrefp) Table *table = NULL; - const char *group; - r = options_get_help_table_group(optns, options_end, /* option_groups= */ NULL, &table, &group); + r = options_get_help_table_group( + optns, options_end, + /* option_groups= */ NULL, + &table, + /* ret_group= */ NULL); if (r < 0) return r; + /* Note: we assume that only one option group is used for a verb. If this ever + * stops being true, the code here would need to be adjusted to loop over groups. */ help_section("Options"); r = table_print_or_warn(table); if (r < 0) From 852f1935601c797908decdf6e0506d3c4d59899a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 28 Aug 2026 14:50:00 +0200 Subject: [PATCH 11/11] shared/format-table: make one more function static --- src/shared/format-table.c | 2 +- src/shared/format-table.h | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 24dcddab1a..88d76b67f0 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -2172,7 +2172,7 @@ static const char* table_data_rgap_underline(const TableData *d) { return NULL; } -int table_data_requested_width(Table *table, size_t column, size_t *ret) { +static int table_data_requested_width(Table *table, size_t column, size_t *ret) { size_t width = 0; int r; diff --git a/src/shared/format-table.h b/src/shared/format-table.h index ebf447ed09..0907eeb698 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -144,8 +144,6 @@ int table_set_reverse(Table *t, size_t column, bool b); int table_hide_column_from_display_internal(Table *t, ...); #define table_hide_column_from_display(t, ...) table_hide_column_from_display_internal(t, __VA_ARGS__, SIZE_MAX) -int table_data_requested_width(Table *table, size_t column, size_t *ret); - int table_set_column_width(Table *t, size_t column, size_t width); int table_sync_all_column_widths(size_t column, Table **a);