From f57d2b5bdd65e099d8b5003cfc3c51f3106e01d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 09:45:11 +0200 Subject: [PATCH 01/12] hwdb: convert to the new option and verb parsers Verbs are reordered to show 'query' above 'update'. I think this makes more sense. Co-developed-by: Claude Opus 4.6 --- src/hwdb/hwdb.c | 110 ++++++++++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/src/hwdb/hwdb.c b/src/hwdb/hwdb.c index 3b407bb070..141387da4f 100644 --- a/src/hwdb/hwdb.c +++ b/src/hwdb/hwdb.c @@ -1,13 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" #include "build.h" +#include "format-table.h" #include "hwdb-util.h" #include "label-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pretty-print.h" #include "verbs.h" @@ -15,10 +15,14 @@ static const char *arg_hwdb_bin_dir = NULL; static const char *arg_root = NULL; static bool arg_strict = false; +VERB(verb_query, "query", "MODALIAS", 2, 2, 0, + "Query database and print result"); static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { return hwdb_query(argv[1], arg_root); } +VERB_NOARG(verb_update, "update", + "Update the hwdb database"); static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) { if (hwdb_bypass()) return 0; @@ -28,99 +32,93 @@ static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-hwdb", "8", &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; + + (void) table_sync_column_widths(0, verbs, options); + printf("%s [OPTIONS...] COMMAND ...\n\n" "%sUpdate or query the hardware database.%s\n" - "\nCommands:\n" - " update Update the hwdb database\n" - " query MODALIAS Query database and print result\n" - "\nOptions:\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -s --strict When updating, return non-zero exit value on any parsing error\n" - " --usr Generate in " UDEVLIBEXECDIR " instead of /etc/udev\n" - " -r --root=PATH Alternative root path in the filesystem\n" - "\nSee the %s for details.\n", + "\n%sCommands:%s\n", program_invocation_short_name, ansi_highlight(), ansi_normal(), - link); + 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("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_USR, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "usr", no_argument, NULL, ARG_USR }, - { "strict", no_argument, NULL, 's' }, - { "root", required_argument, NULL, 'r' }, - {} - }; - - int c; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "sr:h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_USR: - arg_hwdb_bin_dir = UDEVLIBEXECDIR; - break; - - case 's': + OPTION('s', "strict", NULL, + "When updating, return non-zero exit value on any parsing error"): arg_strict = true; break; - case 'r': - arg_root = optarg; + OPTION('r', "root", "PATH", "Alternative root path in the filesystem"): + arg_root = arg; break; - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION_LONG("usr", NULL, + "Generate in " UDEVLIBEXECDIR " instead of /etc/udev"): + arg_hwdb_bin_dir = UDEVLIBEXECDIR; + break; } + *ret_args = option_parser_get_args(&state); return 1; } -static int hwdb_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "update", 1, 1, 0, verb_update }, - { "query", 2, 2, 0, verb_query }, - {}, - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -128,7 +126,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return hwdb_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From b16a974ebe525ede40faa782516461df148ee2e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 10:19:17 +0200 Subject: [PATCH 02/12] export: convert to the new option and verb parsers --help is the same except for whitespace. Co-developed-by: Claude Opus 4.6 --- src/import/export.c | 129 ++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 72 deletions(-) diff --git a/src/import/export.c b/src/import/export.c index 389d5428bf..71f892feb0 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -13,9 +12,11 @@ #include "export-raw.h" #include "export-tar.h" #include "fd-util.h" +#include "format-table.h" #include "import-common.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "runtime-scope.h" #include "signal-util.h" #include "string-util.h" @@ -44,6 +45,8 @@ static void on_tar_finished(TarExport *export, int error, void *userdata) { sd_event_exit(event, ABS(error)); } +VERB(verb_export_tar, "tar", "NAME [FILE]", 2, 3, 0, + "Export a TAR image"); static int verb_export_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(tar_export_unrefp) TarExport *export = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -125,6 +128,8 @@ static void on_raw_finished(RawExport *export, int error, void *userdata) { sd_event_exit(event, ABS(error)); } +VERB(verb_export_raw, "raw", "NAME [FILE]", 2, 3, 0, + "Export a RAW image"); static int verb_export_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(raw_export_unrefp) RawExport *export = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -189,126 +194,106 @@ static int verb_export_raw(int argc, char *argv[], uintptr_t _data, void *userda } static int help(void) { - printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sExport disk images.%5$s\n" - "\n%2$sCommands:%3$s\n" - " tar NAME [FILE] Export a TAR image\n" - " raw NAME [FILE] Export a RAW image\n" - "\n%2$sOptions:%3$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --format=FORMAT Select format\n" - " --class=CLASS Select image class (machine, sysext, confext,\n" - " portable)\n" - " --system Operate in per-system mode\n" - " --user Operate in per-user mode\n", + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; + int r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "%sExport disk images.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - ansi_underline(), - ansi_normal(), 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; + return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_FORMAT, - ARG_CLASS, - ARG_SYSTEM, - ARG_USER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "format", required_argument, NULL, ARG_FORMAT }, - { "class", required_argument, NULL, ARG_CLASS }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - int c; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_FORMAT: - arg_compress = compression_from_string_harder(optarg); + OPTION_LONG("format", "FORMAT", "Select format"): + arg_compress = compression_from_string_harder(arg); if (arg_compress < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown format: %s", optarg); + "Unknown format: %s", arg); break; - case ARG_CLASS: - arg_class = image_class_from_string(optarg); + OPTION_LONG("class", "CLASS", + "Select image class (machine, sysext, confext, portable)"): + arg_class = image_class_from_string(arg); if (arg_class < 0) - return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); - + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg); break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Operate in per-system mode"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Operate in per-user mode"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_runtime_scope == RUNTIME_SCOPE_USER) arg_import_flags |= IMPORT_FOREIGN_UID; + *ret_args = option_parser_get_args(&state); return 1; } -static int export_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "tar", 2, 3, 0, verb_export_tar }, - { "raw", 2, 3, 0, verb_export_raw }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; (void) ignore_signals(SIGPIPE); - return export_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 94ec4b98438e65dc8741045c213ab21f2abe2bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 10:20:39 +0200 Subject: [PATCH 03/12] import-fs: convert to the new option and verb parsers --help is the same except for whitespace. Co-developed-by: Claude Opus 4.6 --- src/import/import-fs.c | 168 ++++++++++++++++------------------------- 1 file changed, 66 insertions(+), 102 deletions(-) diff --git a/src/import/import-fs.c b/src/import/import-fs.c index 4b2394d414..3605320300 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -11,6 +10,7 @@ #include "copy.h" #include "discover-image.h" #include "fd-util.h" +#include "format-table.h" #include "format-util.h" #include "import-common.h" #include "import-util.h" @@ -18,6 +18,7 @@ #include "log.h" #include "main-func.h" #include "mkdir-label.h" +#include "options.h" #include "parse-argument.h" #include "path-util.h" #include "ratelimit.h" @@ -107,6 +108,8 @@ static int progress_bytes(uint64_t nbytes, uint64_t bps, void *userdata) { return 0; } +VERB(verb_import_fs, "run", "DIRECTORY [NAME]", 2, 3, 0, + "Import a directory"); static int verb_import_fs(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(rm_rf_subvolume_and_freep) char *temp_path = NULL; _cleanup_(progress_info_free) ProgressInfo progress = { .bps = UINT64_MAX }; @@ -266,145 +269,115 @@ static int verb_import_fs(int argc, char *argv[], uintptr_t _data, void *userdat } static int help(void) { - printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sImport container images from a file system directories.%5$s\n" - "\n%2$sCommands:%3$s\n" - " run DIRECTORY [NAME] Import a directory\n" - "\n%2$sOptions:%3$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --force Force creation of image\n" - " --image-root=PATH Image root directory\n" - " --read-only Create a read-only image\n" - " --direct Import directly to specified directory\n" - " --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n" - " instead of a directory\n" - " --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n" - " subvolume\n" - " --sync=BOOL Controls whether to sync() before completing\n" - " --class=CLASS Select image class (machine, sysext, confext,\n" - " portable)\n" - " --system Operate in per-system mode\n" - " --user Operate in per-user mode\n", + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; + int r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "%sImport container images from file system directories.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - ansi_underline(), - ansi_normal(), 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; + return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_FORCE, - ARG_IMAGE_ROOT, - ARG_READ_ONLY, - ARG_DIRECT, - ARG_BTRFS_SUBVOL, - ARG_BTRFS_QUOTA, - ARG_SYNC, - ARG_CLASS, - ARG_SYSTEM, - ARG_USER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "force", no_argument, NULL, ARG_FORCE }, - { "image-root", required_argument, NULL, ARG_IMAGE_ROOT }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "direct", no_argument, NULL, ARG_DIRECT }, - { "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL }, - { "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA }, - { "sync", required_argument, NULL, ARG_SYNC }, - { "class", required_argument, NULL, ARG_CLASS }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - int c, r; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_FORCE: + OPTION_LONG("force", NULL, "Force creation of image"): arg_force = true; break; - case ARG_IMAGE_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root); + OPTION_LONG("image-root", "PATH", "Image root directory"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image_root); if (r < 0) return r; - break; - case ARG_READ_ONLY: + OPTION_LONG("read-only", NULL, "Create a read-only image"): arg_read_only = true; break; - case ARG_DIRECT: + OPTION_LONG("direct", NULL, "Import directly to specified directory"): arg_direct = true; break; - case ARG_BTRFS_SUBVOL: - r = parse_boolean_argument("--btrfs-subvol=", optarg, &arg_btrfs_subvol); + OPTION_LONG("btrfs-subvol", "BOOL", + "Controls whether to create a btrfs subvolume instead of a directory"): + r = parse_boolean_argument("--btrfs-subvol=", arg, &arg_btrfs_subvol); if (r < 0) return r; - break; - case ARG_BTRFS_QUOTA: - r = parse_boolean_argument("--btrfs-quota=", optarg, &arg_btrfs_quota); + OPTION_LONG("btrfs-quota", "BOOL", + "Controls whether to set up quota for btrfs subvolume"): + r = parse_boolean_argument("--btrfs-quota=", arg, &arg_btrfs_quota); if (r < 0) return r; - break; - case ARG_SYNC: - r = parse_boolean_argument("--sync=", optarg, &arg_sync); + OPTION_LONG("sync", "BOOL", "Controls whether to sync() before completing"): + r = parse_boolean_argument("--sync=", arg, &arg_sync); if (r < 0) return r; - break; - case ARG_CLASS: - arg_class = image_class_from_string(optarg); + OPTION_LONG("class", "CLASS", + "Select image class (machine, sysext, confext, portable)"): + arg_class = image_class_from_string(arg); if (arg_class < 0) - return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); - + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg); break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Operate in per-system mode"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Operate in per-user mode"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_image_root) { @@ -413,31 +386,22 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to pick image root: %m"); } + *ret_args = option_parser_get_args(&state); return 1; } -static int import_fs_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "run", 2, 3, 0, verb_import_fs }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return import_fs_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 2a94920ab242652c8f0da41846b50cd1df04ad83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 10:22:01 +0200 Subject: [PATCH 04/12] growfs: convert to the new option parser --help is the same except for whitespace and common option strings. Co-developed-by: Claude Opus 4.6 --- src/growfs/growfs.c | 64 ++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/src/growfs/growfs.c b/src/growfs/growfs.c index 8481257ab7..e7a0ca385b 100644 --- a/src/growfs/growfs.c +++ b/src/growfs/growfs.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "alloc-util.h" @@ -13,10 +12,12 @@ #include "devnum-util.h" #include "dissect-image.h" #include "fd-util.h" +#include "format-table.h" #include "format-util.h" #include "log.h" #include "main-func.h" #include "mountpoint-util.h" +#include "options.h" #include "pretty-print.h" #include "resize-fs.h" #include "string-util.h" @@ -132,67 +133,60 @@ static int maybe_resize_underlying_device( static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-growfs@.service", "8", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] /path/to/mountpoint\n\n" - "Grow filesystem or encrypted payload to device size.\n\n" - "Options:\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - " -n --dry-run Just print what would be done\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] /path/to/mountpoint\n\n" + "Grow filesystem or encrypted payload to device size.\n", + program_invocation_short_name); + + 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[]) { - enum { - ARG_VERSION = 0x100, - }; - - int c; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version" , no_argument, NULL, ARG_VERSION }, - { "dry-run", no_argument, NULL, 'n' }, - {} - }; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hn", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'n': + OPTION('n', "dry-run", NULL, "Just print what would be done"): arg_dry_run = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind + 1 != argc) + if (option_parser_get_n_args(&state) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s expects exactly one argument (the mount point).", program_invocation_short_name); - arg_target = argv[optind]; + arg_target = option_parser_get_args(&state)[0]; return 1; } From 1db7a4d3b365aa39ab500615a06ea8e1c54b7394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 10:23:25 +0200 Subject: [PATCH 05/12] hibernate-resume: convert to the new option parser --help is the same except for whitespace. Co-developed-by: Claude Opus 4.6 --- src/hibernate-resume/hibernate-resume.c | 80 ++++++++++++------------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/src/hibernate-resume/hibernate-resume.c b/src/hibernate-resume/hibernate-resume.c index 2c6eed3248..cc48bf22fb 100644 --- a/src/hibernate-resume/hibernate-resume.c +++ b/src/hibernate-resume/hibernate-resume.c @@ -1,20 +1,22 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" #include "build.h" #include "devnum-util.h" +#include "format-table.h" #include "hibernate-resume-config.h" #include "hibernate-util.h" #include "initrd-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-util.h" #include "pretty-print.h" #include "stat-util.h" #include "static-destruct.h" +#include "strv.h" static HibernateInfo arg_info = {}; static bool arg_clear = false; @@ -23,70 +25,59 @@ STATIC_DESTRUCTOR_REGISTER(arg_info, hibernate_info_done); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-hibernate-resume", "8", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] [DEVICE [OFFSET]]\n" - "\n%sInitiate resume from hibernation.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --clear Clear hibernation storage information from EFI and exit\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] [DEVICE [OFFSET]]\n\n" + "%sInitiate resume from hibernation.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + 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[]) { - - enum { - ARG_VERSION = 0x100, - ARG_CLEAR, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "clear", no_argument, NULL, ARG_CLEAR }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_CLEAR: + OPTION_LONG("clear", NULL, + "Clear hibernation storage information from EFI and exit"): arg_clear = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (argc > optind && arg_clear) + if (option_parser_get_n_args(&state) > 0 && arg_clear) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extraneous arguments specified with --clear, refusing."); + *ret_args = option_parser_get_args(&state); return 1; } @@ -130,11 +121,14 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - if (argc - optind > 2) + size_t n_args = strv_length(args); + + if (n_args > 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program expects zero, one, or two arguments."); umask(0022); @@ -146,7 +140,7 @@ static int run(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Not running in initrd, refusing to initiate resume from hibernation."); - if (argc <= optind) { + if (n_args == 0) { r = setup_hibernate_info_and_warn(); if (r <= 0) return r; @@ -154,12 +148,12 @@ static int run(int argc, char *argv[]) { if (arg_info.efi) (void) clear_efi_hibernate_location_and_warn(); } else { - arg_info.device = ASSERT_PTR(argv[optind]); + arg_info.device = ASSERT_PTR(args[0]); - if (argc - optind == 2) { - r = safe_atou64(argv[optind + 1], &arg_info.offset); + if (n_args == 2) { + r = safe_atou64(args[1], &arg_info.offset); if (r < 0) - return log_error_errno(r, "Failed to parse resume offset %s: %m", argv[optind + 1]); + return log_error_errno(r, "Failed to parse resume offset %s: %m", args[1]); } } From 53ea569d8af18e6c3de44573e81c6543d587c2fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 10:27:43 +0200 Subject: [PATCH 06/12] test-ndisc-send: convert to the new option parser Co-developed-by: Claude Opus 4.6 --- src/libsystemd-network/test-ndisc-send.c | 161 ++++++++--------------- 1 file changed, 58 insertions(+), 103 deletions(-) diff --git a/src/libsystemd-network/test-ndisc-send.c b/src/libsystemd-network/test-ndisc-send.c index 247cc0ec16..e0bbcfe6c2 100644 --- a/src/libsystemd-network/test-ndisc-send.c +++ b/src/libsystemd-network/test-ndisc-send.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-ndisc-protocol.h" @@ -17,6 +16,7 @@ #include "ndisc-option.h" #include "netlink-util.h" #include "network-common.h" +#include "options.h" #include "parse-util.h" #include "set.h" #include "string-util.h" @@ -72,197 +72,158 @@ static int parse_preference(const char *str) { } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_RA_HOP_LIMIT, - ARG_RA_MANAGED, - ARG_RA_OTHER, - ARG_RA_HOME_AGENT, - ARG_RA_PREFERENCE, - ARG_RA_LIFETIME, - ARG_RA_REACHABLE, - ARG_RA_RETRANSMIT, - ARG_NA_ROUTER, - ARG_NA_SOLICITED, - ARG_NA_OVERRIDE, - ARG_TARGET_ADDRESS, - ARG_REDIRECT_DESTINATION, - ARG_OPTION_SOURCE_LL, - ARG_OPTION_TARGET_LL, - ARG_OPTION_REDIRECTED_HEADER, - ARG_OPTION_MTU, - }; - - static const struct option options[] = { - { "version", no_argument, NULL, ARG_VERSION }, - { "interface", required_argument, NULL, 'i' }, - { "type", required_argument, NULL, 't' }, - { "dest", required_argument, NULL, 'd' }, - /* For Router Advertisement */ - { "hop-limit", required_argument, NULL, ARG_RA_HOP_LIMIT }, - { "managed", required_argument, NULL, ARG_RA_MANAGED }, - { "other", required_argument, NULL, ARG_RA_OTHER }, - { "home-agent", required_argument, NULL, ARG_RA_HOME_AGENT }, - { "preference", required_argument, NULL, ARG_RA_PREFERENCE }, - { "lifetime", required_argument, NULL, ARG_RA_LIFETIME }, - { "reachable-time", required_argument, NULL, ARG_RA_REACHABLE }, - { "retransmit-timer", required_argument, NULL, ARG_RA_RETRANSMIT }, - /* For Neighbor Advertisement */ - { "is-router", required_argument, NULL, ARG_NA_ROUTER }, - { "is-solicited", required_argument, NULL, ARG_NA_SOLICITED }, - { "is-override", required_argument, NULL, ARG_NA_OVERRIDE }, - /* For Neighbor Solicit, Neighbor Advertisement, and Redirect */ - { "target-address", required_argument, NULL, ARG_TARGET_ADDRESS }, - /* For Redirect */ - { "redirect-destination", required_argument, NULL, ARG_REDIRECT_DESTINATION }, - /* Options */ - { "source-ll-address", required_argument, NULL, ARG_OPTION_SOURCE_LL }, - { "target-ll-address", required_argument, NULL, ARG_OPTION_TARGET_LL }, - { "redirected-header", required_argument, NULL, ARG_OPTION_REDIRECTED_HEADER }, - { "mtu", required_argument, NULL, ARG_OPTION_MTU }, - {} - }; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "i:t:d:", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'i': - r = rtnl_resolve_interface_or_warn(&rtnl, optarg); + OPTION('i', "interface", "INTERFACE", "Network interface"): + r = rtnl_resolve_interface_or_warn(&rtnl, arg); if (r < 0) return r; arg_ifindex = r; break; - case 't': - r = parse_icmp6_type(optarg); + OPTION('t', "type", "TYPE", "ICMPv6 message type"): + r = parse_icmp6_type(arg); if (r < 0) return log_error_errno(r, "Failed to parse message type: %m"); arg_icmp6_type = r; break; - case 'd': - r = in_addr_from_string(AF_INET6, optarg, &arg_dest); + OPTION('d', "dest", "ADDRESS", "Destination address"): + r = in_addr_from_string(AF_INET6, arg, &arg_dest); if (r < 0) return log_error_errno(r, "Failed to parse destination address: %m"); if (!in6_addr_is_link_local(&arg_dest.in6)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "The destination address %s is not a link-local address.", optarg); + "The destination address %s is not a link-local address.", arg); break; - case ARG_RA_HOP_LIMIT: - r = safe_atou8(optarg, &arg_hop_limit); + OPTION_GROUP("Router Advertisement"): {} + + OPTION_LONG("hop-limit", "LIMIT", "Hop limit"): + r = safe_atou8(arg, &arg_hop_limit); if (r < 0) return log_error_errno(r, "Failed to parse hop limit: %m"); break; - case ARG_RA_MANAGED: - r = parse_boolean(optarg); + OPTION_LONG("managed", "BOOL", "Managed flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse managed flag: %m"); SET_FLAG(arg_ra_flags, ND_RA_FLAG_MANAGED, r); break; - case ARG_RA_OTHER: - r = parse_boolean(optarg); + OPTION_LONG("other", "BOOL", "Other flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse other flag: %m"); SET_FLAG(arg_ra_flags, ND_RA_FLAG_OTHER, r); break; - case ARG_RA_HOME_AGENT: - r = parse_boolean(optarg); + OPTION_LONG("home-agent", "BOOL", "Home-agent flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse home-agent flag: %m"); SET_FLAG(arg_ra_flags, ND_RA_FLAG_HOME_AGENT, r); break; - case ARG_RA_PREFERENCE: - r = parse_preference(optarg); + OPTION_LONG("preference", "PREF", "Preference"): + r = parse_preference(arg); if (r < 0) return log_error_errno(r, "Failed to parse preference: %m"); arg_preference = r; break; - case ARG_RA_LIFETIME: - r = parse_sec(optarg, &arg_lifetime); + OPTION_LONG("lifetime", "SECS", "Lifetime"): + r = parse_sec(arg, &arg_lifetime); if (r < 0) return log_error_errno(r, "Failed to parse lifetime: %m"); break; - case ARG_RA_REACHABLE: - r = parse_sec(optarg, &arg_reachable); + OPTION_LONG("reachable-time", "SECS", "Reachable time"): + r = parse_sec(arg, &arg_reachable); if (r < 0) return log_error_errno(r, "Failed to parse reachable time: %m"); break; - case ARG_RA_RETRANSMIT: - r = parse_sec(optarg, &arg_retransmit); + OPTION_LONG("retransmit-timer", "SECS", "Retransmit timer"): + r = parse_sec(arg, &arg_retransmit); if (r < 0) return log_error_errno(r, "Failed to parse retransmit timer: %m"); break; - case ARG_NA_ROUTER: - r = parse_boolean(optarg); + OPTION_GROUP("Neighbor Advertisement"): {} + + OPTION_LONG("is-router", "BOOL", "Router flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse is-router flag: %m"); SET_FLAG(arg_na_flags, ND_NA_FLAG_ROUTER, r); break; - case ARG_NA_SOLICITED: - r = parse_boolean(optarg); + OPTION_LONG("is-solicited", "BOOL", "Solicited flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse is-solicited flag: %m"); SET_FLAG(arg_na_flags, ND_NA_FLAG_SOLICITED, r); break; - case ARG_NA_OVERRIDE: - r = parse_boolean(optarg); + OPTION_LONG("is-override", "BOOL", "Override flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse is-override flag: %m"); SET_FLAG(arg_na_flags, ND_NA_FLAG_OVERRIDE, r); break; - case ARG_TARGET_ADDRESS: - r = in_addr_from_string(AF_INET6, optarg, &arg_target_address); + OPTION_GROUP("Neighbor Solicit/Advertisement and Redirect"): {} + + OPTION_LONG("target-address", "ADDRESS", "Target address"): + r = in_addr_from_string(AF_INET6, arg, &arg_target_address); if (r < 0) return log_error_errno(r, "Failed to parse target address: %m"); break; - case ARG_REDIRECT_DESTINATION: - r = in_addr_from_string(AF_INET6, optarg, &arg_redirect_destination); + OPTION_GROUP("Redirect"): {} + + OPTION_LONG("redirect-destination", "ADDRESS", "Redirect destination address"): + r = in_addr_from_string(AF_INET6, arg, &arg_redirect_destination); if (r < 0) return log_error_errno(r, "Failed to parse destination address: %m"); break; - case ARG_OPTION_SOURCE_LL: - r = parse_boolean(optarg); + OPTION_GROUP("NDisc Options"): {} + + OPTION_LONG("source-ll-address", "BOOL", "Include source link-layer address"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse source LL address option: %m"); arg_set_source_mac = r; break; - case ARG_OPTION_TARGET_LL: - r = parse_ether_addr(optarg, &arg_target_mac); + OPTION_LONG("target-ll-address", "ADDRESS", "Target link-layer address"): + r = parse_ether_addr(arg, &arg_target_mac); if (r < 0) return log_error_errno(r, "Failed to parse target LL address option: %m"); arg_set_target_mac = true; break; - case ARG_OPTION_REDIRECTED_HEADER: { + OPTION_LONG("redirected-header", "BASE64", "Redirected header (base64)"): { _cleanup_free_ void *p = NULL; size_t len; - r = unbase64mem(optarg, &p, &len); + r = unbase64mem(arg, &p, &len); if (r < 0) return log_error_errno(r, "Failed to parse redirected header: %m"); @@ -272,20 +233,14 @@ static int parse_argv(int argc, char *argv[]) { arg_redirected_header = TAKE_PTR(p); break; } - case ARG_OPTION_MTU: - r = safe_atou32(optarg, &arg_mtu); + + OPTION_LONG("mtu", "MTU", "MTU"): + r = safe_atou32(arg, &arg_mtu); if (r < 0) return log_error_errno(r, "Failed to parse MTU: %m"); arg_set_mtu = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } if (arg_ifindex <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--interface/-i option is mandatory."); From 58e2c1aa6dc57fec9b994a5572fc3d86abcd95f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 10:30:46 +0200 Subject: [PATCH 07/12] test-journal-append: convert to the new option parser The help string is adjusted/reworded. In particular, [a, b) is used as notation to show a closed-open range, instead of the unusual --- .../sd-journal/test-journal-append.c | 104 ++++++++---------- 1 file changed, 44 insertions(+), 60 deletions(-) diff --git a/src/libsystemd/sd-journal/test-journal-append.c b/src/libsystemd/sd-journal/test-journal-append.c index 605839169c..a07634a249 100644 --- a/src/libsystemd/sd-journal/test-journal-append.c +++ b/src/libsystemd/sd-journal/test-journal-append.c @@ -1,15 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include "chattr-util.h" +#include "format-table.h" #include "iovec-util.h" #include "journal-file-util.h" #include "log.h" #include "mmap-cache.h" +#include "options.h" #include "parse-util.h" #include "random-util.h" #include "rm-rf.h" @@ -146,98 +147,81 @@ int main(int argc, char *argv[]) { uint64_t iteration_step = 1; uint64_t corrupt_step = 31; bool sequential = false, run_one = false; - int c, r; + int r; test_setup_logging(LOG_DEBUG); - enum { - ARG_START_OFFSET = 0x1000, - ARG_ITERATIONS, - ARG_ITERATION_STEP, - ARG_CORRUPT_STEP, - ARG_SEQUENTIAL, - ARG_RUN_ONE, - }; + OptionParser state = { argc, argv }; + const char *arg; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "start-offset", required_argument, NULL, ARG_START_OFFSET }, - { "iterations", required_argument, NULL, ARG_ITERATIONS }, - { "iteration-step", required_argument, NULL, ARG_ITERATION_STEP }, - { "corrupt-step", required_argument, NULL, ARG_CORRUPT_STEP }, - { "sequential", no_argument, NULL, ARG_SEQUENTIAL }, - { "run-one", required_argument, NULL, ARG_RUN_ONE }, - {} - }; - - ASSERT_GE(argc, 0); - ASSERT_NOT_NULL(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + 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" - "Options:\n" - " --start-offset=OFFSET Offset at which to start corrupting the journal\n" - " (default: random offset is picked, unless\n" - " --sequential is used - in that case we use 0 + iteration)\n" - " --iterations=ITER Number of iterations to perform before exiting\n" - " (default: 100)\n" - " --iteration-step=STEP Iteration step (default: 1)\n" - " --corrupt-step=STEP Corrupt every n-th byte starting from OFFSET (default: 31)\n" - " --sequential Go through offsets sequentially instead of picking\n" - " a random one on each iteration. If set, we go through\n" - " offsets <0; ITER), or Date: Thu, 16 Apr 2026 10:30:51 +0200 Subject: [PATCH 08/12] keyutil: convert to the new option and verb parsers --help is reorderded slightly and argument specifications are moved to improve table formatting. Co-developed-by: Claude Opus 4.6 --- src/keyutil/keyutil.c | 184 ++++++++++++++++++------------------------ 1 file changed, 78 insertions(+), 106 deletions(-) diff --git a/src/keyutil/keyutil.c b/src/keyutil/keyutil.c index 516527aa2b..dcdd264226 100644 --- a/src/keyutil/keyutil.c +++ b/src/keyutil/keyutil.c @@ -1,16 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" #include "ask-password-api.h" #include "build.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "log.h" #include "main-func.h" #include "openssl-util.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "string-util.h" @@ -25,7 +25,7 @@ static char *arg_certificate_source = NULL; static CertificateSourceType arg_certificate_source_type = OPENSSL_CERTIFICATE_SOURCE_FILE; static char *arg_signature = NULL; static char *arg_content = NULL; -static char *arg_hash_algorithm = NULL; +static const char *arg_hash_algorithm = NULL; static char *arg_output = NULL; STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); @@ -38,164 +38,137 @@ STATIC_DESTRUCTOR_REGISTER(arg_output, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-keyutil", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sPerform various operations on private keys and certificates.%6$s\n" - "\n%3$sCommands:%4$s\n" - " validate Load and validate the given certificate and private key\n" - " extract-public Extract a public key\n" - " extract-certificate Extract a certificate\n" - " pkcs7 Generate a PKCS#7 signature\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --private-key=KEY Private key in PEM format\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used for signing\n" - " --certificate=PATH|URI\n" - " PEM certificate to use for signing, or a provider\n" - " specific designation if --certificate-source= is used\n" - " --certificate-source=file|provider:PROVIDER\n" - " Specify how to interpret the certificate from\n" - " --certificate=. Allows the certificate to be loaded\n" - " from an OpenSSL provider\n" - " --content=PATH Raw data content to embed in PKCS#7 signature\n" - " --signature=PATH PKCS#1 signature to embed in PKCS#7 signature\n" - " --hash-algorithm=ALGORITHM\n" - " Hash algorithm used to create the PKCS#1 signature\n" - " --output=PATH Where to write the PKCS#7 signature\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sPerform various operations on private keys and certificates.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), 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("\nSee the %s for details.\n", link); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - ARG_CERTIFICATE, - ARG_CERTIFICATE_SOURCE, - ARG_SIGNATURE, - ARG_CONTENT, - ARG_HASH_ALGORITHM, - ARG_OUTPUT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, - { "signature", required_argument, NULL, ARG_SIGNATURE }, - { "content", required_argument, NULL, ARG_CONTENT }, - { "hash-algorithm", required_argument, NULL, ARG_HASH_ALGORITHM }, - { "output", required_argument, NULL, ARG_OUTPUT }, - {} - }; - - int c, r; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_PRIVATE_KEY: - r = free_and_strdup_warn(&arg_private_key, optarg); + OPTION_LONG("private-key", "KEY", "Private key in PEM format"): + r = free_and_strdup_warn(&arg_private_key, arg); if (r < 0) return r; - break; - case ARG_PRIVATE_KEY_SOURCE: + OPTION_LONG("private-key-source", "SOURCE", + "Specify how to use KEY for --private-key= " + "(file, provider:PROVIDER, engine:ENGINE)"): r = parse_openssl_key_source_argument( - optarg, + arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) return r; - break; - case ARG_CERTIFICATE: - r = free_and_strdup_warn(&arg_certificate, optarg); + OPTION_LONG("certificate", "PATH|URI", + "PEM certificate to use for signing, " + "or a provider-specific designation if --certificate-source= is used"): + r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: + OPTION_LONG("certificate-source", "SOURCE", + "Specify how to interpret the certificate from --certificate= " + "(file, provider:PROVIDER)"): r = parse_openssl_certificate_source_argument( - optarg, + arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) return r; break; - case ARG_SIGNATURE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_signature); + OPTION_LONG("signature", "PATH", "PKCS#1 signature to embed in PKCS#7 signature"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_signature); if (r < 0) return r; - break; - case ARG_CONTENT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_content); + OPTION_LONG("content", "PATH", "Raw data content to embed in PKCS#7 signature"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_content); if (r < 0) return r; - break; - case ARG_HASH_ALGORITHM: - arg_hash_algorithm = optarg; + OPTION_LONG("hash-algorithm", "ALGORITHM", + "Hash algorithm used to create the PKCS#1 signature"): + arg_hash_algorithm = arg; break; - case ARG_OUTPUT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_output); + OPTION_LONG("output", "PATH", "Where to write the PKCS#7 signature"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_output); if (r < 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_private_key_source && !arg_certificate) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When using --private-key-source=, --certificate= must be specified."); + *ret_args = option_parser_get_args(&state); return 1; } +VERB_NOARG(verb_validate, "validate", + "Load and validate the given certificate and private key"); static int verb_validate(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(X509_freep) X509 *certificate = NULL; _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; @@ -251,6 +224,9 @@ static int verb_validate(int argc, char *argv[], uintptr_t _data, void *userdata return 0; } +VERB_NOARG(verb_extract_public, "extract-public", + "Extract a public key"); +VERB(verb_extract_public, "public", NULL, VERB_ANY, 1, 0, NULL); /* Deprecated alias */ static int verb_extract_public(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *public_key = NULL; int r; @@ -318,6 +294,8 @@ static int verb_extract_public(int argc, char *argv[], uintptr_t _data, void *us return 0; } +VERB_NOARG(verb_extract_certificate, "extract-certificate", + "Extract a certificate"); static int verb_extract_certificate(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(X509_freep) X509 *certificate = NULL; int r; @@ -345,6 +323,8 @@ static int verb_extract_certificate(int argc, char *argv[], uintptr_t _data, voi return 0; } +VERB(verb_pkcs7, "pkcs7", NULL, VERB_ANY, VERB_ANY, 0, + "Generate a PKCS#7 signature"); static int verb_pkcs7(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(X509_freep) X509 *certificate = NULL; _cleanup_free_ char *pkcs1 = NULL; @@ -429,24 +409,16 @@ static int verb_pkcs7(int argc, char *argv[], uintptr_t _data, void *userdata) { } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "validate", VERB_ANY, 1, 0, verb_validate }, - { "extract-public", VERB_ANY, 1, 0, verb_extract_public }, - { "public", VERB_ANY, 1, 0, verb_extract_public }, /* Deprecated but kept for backwards compat. */ - { "extract-certificate", VERB_ANY, 1, 0, verb_extract_certificate }, - { "pkcs7", VERB_ANY, VERB_ANY, 0, verb_pkcs7 }, - {} - }; int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From ecf09a39eda7312152de0edd8f3fd32e92924b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 11:42:50 +0200 Subject: [PATCH 09/12] machine-id-setup: convert to the new option parser --help is the same except for whitespace changes and that "option commands" are ordered in the usual style with "--help" first. Co-developed-by: Claude Opus 4.6 --- src/machine-id-setup/machine-id-setup-main.c | 126 +++++++++---------- 1 file changed, 59 insertions(+), 67 deletions(-) diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c index 70774dcdd1..4fb7082112 100644 --- a/src/machine-id-setup/machine-id-setup-main.c +++ b/src/machine-id-setup/machine-id-setup-main.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" #include "build.h" #include "dissect-image.h" +#include "format-table.h" #include "id128-util.h" #include "image-policy.h" #include "log.h" @@ -13,6 +13,7 @@ #include "machine-id-setup.h" #include "main-func.h" #include "mount-util.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" @@ -28,104 +29,95 @@ 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(); - printf("%1$s [OPTIONS...]\n" - "\n%2$sInitialize /etc/machine-id from a random source.%4$s\n" - "\n%3$sCommands:%4$s\n" - " --commit Commit transient ID\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sOptions:%4$s\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY Specify disk image dissection policy\n" - " --print Print used machine ID\n" - "\nSee the %5$s for details.\n", + 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_underline(), ansi_normal(), - link); + 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[]) { - - enum { - ARG_VERSION = 0x100, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_COMMIT, - ARG_PRINT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "commit", no_argument, NULL, ARG_COMMIT }, - { "print", no_argument, NULL, ARG_PRINT }, - {} - }; - - int c, r; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ROOT: - r = parse_path_argument(optarg, true, &arg_root); - if (r < 0) - return r; - break; - - case ARG_IMAGE: - r = parse_path_argument(optarg, false, &arg_image); - if (r < 0) - return r; - break; - - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); - if (r < 0) - return r; - break; - - case ARG_COMMIT: + OPTION_LONG("commit", NULL, "Commit transient ID"): arg_commit = true; break; - case ARG_PRINT: - arg_print = true; + OPTION_GROUP("Options"): {} + + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(arg, true, &arg_root); + if (r < 0) + return r; break; - case '?': - return -EINVAL; + OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): + r = parse_path_argument(arg, false, &arg_image); + if (r < 0) + return r; + break; - default: - assert_not_reached(); + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); + if (r < 0) + return r; + break; + + OPTION_LONG("print", NULL, "Print used machine ID"): + arg_print = true; + break; } - if (optind < argc) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extraneous arguments"); From f0c3bb330d0074f57758b681bf2a5916b94a5311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 11:51:14 +0200 Subject: [PATCH 10/12] modules-load: convert to the new option parser --help is the same except for common option strings and alignment. Co-developed-by: Claude Opus 4.6 --- src/modules-load/modules-load.c | 63 +++++++++++++++------------------ 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/src/modules-load/modules-load.c b/src/modules-load/modules-load.c index e8aeb67f1f..5435de4e2e 100644 --- a/src/modules-load/modules-load.c +++ b/src/modules-load/modules-load.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -15,10 +14,12 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "log.h" #include "macro.h" #include "main-func.h" #include "module-util.h" +#include "options.h" #include "ordered-set.h" #include "parse-util.h" #include "pretty-print.h" @@ -331,53 +332,46 @@ static unsigned determine_num_worker_threads(unsigned n_modules) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; + int r; if (terminal_urlify_man("systemd-modules-load.service", "8", &link) < 0) return log_oom(); - printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" - "Loads statically configured kernel modules.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" + "Loads statically configured kernel modules.\n\n", + program_invocation_short_name); + + 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[]) { - enum { - ARG_VERSION = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + + FOREACH_OPTION(&state, c, /* arg= */ NULL, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } @@ -395,7 +389,8 @@ static int run(int argc, char *argv[]) { char *module; int ret = 0, r; - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -407,9 +402,9 @@ static int run(int argc, char *argv[]) { if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - if (argc > optind) { - for (int i = optind; i < argc; i++) { - r = apply_file_from_path(argv[i], &module_set); + if (!strv_isempty(args)) { + STRV_FOREACH(i, args) { + r = apply_file_from_path(*i, &module_set); if (r < 0) RET_GATHER(ret, r); } From 4bd794d97417aa9d64e392ad0e3bc335a7aaa108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 11:57:17 +0200 Subject: [PATCH 11/12] mute-console: convert to the new option parser --help is identical except for whitespace. Co-developed-by: Claude Opus 4.6 --- src/mute-console/mute-console.c | 68 ++++++++++++--------------------- 1 file changed, 25 insertions(+), 43 deletions(-) diff --git a/src/mute-console/mute-console.c b/src/mute-console/mute-console.c index b18e79d622..b40f86fafb 100644 --- a/src/mute-console/mute-console.c +++ b/src/mute-console/mute-console.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" @@ -15,8 +14,10 @@ #include "bus-util.h" #include "daemon-util.h" #include "errno-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "printk-util.h" @@ -30,79 +31,60 @@ static bool arg_varlink = false; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-mute-console", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...]\n" - "\n%sMute status output to the console.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --kernel=BOOL Mute kernel log output\n" - " --pid1=BOOL Mute PID 1 status output\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n\n" + "%sMute status output to the console.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + 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[]) { - - enum { - ARG_VERSION = 0x100, - ARG_KERNEL, - ARG_PID1, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "kernel", required_argument, NULL, ARG_KERNEL }, - { "pid1", required_argument, NULL, ARG_PID1 }, - {} - }; - - int c, r; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_PID1: - r = parse_boolean_argument("--pid1=", optarg, &arg_mute_pid1); + OPTION_LONG("kernel", "BOOL", "Mute kernel log output"): + r = parse_boolean_argument("--kernel=", arg, &arg_mute_kernel); if (r < 0) return r; - break; - case ARG_KERNEL: - r = parse_boolean_argument("--kernel=", optarg, &arg_mute_kernel); + OPTION_LONG("pid1", "BOOL", "Mute PID 1 status output"): + r = parse_boolean_argument("--pid1=", arg, &arg_mute_pid1); if (r < 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); if (r < 0) From 91aa31c18cd0753bb29315a61042aa24402f11f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 22 Apr 2026 14:18:50 +0200 Subject: [PATCH 12/12] oomctl: fix coccinelle check --- src/oom/oomctl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/oom/oomctl.c b/src/oom/oomctl.c index 703b9c3f0e..51dfe1f1a4 100644 --- a/src/oom/oomctl.c +++ b/src/oom/oomctl.c @@ -89,6 +89,7 @@ static int verb_dump_state(int argc, char *argv[], uintptr_t _data, void *userda static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); OptionParser state = { argc, argv }; const char *arg;