From fb0143f1ceb03f1b8f8437f5787d5d402a0d2dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 29 Apr 2026 12:01:30 +0200 Subject: [PATCH 01/21] shared/options: add option_parser_get_help_table_ns() helper It'll be used in the next commit. --- src/run/run.c | 2 +- src/shared/options.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/run/run.c b/src/run/run.c index ce35b48fba..5827d91e1f 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -188,7 +188,7 @@ static int help_sudo_mode(void) { * sudo's short switches, hence please do not introduce new short switches unless they have a roughly * equivalent purpose on sudo. Use long options for everything private to run0. */ - r = option_parser_get_help_table_full("run0", /* group= */ NULL, &opts_table); + r = option_parser_get_help_table_ns("run0", &opts_table); if (r < 0) return r; diff --git a/src/shared/options.h b/src/shared/options.h index 5f55dd5d19..f50fbdb3cb 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -236,6 +236,8 @@ int _option_parser_get_help_table_full( Table **ret); #define option_parser_get_help_table_full(namespace, group, ret) \ _option_parser_get_help_table_full(ALIGN_PTR(__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) \ From c2c98878520e816c2d6535edebd3a5e233360ed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 26 Apr 2026 12:51:36 +0200 Subject: [PATCH 02/21] udevadm: convert option parsing to the new option parser Verb dispatch is left untouched for now. Co-developed-by: Claude Opus 4.7 (1M context) --- src/udev/udevadm.c | 70 +++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index 70ff213cb9..23e03d6fb0 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -1,13 +1,13 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include #include -#include "alloc-util.h" #include "argv-util.h" +#include "format-table.h" +#include "help-util.h" #include "label-util.h" #include "main-func.h" -#include "pretty-print.h" +#include "options.h" #include "udev-util.h" #include "udevadm.h" #include "udevd.h" @@ -28,60 +28,53 @@ static int help(void) { { "lock", "Lock a block device" }, }; - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("udevadm", "8", &link); + r = option_parser_get_help_table_ns("udevadm", &options); if (r < 0) - return log_oom(); + return r; - printf("%s [--help] [--version] [--debug] COMMAND [COMMAND OPTIONS]\n\n" - "Send control commands or test the device manager.\n\n" - "Commands:\n", - program_invocation_short_name); + help_cmdline("[OPTIONS…] COMMAND [COMMAND OPTIONS…]"); + help_abstract("Send control commands or test the device manager."); + help_section("Commands:"); FOREACH_ELEMENT(desc, short_descriptions) printf(" %-12s %s\n", (*desc)[0], (*desc)[1]); - printf("\nSee the %s for details.\n", link); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } -static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "debug", no_argument, NULL, 'd' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - {} - }; - int c; - +static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); + assert(remaining_args); - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; - while ((c = getopt_long(argc, argv, "+dhV", options, NULL)) >= 0) + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION, "udevadm" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'd': - log_set_max_level(LOG_DEBUG); - break; + OPTION_NAMESPACE("udevadm"): {} - case 'h': + OPTION_COMMON_HELP: return help(); - case 'V': + OPTION_COMMON_VERSION_WITH_HIDDEN_V: return print_version(); - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION('d', "debug", NULL, "Enable debug logging"): + log_set_max_level(LOG_DEBUG); + break; } + *remaining_args = option_parser_get_args(&opts); return 1; /* work to do */ } @@ -99,7 +92,7 @@ static int verb_help_main(int argc, char *argv[], uintptr_t _data, void *userdat return help(); } -static int udevadm_main(int argc, char *argv[]) { +static int udevadm_main(char **args) { static const Verb verbs[] = { { "cat", VERB_ANY, VERB_ANY, 0, verb_cat_main }, { "info", VERB_ANY, VERB_ANY, 0, verb_info_main }, @@ -118,10 +111,11 @@ static int udevadm_main(int argc, char *argv[]) { {} }; - return dispatch_verb(argc, argv, verbs, NULL); + return _dispatch_verb_with_args(args, verbs, verbs + ELEMENTSOF(verbs) - 1, NULL); } static int run(int argc, char *argv[]) { + char **args = NULL; int r; if (invoked_as(argv, "udevd")) @@ -130,7 +124,7 @@ static int run(int argc, char *argv[]) { (void) udev_parse_config(); log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -138,7 +132,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return udevadm_main(argc, argv); + return udevadm_main(args); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From 07fc22cd0384da5dc4a5d576cc90b7e02d38cfe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 27 Apr 2026 09:30:54 +0200 Subject: [PATCH 03/21] udevadm-cat: convert to OPTION macros --- src/udev/udevadm-cat.c | 88 +++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 48 deletions(-) diff --git a/src/udev/udevadm-cat.c b/src/udev/udevadm-cat.c index 9d94f5a86c..48ca720416 100644 --- a/src/udev/udevadm-cat.c +++ b/src/udev/udevadm-cat.c @@ -1,14 +1,14 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include - #include "alloc-util.h" #include "conf-files.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "static-destruct.h" -#include "strv.h" #include "udevadm.h" #include "udevadm-util.h" @@ -19,83 +19,75 @@ static bool arg_config = false; STATIC_DESTRUCTOR_REGISTER(arg_root, freep); static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("udevadm", "8", &link); + r = option_parser_get_help_table_ns("udevadm-cat", &options); if (r < 0) - return log_oom(); + return r; - printf("%s cat [OPTIONS] [FILE...]\n" - "\n%sShow udev rules files.%s\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --tldr Skip comments and empty lines\n" - " --config Show udev.conf rather than udev rules files\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + help_cmdline("cat [OPTIONS...] [FILE...]"); + help_abstract("Show udev rules files."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_ROOT = 0x100, - ARG_TLDR, - ARG_CONFIG, - }; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "tldr", no_argument, NULL, ARG_TLDR }, - { "config", no_argument, NULL, ARG_CONFIG }, - {} - }; - - int r, c; +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "hVN:", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "udevadm-cat" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'h': + + OPTION_NAMESPACE("udevadm-cat"): {} + + OPTION_COMMON_HELP: return help(); - case 'V': + + OPTION_COMMON_VERSION_WITH_HIDDEN_V: return print_version(); - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); + + OPTION_LONG("root", "PATH", + "Operate on an alternate filesystem root"): + r = parse_path_argument(opts.arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; - case ARG_TLDR: + + OPTION_LONG("tldr", NULL, + "Skip comments and empty lines"): arg_cat_flags = CAT_TLDR; break; - case ARG_CONFIG: + + OPTION_LONG("config", NULL, + "Show udev.conf rather than udev rules files"): arg_config = true; break; - case '?': - return -EINVAL; - default: - assert_not_reached(); } - if (arg_config && optind < argc) + if (arg_config && option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Combination of --config and FILEs is not supported."); + *remaining_args = option_parser_get_args(&opts); return 1; } int verb_cat_main(int argc, char *argv[], uintptr_t _data, void *userdata) { + char **args = NULL; int r; - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -107,7 +99,7 @@ int verb_cat_main(int argc, char *argv[], uintptr_t _data, void *userdata) { CLEANUP_ARRAY(files, n_files, conf_file_free_array); - r = search_rules_files(strv_skip(argv, optind), arg_root, &files, &n_files); + r = search_rules_files(args, arg_root, &files, &n_files); if (r < 0) return r; From 765fc4125b871394580e988192c497ad5625ae90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 29 Apr 2026 12:02:25 +0200 Subject: [PATCH 04/21] shared/options: add OPTION_COMMON_RESOLVE_NAMES --- src/shared/options.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/shared/options.h b/src/shared/options.h index f50fbdb3cb..a171f5f6a4 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -150,10 +150,13 @@ typedef struct Option { "(file, provider:PROVIDER)") /* A form used in udev code for compatibility. -V is accepted but not documented. */ -#define OPTION_COMMON_VERSION_WITH_HIDDEN_V \ - OPTION_COMMON_VERSION: {} \ +#define OPTION_COMMON_VERSION_WITH_HIDDEN_V \ + OPTION_COMMON_VERSION: {} \ OPTION_SHORT('V', NULL, /* help= */ NULL) +#define OPTION_COMMON_RESOLVE_NAMES \ + OPTION('N', "resolve-names", "MODE", \ + "When to resolve users and groups (early, late, or never)") /* This is magically mapped to the beginning and end of the section */ extern const Option __start_SYSTEMD_OPTIONS[]; From bfc07f83da24a883da843ae25fefd8376a1e217d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 20:28:00 +0200 Subject: [PATCH 05/21] udev: fix stale optarg use Fixup for 8623980980d3798f26f23aa56c1491cfd6ceb7b2. This didn't cause any problems until the conversion away from getopt_long(). --- src/udev/udevadm-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/udev/udevadm-util.c b/src/udev/udevadm-util.c index c30af47ff7..7e2420a77e 100644 --- a/src/udev/udevadm-util.c +++ b/src/udev/udevadm-util.c @@ -144,7 +144,7 @@ int parse_resolve_name_timing(const char *str, ResolveNameTiming *ret) { if (streq(str, "help")) return DUMP_STRING_TABLE(resolve_name_timing, ResolveNameTiming, _RESOLVE_NAME_TIMING_MAX); - ResolveNameTiming v = resolve_name_timing_from_string(optarg); + ResolveNameTiming v = resolve_name_timing_from_string(str); if (v < 0) return log_error_errno(v, "--resolve-names= must be 'early', 'late', or 'never'."); From 5893cf3dcf35f8017c15e34dacdc51a695163aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 10:48:04 +0200 Subject: [PATCH 06/21] udev: convert udev-config.c to OPTION macros --timeout-signal is now documented (fixup for e209926778267cbd3e09ed8137bf45b7f370aed0). Co-developed-by: Claude Opus 4.7 --- src/udev/udev-config.c | 134 ++++++++++++++++++----------------------- 1 file changed, 58 insertions(+), 76 deletions(-) diff --git a/src/udev/udev-config.c b/src/udev/udev-config.c index 17deadfe76..e234d6fe6d 100644 --- a/src/udev/udev-config.c +++ b/src/udev/udev-config.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include #include #include "conf-parser.h" @@ -8,10 +7,12 @@ #include "daemon-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "hashmap.h" +#include "help-util.h" #include "limits-util.h" +#include "options.h" #include "parse-util.h" -#include "pretty-print.h" #include "proc-cmdline.h" #include "serialize.h" #include "signal-util.h" @@ -149,110 +150,91 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat } static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("systemd-udevd.service", "8", &link); + r = option_parser_get_help_table_ns("udevd", &options); if (r < 0) - return log_oom(); + return r; - printf("%s [OPTIONS...]\n\n" - "Rule-based manager for device events and files.\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -d --daemon Detach and run in the background\n" - " -D --debug Enable debug output\n" - " -c --children-max=INT Set maximum number of workers\n" - " -e --exec-delay=SECONDS Seconds to wait before executing RUN=\n" - " -t --event-timeout=SECONDS Seconds to wait before terminating an event\n" - " -N --resolve-names=early|late|never\n" - " When to resolve users and groups\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + help_cmdline("[OPTIONS...]"); + help_abstract("Rule-based manager for device events and files."); + help_section("Options:"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-udevd.service", "8"); return 0; } static int parse_argv(int argc, char *argv[], UdevConfig *config) { - enum { - ARG_TIMEOUT_SIGNAL, - }; - - static const struct option options[] = { - { "daemon", no_argument, NULL, 'd' }, - { "debug", no_argument, NULL, 'D' }, - { "children-max", required_argument, NULL, 'c' }, - { "exec-delay", required_argument, NULL, 'e' }, - { "event-timeout", required_argument, NULL, 't' }, - { "resolve-names", required_argument, NULL, 'N' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "timeout-signal", required_argument, NULL, ARG_TIMEOUT_SIGNAL }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); assert(config); - while ((c = getopt_long(argc, argv, "c:de:Dt:N:hV", options, NULL)) >= 0) { + OptionParser opts = { argc, argv, OPTION_PARSER_NORMAL, "udevd" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'd': + OPTION_NAMESPACE("udevd"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + printf("%s\n", GIT_VERSION); + return 0; + + OPTION('d', "daemon", NULL, "Detach and run in the background"): arg_daemonize = true; break; - case 'c': - r = safe_atou(optarg, &config->children_max); - if (r < 0) - log_warning_errno(r, "Failed to parse --children-max= value '%s', ignoring: %m", optarg); - break; - case 'e': - r = parse_sec(optarg, &config->exec_delay_usec); - if (r < 0) - log_warning_errno(r, "Failed to parse --exec-delay= value '%s', ignoring: %m", optarg); - break; - case ARG_TIMEOUT_SIGNAL: - r = signal_from_string(optarg); - if (r <= 0) - log_warning_errno(r, "Failed to parse --timeout-signal= value '%s', ignoring: %m", optarg); - else - config->timeout_signal = r; - break; - case 't': - r = parse_sec(optarg, &config->timeout_usec); - if (r < 0) - log_warning_errno(r, "Failed to parse --event-timeout= value '%s', ignoring: %m", optarg); - break; - case 'D': + OPTION('D', "debug", NULL, "Enable debug output"): arg_debug = true; config->log_level = LOG_DEBUG; break; - case 'N': { - ResolveNameTiming t; - t = resolve_name_timing_from_string(optarg); + OPTION('c', "children-max", "INT", "Set maximum number of workers"): + r = safe_atou(opts.arg, &config->children_max); + if (r < 0) + log_warning_errno(r, "Failed to parse --children-max= value '%s', ignoring: %m", opts.arg); + break; + + OPTION('e', "exec-delay", "SECONDS", "Seconds to wait before executing RUN="): + r = parse_sec(opts.arg, &config->exec_delay_usec); + if (r < 0) + log_warning_errno(r, "Failed to parse --exec-delay= value '%s', ignoring: %m", opts.arg); + break; + + OPTION('t', "event-timeout", "SECONDS", "Seconds to wait before terminating an event"): + r = parse_sec(opts.arg, &config->timeout_usec); + if (r < 0) + log_warning_errno(r, "Failed to parse --event-timeout= value '%s', ignoring: %m", opts.arg); + break; + + OPTION_COMMON_RESOLVE_NAMES: { + ResolveNameTiming t = resolve_name_timing_from_string(opts.arg); if (t < 0) - log_warning("Invalid --resolve-names= value '%s', ignoring.", optarg); + log_warning("Invalid --resolve-names= value '%s', ignoring.", opts.arg); else config->resolve_name_timing = t; break; } - case 'h': - return help(); - case 'V': - printf("%s\n", GIT_VERSION); - return 0; - case '?': - return -EINVAL; - default: - assert_not_reached(); + OPTION_LONG("timeout-signal", "SIGNAL", "Signal used when terminating an event"): + r = signal_from_string(opts.arg); + if (r <= 0) + log_warning_errno(r, "Failed to parse --timeout-signal= value '%s', ignoring: %m", opts.arg); + else + config->timeout_signal = r; + break; } - } return 1; } From ce4746f228085950b455c2c0c55a3ab9d17ba89e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:00:00 +0200 Subject: [PATCH 07/21] udevadm-hwdb: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-hwdb.c | 93 +++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/src/udev/udevadm-hwdb.c b/src/udev/udevadm-hwdb.c index 5810efefd8..f4060673eb 100644 --- a/src/udev/udevadm-hwdb.c +++ b/src/udev/udevadm-hwdb.c @@ -1,10 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include +#include "format-table.h" +#include "help-util.h" #include "hwdb-util.h" #include "log.h" +#include "options.h" #include "udevadm.h" static const char *arg_test = NULL; @@ -14,65 +16,64 @@ static bool arg_update = false; static bool arg_strict = false; static int help(void) { - printf("%s hwdb [OPTIONS]\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -u --update Update the hardware database\n" - " -s --strict When updating, return non-zero exit value on any parsing error\n" - " --usr Generate in " UDEVLIBEXECDIR " instead of /etc/udev\n" - " -t --test=MODALIAS Query database and print result\n" - " -r --root=PATH Alternative root path in the filesystem\n\n" - "NOTE:\n" - "The sub-command 'hwdb' is deprecated, and is left for backwards compatibility.\n" - "Please use systemd-hwdb instead.\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + r = option_parser_get_help_table_ns("udevadm-hwdb", &options); + if (r < 0) + return r; + + help_cmdline("hwdb [OPTIONS]"); + help_abstract("Update or query the hardware database."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nNOTE:\n" + "The sub-command 'hwdb' is deprecated, and is left for backwards compatibility.\n" + "Please use systemd-hwdb instead.\n"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_USR = 0x100, - }; + assert(argc >= 0); + assert(argv); - static const struct option options[] = { - { "update", no_argument, NULL, 'u' }, - { "usr", no_argument, NULL, ARG_USR }, - { "strict", no_argument, NULL, 's' }, - { "test", required_argument, NULL, 't' }, - { "root", required_argument, NULL, 'r' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - {} - }; + OptionParser opts = { argc, argv, .namespace = "udevadm-hwdb" }; - int c; - - while ((c = getopt_long(argc, argv, "ust:r:Vh", options, NULL)) >= 0) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'u': + + OPTION_NAMESPACE("udevadm-hwdb"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('u', "update", NULL, "Update the hardware database"): arg_update = true; break; - 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 't': - arg_test = optarg; + + OPTION_LONG("usr", NULL, + "Generate in " UDEVLIBEXECDIR " instead of /etc/udev"): + arg_hwdb_bin_dir = UDEVLIBEXECDIR; break; - case 'r': - arg_root = optarg; + + OPTION('t', "test", "MODALIAS", "Query database and print result"): + arg_test = opts.arg; + break; + + OPTION('r', "root", "PATH", "Alternative root path in the filesystem"): + arg_root = opts.arg; break; - case 'V': - return print_version(); - case 'h': - return help(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } return 1; From 57516477999d09f27be689a01c79360b32fb370f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:01:28 +0200 Subject: [PATCH 08/21] udevadm-test-builtin: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-test-builtin.c | 70 ++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c index f17df9a7d5..31ac569957 100644 --- a/src/udev/udevadm-test-builtin.c +++ b/src/udev/udevadm-test-builtin.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include -#include - #include "device-private.h" #include "device-util.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "udev-builtin.h" #include "udevadm.h" #include "udevadm-util.h" @@ -15,51 +15,57 @@ static const char *arg_command = NULL; static const char *arg_syspath = NULL; static int help(void) { - printf("%s test-builtin [OPTIONS] COMMAND DEVPATH\n\n" - "Test a built-in command.\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -a --action=ACTION|help Set action string\n" - "\nCommands:\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + r = option_parser_get_help_table_ns("udevadm-test-builtin", &options); + if (r < 0) + return r; + + help_cmdline("test-builtin [OPTIONS] COMMAND DEVPATH"); + help_abstract("Test a built-in command."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_section("Commands:"); udev_builtin_list(); - return 0; } static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "action", required_argument, NULL, 'a' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - {} - }; + int r; - int r, c; + assert(argc >= 0); + assert(argv); - while ((c = getopt_long(argc, argv, "a:Vh", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "udevadm-test-builtin" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'a': - r = parse_device_action(optarg, &arg_action); + + OPTION_NAMESPACE("udevadm-test-builtin"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('a', "action", "ACTION|help", "Set action string"): + r = parse_device_action(opts.arg, &arg_action); if (r <= 0) return r; break; - case 'V': - return print_version(); - case 'h': - return help(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - if (argc != optind + 2) + if (option_parser_get_n_args(&opts) != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected two arguments: command string and device path."); - arg_command = ASSERT_PTR(argv[optind]); - arg_syspath = ASSERT_PTR(argv[optind+1]); + char **args = option_parser_get_args(&opts); + arg_command = ASSERT_PTR(args[0]); + arg_syspath = ASSERT_PTR(args[1]); return 1; } From 12e5e0e90381b79f1d58d86f9ec06973f6b9b9c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:16:04 +0200 Subject: [PATCH 09/21] udevadm-verify: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-verify.c | 91 +++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 52 deletions(-) diff --git a/src/udev/udevadm-verify.c b/src/udev/udevadm-verify.c index 6af7f06ab0..1ecc1fbee9 100644 --- a/src/udev/udevadm-verify.c +++ b/src/udev/udevadm-verify.c @@ -1,16 +1,17 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include #include #include "alloc-util.h" +#include "ansi-color.h" #include "conf-files.h" #include "errno-util.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "parse-argument.h" -#include "pretty-print.h" #include "static-destruct.h" -#include "strv.h" #include "udev-rules.h" #include "udevadm.h" #include "udevadm-util.h" @@ -23,81 +24,66 @@ static bool arg_style = true; STATIC_DESTRUCTOR_REGISTER(arg_root, freep); static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("udevadm", "8", &link); + r = option_parser_get_help_table_ns("udevadm-verify", &options); if (r < 0) - return log_oom(); + return r; - printf("%s verify [OPTIONS] [FILE...]\n" - "\n%sVerify udev rules files.%s\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -N --resolve-names=early|late|never When to resolve names\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --no-summary Do not show summary\n" - " --no-style Ignore style issues\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + help_cmdline("verify [OPTIONS] [FILE...]"); + help_abstract("Verify udev rules files."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_ROOT = 0x100, - ARG_NO_SUMMARY, - ARG_NO_STYLE, - }; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "resolve-names", required_argument, NULL, 'N' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "no-summary", no_argument, NULL, ARG_NO_SUMMARY }, - { "no-style", no_argument, NULL, ARG_NO_STYLE }, - {} - }; - - int r, c; +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "hVN:", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "udevadm-verify" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'h': + + OPTION_NAMESPACE("udevadm-verify"): {} + + OPTION_COMMON_HELP: return help(); - case 'V': + + OPTION('V', "version", NULL, "Show package version"): return print_version(); - case 'N': - r = parse_resolve_name_timing(optarg, &arg_resolve_name_timing); + + OPTION_COMMON_RESOLVE_NAMES: + r = parse_resolve_name_timing(opts.arg, &arg_resolve_name_timing); if (r <= 0) return r; break; - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); + + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(opts.arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; - case ARG_NO_SUMMARY: + + OPTION_LONG("no-summary", NULL, "Do not show summary"): arg_summary = false; break; - case ARG_NO_STYLE: + OPTION_LONG("no-style", NULL, "Ignore style issues"): arg_style = false; break; - - case '?': - return -EINVAL; - default: - assert_not_reached(); } + *remaining_args = option_parser_get_args(&opts); return 1; } @@ -158,9 +144,10 @@ static int verify_rules(UdevRules *rules, ConfFile * const *files, size_t n_file int verb_verify_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(udev_rules_freep) UdevRules *rules = NULL; + char **args = NULL; int r; - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -173,7 +160,7 @@ int verb_verify_main(int argc, char *argv[], uintptr_t _data, void *userdata) { CLEANUP_ARRAY(files, n_files, conf_file_free_array); - r = search_rules_files(strv_skip(argv, optind), arg_root, &files, &n_files); + r = search_rules_files(args, arg_root, &files, &n_files); if (r < 0) return r; From a517a6297e6dfc98cfd823baf692be3553088899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:17:14 +0200 Subject: [PATCH 10/21] udevadm-test: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-test.c | 89 +++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index f3ac39717e..a784133301 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -3,7 +3,6 @@ * Copyright © 2003-2004 Greg Kroah-Hartman */ -#include #include #include @@ -12,7 +11,10 @@ #include "alloc-util.h" #include "device-private.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "parse-argument.h" #include "static-destruct.h" #include "strv.h" @@ -33,55 +35,59 @@ static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; STATIC_DESTRUCTOR_REGISTER(arg_extra_rules_dir, strv_freep); static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; - printf("%s test [OPTIONS] DEVPATH\n\n" - "Test an event run.\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -a --action=ACTION|help Set action string\n" - " -N --resolve-names=early|late|never When to resolve names\n" - " -D --extra-rules-dir=DIR Also load rules from the directory\n" - " -v --verbose Show verbose logs\n" - " --json=pretty|short|off Generate JSON output\n", - program_invocation_short_name); + r = option_parser_get_help_table_ns("udevadm-test", &options); + if (r < 0) + return r; + help_cmdline("test [OPTIONS] DEVPATH"); + help_abstract("Test an event run."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_JSON = 0x100, - }; + int r; - static const struct option options[] = { - { "action", required_argument, NULL, 'a' }, - { "resolve-names", required_argument, NULL, 'N' }, - { "extra-rules-dir", required_argument, NULL, 'D' }, - { "verbose", no_argument, NULL, 'v' }, - { "json", required_argument, NULL, ARG_JSON }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - {} - }; + assert(argc >= 0); + assert(argv); - int r, c; + OptionParser opts = { argc, argv, .namespace = "udevadm-test" }; - while ((c = getopt_long(argc, argv, "a:N:D:vVh", options, NULL)) >= 0) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'a': - r = parse_device_action(optarg, &arg_action); + + OPTION_NAMESPACE("udevadm-test"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('a', "action", "ACTION|help", "Set action string"): + r = parse_device_action(opts.arg, &arg_action); if (r <= 0) return r; break; - case 'N': - r = parse_resolve_name_timing(optarg, &arg_resolve_name_timing); + + OPTION_COMMON_RESOLVE_NAMES: + r = parse_resolve_name_timing(opts.arg, &arg_resolve_name_timing); if (r <= 0) return r; break; - case 'D': { + + OPTION('D', "extra-rules-dir", "DIR", "Also load rules from the directory"): { _cleanup_free_ char *p = NULL; - r = parse_path_argument(optarg, /* suppress_root= */ false, &p); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &p); if (r < 0) return r; @@ -90,25 +96,20 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); break; } - case 'v': + + OPTION('v', "verbose", NULL, "Show verbose logs"): arg_verbose = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; - case 'V': - return print_version(); - case 'h': - return help(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - arg_syspath = argv[optind]; + char **args = option_parser_get_args(&opts); + arg_syspath = args[0]; if (!arg_syspath) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "syspath parameter missing."); From 5a2144f9bc78c41d670791f7c8063a4574296f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:22:32 +0200 Subject: [PATCH 11/21] udevadm-monitor: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-monitor.c | 91 ++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/src/udev/udevadm-monitor.c b/src/udev/udevadm-monitor.c index 6f33cc3710..0c165241a2 100644 --- a/src/udev/udevadm-monitor.c +++ b/src/udev/udevadm-monitor.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include - #include "sd-device.h" #include "sd-event.h" @@ -9,8 +7,11 @@ #include "device-monitor-private.h" #include "device-private.h" #include "device-util.h" +#include "format-table.h" #include "format-util.h" #include "hashmap.h" +#include "help-util.h" +#include "options.h" #include "set.h" #include "static-destruct.h" #include "string-util.h" @@ -99,60 +100,70 @@ static int setup_monitor(MonitorNetlinkGroup sender, sd_event *event, sd_device_ } static int help(void) { - printf("%s monitor [OPTIONS]\n\n" - "Listen to kernel and udev events.\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -p --property Print the event properties\n" - " -k --kernel Print kernel uevents\n" - " -u --udev Print udev events\n" - " -s --subsystem-match=SUBSYSTEM[/DEVTYPE] Filter events by subsystem\n" - " -t --tag-match=TAG Filter events by tag\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + r = option_parser_get_help_table_ns("udevadm-monitor", &options); + if (r < 0) + return r; + + help_cmdline("monitor [OPTIONS]"); + help_abstract("Listen to kernel and udev events."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "property", no_argument, NULL, 'p' }, - { "environment", no_argument, NULL, 'e' }, /* alias for -p */ - { "kernel", no_argument, NULL, 'k' }, - { "udev", no_argument, NULL, 'u' }, - { "subsystem-match", required_argument, NULL, 's' }, - { "tag-match", required_argument, NULL, 't' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - {} - }; + int r; - int r, c; + assert(argc >= 0); + assert(argv); - while ((c = getopt_long(argc, argv, "pekus:t:Vh", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "udevadm-monitor" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'p': - case 'e': + + OPTION_NAMESPACE("udevadm-monitor"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('e', "environment", NULL, /* help= */ NULL): {} /* hidden alias for -p */ + OPTION('p', "property", NULL, "Print the event properties"): arg_show_property = true; break; - case 'k': + + OPTION('k', "kernel", NULL, "Print kernel uevents"): arg_print_kernel = true; break; - case 'u': + + OPTION('u', "udev", NULL, "Print udev events"): arg_print_udev = true; break; - case 's': { + + OPTION('s', "subsystem-match", "SUBSYSTEM[/DEVTYPE]", + "Filter events by subsystem"): { _cleanup_free_ char *subsystem = NULL, *devtype = NULL; const char *slash; - slash = strchr(optarg, '/'); + slash = strchr(opts.arg, '/'); if (slash) { devtype = strdup(slash + 1); if (!devtype) return log_oom(); - subsystem = strndup(optarg, slash - optarg); + subsystem = strndup(opts.arg, slash - opts.arg); } else - subsystem = strdup(optarg); + subsystem = strdup(opts.arg); if (!subsystem) return log_oom(); @@ -165,20 +176,12 @@ static int parse_argv(int argc, char *argv[]) { TAKE_PTR(devtype); break; } - case 't': - r = set_put_strdup(&arg_tag_filter, optarg); + + OPTION('t', "tag-match", "TAG", "Filter events by tag"): + r = set_put_strdup(&arg_tag_filter, opts.arg); if (r < 0) return log_oom(); break; - - case 'V': - return print_version(); - case 'h': - return help(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } if (!arg_print_kernel && !arg_print_udev) { From ed2b92e2057a4bdae32ab7c81480a3a6c70e2487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:28:35 +0200 Subject: [PATCH 12/21] udevadm-settle: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-settle.c | 89 +++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/src/udev/udevadm-settle.c b/src/udev/udevadm-settle.c index b71759dc81..1292462d28 100644 --- a/src/udev/udevadm-settle.c +++ b/src/udev/udevadm-settle.c @@ -4,7 +4,6 @@ * Copyright © 2009 Scott James Remnant */ -#include #include #include "sd-bus.h" @@ -14,6 +13,9 @@ #include "alloc-util.h" #include "bus-util.h" +#include "format-table.h" +#include "help-util.h" +#include "options.h" #include "path-util.h" #include "string-util.h" #include "strv.h" @@ -28,60 +30,63 @@ static usec_t arg_timeout_usec = 120 * USEC_PER_SEC; static const char *arg_exists = NULL; static int help(void) { - printf("%s settle [OPTIONS]\n\n" - "Wait for pending udev events.\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -t --timeout=SEC Maximum time to wait for events\n" - " -E --exit-if-exists=FILE Stop waiting if file exists\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + r = option_parser_get_help_table_ns("udevadm-settle", &options); + if (r < 0) + return r; + + help_cmdline("settle [OPTIONS]"); + help_abstract("Wait for pending udev events."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "timeout", required_argument, NULL, 't' }, - { "exit-if-exists", required_argument, NULL, 'E' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - { "seq-start", required_argument, NULL, 's' }, /* removed */ - { "seq-end", required_argument, NULL, 'e' }, /* removed */ - { "quiet", no_argument, NULL, 'q' }, /* removed */ - {} - }; + int r; - int c, r; + assert(argc >= 0); + assert(argv); - while ((c = getopt_long(argc, argv, "t:E:Vhs:e:q", options, NULL)) >= 0) { + OptionParser opts = { argc, argv, .namespace = "udevadm-settle" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 't': - r = parse_sec(optarg, &arg_timeout_usec); - if (r < 0) - return log_error_errno(r, "Failed to parse timeout value '%s': %m", optarg); - break; - case 'E': - if (!path_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path: %s", optarg); - arg_exists = optarg; - break; - case 'V': - return print_version(); - case 'h': + OPTION_NAMESPACE("udevadm-settle"): {} + + OPTION_COMMON_HELP: return help(); - case 's': - case 'e': - case 'q': + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('t', "timeout", "SEC", "Maximum time to wait for events"): + r = parse_sec(opts.arg, &arg_timeout_usec); + if (r < 0) + return log_error_errno(r, "Failed to parse timeout value '%s': %m", opts.arg); + break; + + OPTION('E', "exit-if-exists", "FILE", "Stop waiting if file exists"): + if (!path_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path: %s", opts.arg); + + arg_exists = opts.arg; + break; + + OPTION('s', "seq-start", "ARG", NULL): {} /* removed */ + OPTION('e', "seq-end", "ARG", NULL): {} /* removed */ + OPTION('q', "quiet", NULL, NULL): /* removed */ return log_info_errno(SYNTHETIC_ERRNO(EINVAL), "Option -%c no longer supported.", - c); - case '?': - return -EINVAL; - default: - assert_not_reached(); + opts.opt->short_code); } - } return 1; } From a00de0b648aa9e62daef97c8d5461be8e8d55afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:31:51 +0200 Subject: [PATCH 13/21] udevadm-lock: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-lock.c | 91 ++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 56 deletions(-) diff --git a/src/udev/udevadm-lock.c b/src/udev/udevadm-lock.c index 483b64973d..cebce08007 100644 --- a/src/udev/udevadm-lock.c +++ b/src/udev/udevadm-lock.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include #include #include #include @@ -9,11 +8,14 @@ #include "device-util.h" #include "fd-util.h" #include "fdset.h" +#include "format-table.h" +#include "glyph-util.h" #include "hash-funcs.h" +#include "help-util.h" #include "lock-util.h" +#include "options.h" #include "path-util.h" #include "pidref.h" -#include "pretty-print.h" #include "process-util.h" #include "signal-util.h" #include "sort-util.h" @@ -33,70 +35,52 @@ STATIC_DESTRUCTOR_REGISTER(arg_backing, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_cmdline, strv_freep); static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("udevadm", "8", &link); + r = option_parser_get_help_table_ns("udevadm-lock", &options); if (r < 0) - return log_oom(); + return r; - printf("%s [OPTIONS...] COMMAND\n" - "%s [OPTIONS...] --print\n" - "\n%sLock a block device and run a command.%s\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -d --device=DEVICE Block device to lock\n" - " -b --backing=FILE File whose backing block device to lock\n" - " -t --timeout=SECS Block at most the specified time waiting for lock\n" - " -p --print Only show which block device the lock would be taken on\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + help_cmdline("lock [OPTIONS...] COMMAND"); + help_cmdline("lock [OPTIONS...] --print"); + help_abstract("Lock a block device and run a command."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "device", required_argument, NULL, 'd' }, - { "backing", required_argument, NULL, 'b' }, - { "timeout", required_argument, NULL, 't' }, - { "print", no_argument, NULL, 'p' }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; - while ((c = getopt_long(argc, argv, arg_print ? "hVd:b:t:p" : "+hVd:b:t:p", options, NULL)) >= 0) + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION, "udevadm-lock" }; + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_NAMESPACE("udevadm-lock"): {} + + OPTION_COMMON_HELP: return help(); - case 'V': + OPTION('V', "version", NULL, "Show package version"): return print_version(); - case 'd': - case 'b': { + OPTION('d', "device", "DEVICE", "Block device to lock"): {} /* fall through */ + OPTION('b', "backing", "FILE", "File whose backing block device to lock"): { _cleanup_free_ char *s = NULL; - char ***l = c == 'd' ? &arg_devices : &arg_backing; + char ***l = opts.opt->short_code == 'd' ? &arg_devices : &arg_backing; - r = path_make_absolute_cwd(optarg, &s); + r = path_make_absolute_cwd(opts.arg, &s); if (r < 0) - return log_error_errno(r, "Failed to make path '%s' absolute: %m", optarg); + return log_error_errno(r, "Failed to make path '%s' absolute: %m", opts.arg); path_simplify(s); @@ -107,31 +91,26 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 't': - r = parse_sec(optarg, &arg_timeout_usec); + OPTION('t', "timeout", "SECS", "Block at most the specified time waiting for lock"): + r = parse_sec(opts.arg, &arg_timeout_usec); if (r < 0) - return log_error_errno(r, "Failed to parse --timeout= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse --timeout= parameter: %s", opts.arg); break; - case 'p': + OPTION('p', "print", NULL, "Only show which block device the lock would be taken on"): arg_print = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + char **args = option_parser_get_args(&opts); if (arg_print) { - if (optind != argc) + if (!strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No arguments expected."); } else { - if (optind + 1 > argc) + if (strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments, command to execute."); - arg_cmdline = strv_copy(argv + optind); + arg_cmdline = strv_copy(args); if (!arg_cmdline) return log_oom(); } From 0257deff36d1045d31144976a9c4b58e90d7bc1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:33:16 +0200 Subject: [PATCH 14/21] udevadm-control: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-control.c | 141 +++++++++++++++---------------------- 1 file changed, 56 insertions(+), 85 deletions(-) diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c index 964f721731..ed586d5542 100644 --- a/src/udev/udevadm-control.c +++ b/src/udev/udevadm-control.c @@ -1,12 +1,13 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include -#include #include #include "creds-util.h" #include "errno-util.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "static-destruct.h" @@ -47,151 +48,121 @@ static bool arg_has_control_commands(void) { } static int help(void) { - printf("%s control OPTION\n\n" - "Control the udev daemon.\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -e --exit Instruct the daemon to cleanup and exit\n" - " -l --log-level=LEVEL Set the udev log level for the daemon\n" - " -s --stop-exec-queue Do not execute events, queue only\n" - " -S --start-exec-queue Execute events, flush queue\n" - " -R --reload Reload rules and databases\n" - " -p --property=KEY=VALUE Set a global property for all events\n" - " -m --children-max=N Maximum number of children\n" - " --ping Wait for udev to respond to a ping message\n" - " --trace=BOOL Enable/disable trace logging\n" - " --revert Revert previously set configurations\n" - " -t --timeout=SECONDS Maximum time to block for a reply\n" - " --load-credentials Load udev rules from credentials\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + r = option_parser_get_help_table_ns("udevadm-control", &options); + if (r < 0) + return r; + + help_cmdline("control OPTION"); + help_abstract("Control the udev daemon."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_PING = 0x100, - ARG_TRACE, - ARG_REVERT, - ARG_LOAD_CREDENTIALS, - }; - - static const struct option options[] = { - { "exit", no_argument, NULL, 'e' }, - { "log-level", required_argument, NULL, 'l' }, - { "log-priority", required_argument, NULL, 'l' }, /* for backward compatibility */ - { "stop-exec-queue", no_argument, NULL, 's' }, - { "start-exec-queue", no_argument, NULL, 'S' }, - { "reload", no_argument, NULL, 'R' }, - { "reload-rules", no_argument, NULL, 'R' }, /* alias for -R */ - { "property", required_argument, NULL, 'p' }, - { "env", required_argument, NULL, 'p' }, /* alias for -p */ - { "children-max", required_argument, NULL, 'm' }, - { "ping", no_argument, NULL, ARG_PING }, - { "trace", required_argument, NULL, ARG_TRACE }, - { "revert", no_argument, NULL, ARG_REVERT }, - { "timeout", required_argument, NULL, 't' }, - { "load-credentials", no_argument, NULL, ARG_LOAD_CREDENTIALS }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "el:sSRp:m:t:Vh", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "udevadm-control" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'e': + OPTION_NAMESPACE("udevadm-control"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('e', "exit", NULL, "Instruct the daemon to cleanup and exit"): arg_exit = true; break; - case 'l': - arg_log_level = log_level_from_string(optarg); + OPTION_LONG("log-priority", "LEVEL", NULL): {} /* backward compat alias for --log-level */ + OPTION('l', "log-level", "LEVEL", "Set the udev log level for the daemon"): + arg_log_level = log_level_from_string(opts.arg); if (arg_log_level < 0) - return log_error_errno(arg_log_level, "Failed to parse log level '%s': %m", optarg); + return log_error_errno(arg_log_level, "Failed to parse log level '%s': %m", opts.arg); break; - case 's': + OPTION('s', "stop-exec-queue", NULL, "Do not execute events, queue only"): arg_start_exec_queue = false; break; - case 'S': + OPTION('S', "start-exec-queue", NULL, "Execute events, flush queue"): arg_start_exec_queue = true; break; - case 'R': + OPTION_LONG("reload-rules", NULL, NULL): {} /* hidden alias for -R */ + OPTION('R', "reload", NULL, "Reload rules and databases"): arg_reload = true; break; - case 'p': - if (!strchr(optarg, '=')) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "expect = instead of '%s'", optarg); + OPTION_LONG("env", "KEY=VALUE", NULL): {} /* hidden alias for -p */ + OPTION('p', "property", "KEY=VALUE", "Set a global property for all events"): + if (!strchr(opts.arg, '=')) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "expect = instead of '%s'", opts.arg); - r = strv_extend(&arg_env, optarg); + r = strv_extend(&arg_env, opts.arg); if (r < 0) return log_error_errno(r, "Failed to extend environment: %m"); break; - case 'm': { + OPTION('m', "children-max", "N", "Maximum number of children"): { unsigned i; - r = safe_atou(optarg, &i); + r = safe_atou(opts.arg, &i); if (r < 0) - return log_error_errno(r, "Failed to parse maximum number of children '%s': %m", optarg); + return log_error_errno(r, "Failed to parse maximum number of children '%s': %m", opts.arg); arg_max_children = i; break; } - case ARG_PING: + OPTION_LONG("ping", NULL, "Wait for udev to respond to a ping message"): arg_ping = true; break; - case ARG_TRACE: - r = parse_boolean_argument("--trace=", optarg, NULL); + OPTION_LONG("trace", "BOOL", "Enable/disable trace logging"): + r = parse_boolean_argument("--trace=", opts.arg, NULL); if (r < 0) return r; arg_trace = r; break; - case ARG_REVERT: + OPTION_LONG("revert", NULL, "Revert previously set configurations"): arg_revert = true; break; - case 't': - r = parse_sec(optarg, &arg_timeout); + OPTION('t', "timeout", "SECONDS", "Maximum time to block for a reply"): + r = parse_sec(opts.arg, &arg_timeout); if (r < 0) - return log_error_errno(r, "Failed to parse timeout value '%s': %m", optarg); + return log_error_errno(r, "Failed to parse timeout value '%s': %m", opts.arg); break; - case ARG_LOAD_CREDENTIALS: + OPTION_LONG("load-credentials", NULL, "Load udev rules from credentials"): arg_load_credentials = true; break; - - case 'V': - return print_version(); - - case 'h': - return help(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_has_control_commands() && !arg_load_credentials) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No control command option is specified."); - if (optind < argc) + if (option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Extraneous argument: %s", argv[optind]); + "This subprogram takes no positional arguments."); return 1; } From 5656636a417cd859cd5717798719e220962097ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:34:15 +0200 Subject: [PATCH 15/21] udevadm-wait: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-wait.c | 92 +++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 49 deletions(-) diff --git a/src/udev/udevadm-wait.c b/src/udev/udevadm-wait.c index 0e285fc36b..a361bac61a 100644 --- a/src/udev/udevadm-wait.c +++ b/src/udev/udevadm-wait.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include -#include #include #include @@ -10,7 +8,10 @@ #include "device-monitor-private.h" #include "device-util.h" #include "event-util.h" +#include "format-table.h" #include "fs-util.h" +#include "help-util.h" +#include "options.h" #include "parse-util.h" #include "path-util.h" #include "static-destruct.h" @@ -297,79 +298,72 @@ static int setup_periodic_timer(sd_event *event) { } static int help(void) { - printf("%s wait [OPTIONS] DEVICE [DEVICE…]\n\n" - "Wait for devices or device symlinks being created.\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -t --timeout=SEC Maximum time to wait for the device\n" - " --initialized=BOOL Wait for devices being initialized by systemd-udevd\n" - " --removed Wait for devices being removed\n" - " --settle Also wait for all queued events being processed\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + r = option_parser_get_help_table_ns("udevadm-wait", &options); + if (r < 0) + return r; + + help_cmdline("wait [OPTIONS] DEVICE [DEVICE…]"); + help_abstract("Wait for devices or device symlinks being created."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_INITIALIZED = 0x100, - ARG_REMOVED, - ARG_SETTLE, - }; + int r; - static const struct option options[] = { - { "timeout", required_argument, NULL, 't' }, - { "initialized", required_argument, NULL, ARG_INITIALIZED }, - { "removed", no_argument, NULL, ARG_REMOVED }, - { "settle", no_argument, NULL, ARG_SETTLE }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - {} - }; + assert(argc >= 0); + assert(argv); - int c, r; + OptionParser opts = { argc, argv, .namespace = "udevadm-wait" }; - while ((c = getopt_long(argc, argv, "t:hV", options, NULL)) >= 0) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 't': - r = parse_sec(optarg, &arg_timeout_usec); + + OPTION_NAMESPACE("udevadm-wait"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('t', "timeout", "SEC", "Maximum time to wait for the device"): + r = parse_sec(opts.arg, &arg_timeout_usec); if (r < 0) - return log_error_errno(r, "Failed to parse -t/--timeout= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse -t/--timeout= parameter: %s", opts.arg); break; - case ARG_INITIALIZED: - r = parse_boolean(optarg); + OPTION_LONG("initialized", "BOOL", + "Wait for devices being initialized by systemd-udevd"): + r = parse_boolean(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --initialized= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse --initialized= parameter: %s", opts.arg); arg_wait_until = r ? WAIT_UNTIL_INITIALIZED : WAIT_UNTIL_ADDED; break; - case ARG_REMOVED: + OPTION_LONG("removed", NULL, "Wait for devices being removed"): arg_wait_until = WAIT_UNTIL_REMOVED; break; - case ARG_SETTLE: + OPTION_LONG("settle", NULL, "Also wait for all queued events being processed"): arg_settle = true; break; - - case 'V': - return print_version(); - - case 'h': - return help(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind >= argc) + char **args = option_parser_get_args(&opts); + if (strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments, expected at least one device path or device symlink."); - arg_devices = strv_copy(argv + optind); + arg_devices = strv_copy(args); if (!arg_devices) return log_oom(); From 222b417494092ed677ebfd2349605d52ae00d87d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:36:00 +0200 Subject: [PATCH 16/21] udevadm-trigger: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-trigger.c | 232 +++++++++++++++---------------------- 1 file changed, 95 insertions(+), 137 deletions(-) diff --git a/src/udev/udevadm-trigger.c b/src/udev/udevadm-trigger.c index afa6a84262..62ccba37c5 100644 --- a/src/udev/udevadm-trigger.c +++ b/src/udev/udevadm-trigger.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include #include #include "sd-device.h" @@ -10,7 +9,10 @@ #include "device-enumerator-private.h" #include "device-private.h" #include "device-util.h" +#include "format-table.h" +#include "help-util.h" #include "id128-util.h" +#include "options.h" #include "set.h" #include "static-destruct.h" #include "string-table.h" @@ -320,214 +322,170 @@ static int setup_matches(sd_device_enumerator *e) { } static int help(void) { - printf("%s trigger [OPTIONS] DEVPATH\n\n" - "Request events from the kernel.\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -v --verbose Print the list of devices while running\n" - " -n --dry-run Do not actually trigger the events\n" - " -q --quiet Suppress error logging in triggering events\n" - " -t --type= Type of events to trigger\n" - " devices sysfs devices (default)\n" - " subsystems sysfs subsystems and drivers\n" - " all sysfs devices, subsystems, and drivers\n" - " -c --action=ACTION|help Event action value, default is \"change\"\n" - " -s --subsystem-match=SUBSYSTEM Trigger devices from a matching subsystem\n" - " -S --subsystem-nomatch=SUBSYSTEM Exclude devices from a matching subsystem\n" - " -a --attr-match=FILE[=VALUE] Trigger devices with a matching attribute\n" - " -A --attr-nomatch=FILE[=VALUE] Exclude devices with a matching attribute\n" - " -p --property-match=KEY=VALUE Trigger devices with a matching property\n" - " -g --tag-match=TAG Trigger devices with a matching tag\n" - " -y --sysname-match=NAME Trigger devices with this /sys path\n" - " --name-match=NAME Trigger devices with this /dev name\n" - " -b --parent-match=NAME Trigger devices with that parent device\n" - " --include-parents Trigger parent devices of found devices\n" - " --initialized-match Trigger devices that are already initialized\n" - " --initialized-nomatch Trigger devices that are not initialized yet\n" - " -w --settle Wait for the triggered events to complete\n" - " --wait-daemon[=SECONDS] Wait for udevd daemon to be initialized\n" - " before triggering uevents\n" - " --uuid Print synthetic uevent UUID\n" - " --prioritized-subsystem=SUBSYSTEM[,SUBSYSTEM…]\n" - " Trigger devices from a matching subsystem first\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + r = option_parser_get_help_table_ns("udevadm-trigger", &options); + if (r < 0) + return r; + + help_cmdline("trigger [OPTIONS] DEVPATH"); + help_abstract("Request events from the kernel."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_NAME = 0x100, - ARG_PING, - ARG_UUID, - ARG_PRIORITIZED_SUBSYSTEM, - ARG_INITIALIZED_MATCH, - ARG_INITIALIZED_NOMATCH, - ARG_INCLUDE_PARENTS, - }; - - static const struct option options[] = { - { "verbose", no_argument, NULL, 'v' }, - { "dry-run", no_argument, NULL, 'n' }, - { "quiet", no_argument, NULL, 'q' }, - { "type", required_argument, NULL, 't' }, - { "action", required_argument, NULL, 'c' }, - { "subsystem-match", required_argument, NULL, 's' }, - { "subsystem-nomatch", required_argument, NULL, 'S' }, - { "attr-match", required_argument, NULL, 'a' }, - { "attr-nomatch", required_argument, NULL, 'A' }, - { "property-match", required_argument, NULL, 'p' }, - { "tag-match", required_argument, NULL, 'g' }, - { "sysname-match", required_argument, NULL, 'y' }, - { "name-match", required_argument, NULL, ARG_NAME }, - { "parent-match", required_argument, NULL, 'b' }, - { "include-parents", no_argument, NULL, ARG_INCLUDE_PARENTS }, - { "initialized-match", no_argument, NULL, ARG_INITIALIZED_MATCH }, - { "initialized-nomatch", no_argument, NULL, ARG_INITIALIZED_NOMATCH }, - { "settle", no_argument, NULL, 'w' }, - { "wait-daemon", optional_argument, NULL, ARG_PING }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - { "uuid", no_argument, NULL, ARG_UUID }, - { "prioritized-subsystem", required_argument, NULL, ARG_PRIORITIZED_SUBSYSTEM }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "vnqt:c:s:S:a:A:p:g:y:b:wVh", options, NULL)) >= 0) { + OptionParser opts = { argc, argv, .namespace = "udevadm-trigger" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case 'v': + + OPTION_NAMESPACE("udevadm-trigger"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('v', "verbose", NULL, "Print the list of devices while running"): arg_verbose = true; break; - case 'n': + OPTION('n', "dry-run", NULL, "Do not actually trigger the events"): arg_dry_run = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress error logging in triggering events"): arg_quiet = true; break; - case 't': - arg_scan_type = scan_type_from_string(optarg); + OPTION('t', "type", "TYPE", "Type of sysfs events to trigger:"): {} + OPTION_HELP_VERBATIM(" devices", "- devices (default)"): {} + OPTION_HELP_VERBATIM(" subsystems", "- subsystems and drivers"): {} + OPTION_HELP_VERBATIM(" all", "- devices, subsystems, and drivers"): + arg_scan_type = scan_type_from_string(opts.arg); if (arg_scan_type < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown type --type=%s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown type --type=%s", opts.arg); break; - case 'c': - r = parse_device_action(optarg, &arg_action); + OPTION('c', "action", "ACTION|help", "Event action value, default is \"change\""): + r = parse_device_action(opts.arg, &arg_action); if (r <= 0) return r; break; - case 's': - r = strv_extend(&arg_subsystem_match, optarg); + OPTION('s', "subsystem-match", "SUBSYSTEM", + "Trigger devices from a matching subsystem"): + r = strv_extend(&arg_subsystem_match, opts.arg); if (r < 0) return log_oom(); break; - case 'S': - r = strv_extend(&arg_subsystem_nomatch, optarg); + OPTION('S', "subsystem-nomatch", "SUBSYSTEM", + "Exclude devices from a matching subsystem"): + r = strv_extend(&arg_subsystem_nomatch, opts.arg); if (r < 0) return log_oom(); break; - case 'a': - r = strv_extend(&arg_attr_match, optarg); + OPTION('a', "attr-match", "FILE[=VALUE]", + "Trigger devices with a matching attribute"): + r = strv_extend(&arg_attr_match, opts.arg); if (r < 0) return log_oom(); break; - case 'A': - r = strv_extend(&arg_attr_nomatch, optarg); + OPTION('A', "attr-nomatch", "FILE[=VALUE]", + "Exclude devices with a matching attribute"): + r = strv_extend(&arg_attr_nomatch, opts.arg); if (r < 0) return log_oom(); break; - case 'p': - r = strv_extend(&arg_property_match, optarg); + OPTION('p', "property-match", "KEY=VALUE", + "Trigger devices with a matching property"): + r = strv_extend(&arg_property_match, opts.arg); if (r < 0) return log_oom(); break; - case 'g': - r = strv_extend(&arg_tag_match, optarg); + OPTION('g', "tag-match", "TAG", "Trigger devices with a matching tag"): + r = strv_extend(&arg_tag_match, opts.arg); if (r < 0) return log_oom(); break; - case 'y': - r = strv_extend(&arg_sysname_match, optarg); + OPTION('y', "sysname-match", "NAME", "Trigger devices with this /sys path"): + r = strv_extend(&arg_sysname_match, opts.arg); if (r < 0) return log_oom(); break; - case 'b': - r = strv_extend(&arg_parent_match, optarg); + OPTION_LONG("name-match", "NAME", "Trigger devices with this /dev name"): + r = strv_extend(&arg_name_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_INCLUDE_PARENTS: + OPTION('b', "parent-match", "NAME", "Trigger devices with that parent device"): + r = strv_extend(&arg_parent_match, opts.arg); + if (r < 0) + return log_oom(); + break; + + OPTION_LONG("include-parents", NULL, "Trigger parent devices of found devices"): arg_include_parents = true; break; - case 'w': - arg_settle = true; - break; - - case ARG_NAME: - r = strv_extend(&arg_name_match, optarg); - if (r < 0) - return log_oom(); - break; - - case ARG_PING: - arg_ping = true; - if (optarg) { - r = parse_sec(optarg, &arg_ping_timeout_usec); - if (r < 0) - log_error_errno(r, "Failed to parse timeout value '%s', ignoring: %m", optarg); - } - break; - - case ARG_UUID: - arg_uuid = true; - break; - - case ARG_PRIORITIZED_SUBSYSTEM: - r = strv_split_and_extend(&arg_prioritized_subsystems, optarg, ",", /* filter_duplicates= */ false); - if (r < 0) - return log_oom(); - break; - - case ARG_INITIALIZED_MATCH: + OPTION_LONG("initialized-match", NULL, + "Trigger devices that are already initialized"): arg_initialized_match = MATCH_INITIALIZED_YES; break; - case ARG_INITIALIZED_NOMATCH: + OPTION_LONG("initialized-nomatch", NULL, + "Trigger devices that are not initialized yet"): arg_initialized_match = MATCH_INITIALIZED_NO; break; - case 'V': - return print_version(); + OPTION('w', "settle", NULL, "Wait for the triggered events to complete"): + arg_settle = true; + break; - case 'h': - return help(); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "wait-daemon", "SECONDS", + "Wait for udevd daemon to be initialized before triggering uevents"): + arg_ping = true; + if (opts.arg) { + r = parse_sec(opts.arg, &arg_ping_timeout_usec); + if (r < 0) + log_error_errno(r, "Failed to parse timeout value '%s', ignoring: %m", opts.arg); + } + break; - case '?': - return -EINVAL; + OPTION_LONG("uuid", NULL, "Print synthetic uevent UUID"): + arg_uuid = true; + break; - default: - assert_not_reached(); + OPTION_LONG("prioritized-subsystem", "SUBSYSTEM[,SUBSYSTEM…]", + "Trigger devices from a matching subsystem first"): + r = strv_split_and_extend(&arg_prioritized_subsystems, opts.arg, ",", /* filter_duplicates= */ false); + if (r < 0) + return log_oom(); + break; } - } - r = strv_extend_strv(&arg_devices, argv + optind, /* filter_duplicates= */ false); + r = strv_extend_strv(&arg_devices, option_parser_get_args(&opts), /* filter_duplicates= */ false); if (r < 0) return log_error_errno(r, "Failed to build argument list: %m"); From c86ba4e037e1879e0bc7e93449b3fdfe4939dde9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 11:40:54 +0200 Subject: [PATCH 17/21] udevadm-info: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm-info.c | 294 ++++++++++++++++------------------------ 1 file changed, 114 insertions(+), 180 deletions(-) diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c index 62d7dce421..3795856592 100644 --- a/src/udev/udevadm-info.c +++ b/src/udev/udevadm-info.c @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -20,7 +19,10 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "glyph-util.h" +#include "help-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "sort-util.h" @@ -800,51 +802,21 @@ static int query_device(QueryType query, sd_device* device) { } static int help(void) { - printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n" - "Query sysfs or the udev database.\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -q --query=TYPE Query device information:\n" - " name Name of device node\n" - " symlink Pointing to node\n" - " path sysfs device path\n" - " property The device properties\n" - " all All values\n" - " --property=NAME Show only properties by this name\n" - " --value When showing properties, print only their values\n" - " -p --path=SYSPATH sysfs device path used for query or attribute walk\n" - " -n --name=NAME Node or symlink name used for query or attribute walk\n" - " -r --root Prepend dev directory to path names\n" - " -a --attribute-walk Print all key matches walking along the chain\n" - " of parent devices\n" - " -t --tree Show tree of devices\n" - " -d --device-id-of-file=FILE Print major:minor of device containing this file\n" - " -x --export Export key/value pairs\n" - " -P --export-prefix Export the key name with a prefix\n" - " -e --export-db Export the content of the udev database\n" - " -c --cleanup-db Clean up the udev database\n" - " -w --wait-for-initialization[=SECONDS]\n" - " Wait for device to be initialized\n" - " --no-pager Do not pipe output into a pager\n" - " --json=pretty|short|off Generate JSON output\n" - " --subsystem-match=SUBSYSTEM\n" - " Query devices matching a subsystem\n" - " --subsystem-nomatch=SUBSYSTEM\n" - " Query devices not matching a subsystem\n" - " --attr-match=FILE[=VALUE]\n" - " Query devices that match an attribute\n" - " --attr-nomatch=FILE[=VALUE]\n" - " Query devices that do not match an attribute\n" - " --property-match=KEY=VALUE\n" - " Query devices with matching properties\n" - " --tag-match=TAG Query devices with a matching tag\n" - " --sysname-match=NAME Query devices with this /sys path\n" - " --name-match=NAME Query devices with this /dev name\n" - " --parent-match=NAME Query devices with this parent device\n" - " --initialized-match Query devices that are already initialized\n" - " --initialized-nomatch Query devices that are not initialized yet\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + r = option_parser_get_help_table_ns("udevadm-info", &options); + if (r < 0) + return r; + + help_cmdline("info [OPTIONS] [DEVPATH|FILE]"); + help_abstract("Query sysfs or the udev database."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } @@ -1006,90 +978,64 @@ static int print_tree(sd_device* below) { } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_PROPERTY = 0x100, - ARG_VALUE, - ARG_NO_PAGER, - ARG_JSON, - ARG_SUBSYSTEM_MATCH, - ARG_SUBSYSTEM_NOMATCH, - ARG_ATTR_MATCH, - ARG_ATTR_NOMATCH, - ARG_PROPERTY_MATCH, - ARG_TAG_MATCH, - ARG_SYSNAME_MATCH, - ARG_NAME_MATCH, - ARG_PARENT_MATCH, - ARG_INITIALIZED_MATCH, - ARG_INITIALIZED_NOMATCH, - }; - - static const struct option options[] = { - { "attribute-walk", no_argument, NULL, 'a' }, - { "tree", no_argument, NULL, 't' }, - { "cleanup-db", no_argument, NULL, 'c' }, - { "device-id-of-file", required_argument, NULL, 'd' }, - { "export", no_argument, NULL, 'x' }, - { "export-db", no_argument, NULL, 'e' }, - { "export-prefix", required_argument, NULL, 'P' }, - { "help", no_argument, NULL, 'h' }, - { "name", required_argument, NULL, 'n' }, - { "path", required_argument, NULL, 'p' }, - { "property", required_argument, NULL, ARG_PROPERTY }, - { "query", required_argument, NULL, 'q' }, - { "root", no_argument, NULL, 'r' }, - { "value", no_argument, NULL, ARG_VALUE }, - { "version", no_argument, NULL, 'V' }, - { "wait-for-initialization", optional_argument, NULL, 'w' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "json", required_argument, NULL, ARG_JSON }, - { "subsystem-match", required_argument, NULL, ARG_SUBSYSTEM_MATCH }, - { "subsystem-nomatch", required_argument, NULL, ARG_SUBSYSTEM_NOMATCH }, - { "attr-match", required_argument, NULL, ARG_ATTR_MATCH }, - { "attr-nomatch", required_argument, NULL, ARG_ATTR_NOMATCH }, - { "property-match", required_argument, NULL, ARG_PROPERTY_MATCH }, - { "tag-match", required_argument, NULL, ARG_TAG_MATCH }, - { "sysname-match", required_argument, NULL, ARG_SYSNAME_MATCH }, - { "name-match", required_argument, NULL, ARG_NAME_MATCH }, - { "parent-match", required_argument, NULL, ARG_PARENT_MATCH }, - { "initialized-match", no_argument, NULL, ARG_INITIALIZED_MATCH }, - { "initialized-nomatch", no_argument, NULL, ARG_INITIALIZED_NOMATCH }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "atced:n:p:q:rxP:w::Vh", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "udevadm-info" }; + + FOREACH_OPTION(c, &opts, /* on_error= */ return c) switch (c) { - case ARG_PROPERTY: + OPTION_NAMESPACE("udevadm-info"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('q', "query", "TYPE", "Query device information:"): {} + OPTION_HELP_VERBATIM(" name", "- name of device node"): {} + OPTION_HELP_VERBATIM(" symlink", "- pointing to node"): {} + OPTION_HELP_VERBATIM(" path", "- sysfs device path"): {} + OPTION_HELP_VERBATIM(" property", "- the device properties"): {} + OPTION_HELP_VERBATIM(" all", "- all values"): + arg_query = query_type_from_string(opts.arg); + if (arg_query < 0) { + if (streq(opts.arg, "env")) /* deprecated */ + arg_query = QUERY_PROPERTY; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown query type '%s'", opts.arg); + } + break; + + OPTION_LONG("property", "NAME", "Show only properties by this name"): /* Make sure that if the empty property list was specified, we won't show any properties. */ - if (isempty(optarg) && !arg_properties) { + if (isempty(opts.arg) && !arg_properties) { arg_properties = new0(char*, 1); if (!arg_properties) return log_oom(); } else { - r = strv_split_and_extend(&arg_properties, optarg, ",", true); + r = strv_split_and_extend(&arg_properties, opts.arg, ",", true); if (r < 0) return log_oom(); } break; - case ARG_VALUE: + OPTION_LONG("value", NULL, + "When showing properties, print only their values"): arg_value = true; break; - case 'n': - case 'p': { - const char *prefix = c == 'n' ? "/dev/" : "/sys/"; + OPTION('p', "path", "SYSPATH", "sysfs device path used for query or attribute walk"): {} /* fall through */ + OPTION('n', "name", "NAME", "Node or symlink name used for query or attribute walk"): { + const char *prefix = opts.opt->short_code == 'n' ? "/dev/" : "/sys/"; char *path; - path = path_join(path_startswith(optarg, prefix) ? NULL : prefix, optarg); + path = path_join(path_startswith(opts.arg, prefix) ? NULL : prefix, opts.arg); if (!path) return log_oom(); @@ -1099,159 +1045,147 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 'q': - arg_query = query_type_from_string(optarg); - if (arg_query < 0) { - if (streq(optarg, "env")) /* deprecated */ - arg_query = QUERY_PROPERTY; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown query type '%s'", optarg); - } - break; - - case 'r': + OPTION('r', "root", NULL, "Prepend dev directory to path names"): arg_root = true; break; - case 'd': + OPTION('a', "attribute-walk", NULL, + "Print all key matches walking along the chain of parent devices"): + arg_action_type = ACTION_ATTRIBUTE_WALK; + break; + + OPTION('t', "tree", NULL, "Show tree of devices"): + arg_action_type = ACTION_TREE; + break; + + OPTION('d', "device-id-of-file", "FILE", + "Print major:minor of device containing this file"): arg_action_type = ACTION_DEVICE_ID_FILE; - r = free_and_strdup(&arg_name, optarg); + r = free_and_strdup(&arg_name, opts.arg); if (r < 0) return log_oom(); break; - case 'a': - arg_action_type = ACTION_ATTRIBUTE_WALK; + OPTION('x', "export", NULL, "Export key/value pairs"): + arg_export = true; break; - case 't': - arg_action_type = ACTION_TREE; + OPTION('P', "export-prefix", "NAME", "Export the key name with a prefix"): + arg_export = true; + arg_export_prefix = opts.arg; break; - case 'e': + OPTION('e', "export-db", NULL, "Export the content of the udev database"): arg_action_type = ACTION_EXPORT; break; - case 'c': + OPTION('c', "cleanup-db", NULL, "Clean up the udev database"): arg_action_type = ACTION_CLEANUP_DB; break; - case 'x': - arg_export = true; - break; - - case 'P': - arg_export = true; - arg_export_prefix = optarg; - break; - - case 'w': - if (optarg) { - r = parse_sec(optarg, &arg_wait_for_initialization_timeout); + OPTION_FULL(OPTION_OPTIONAL_ARG, 'w', "wait-for-initialization", "SECS", + "Wait for device to be initialized"): + if (opts.arg) { + r = parse_sec(opts.arg, &arg_wait_for_initialization_timeout); if (r < 0) return log_error_errno(r, "Failed to parse timeout value: %m"); } else arg_wait_for_initialization_timeout = USEC_INFINITY; break; - case 'V': - return print_version(); - - case 'h': - return help(); - - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; - case ARG_SUBSYSTEM_MATCH: - r = strv_extend(&arg_subsystem_match, optarg); + OPTION_LONG("subsystem-match", "SUBSYSTEM", + "Query devices matching a subsystem"): + r = strv_extend(&arg_subsystem_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_SUBSYSTEM_NOMATCH: - r = strv_extend(&arg_subsystem_nomatch, optarg); + OPTION_LONG("subsystem-nomatch", "SUBSYSTEM", + "Query devices not matching a subsystem"): + r = strv_extend(&arg_subsystem_nomatch, opts.arg); if (r < 0) return log_oom(); break; - case ARG_ATTR_MATCH: - if (!strchr(optarg, '=')) + OPTION_LONG("attr-match", "FILE[=VALUE]", + "Query devices that match an attribute"): + if (!strchr(opts.arg, '=')) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Expected = instead of '%s'", optarg); + "Expected = instead of '%s'", opts.arg); - r = strv_extend(&arg_attr_match, optarg); + r = strv_extend(&arg_attr_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_ATTR_NOMATCH: - if (!strchr(optarg, '=')) + OPTION_LONG("attr-nomatch", "FILE[=VALUE]", + "Query devices that do not match an attribute"): + if (!strchr(opts.arg, '=')) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Expected = instead of '%s'", optarg); + "Expected = instead of '%s'", opts.arg); - r = strv_extend(&arg_attr_nomatch, optarg); + r = strv_extend(&arg_attr_nomatch, opts.arg); if (r < 0) return log_oom(); break; - case ARG_PROPERTY_MATCH: - if (!strchr(optarg, '=')) + OPTION_LONG("property-match", "KEY=VALUE", + "Query devices with matching properties"): + if (!strchr(opts.arg, '=')) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Expected = instead of '%s'", optarg); + "Expected = instead of '%s'", opts.arg); - r = strv_extend(&arg_property_match, optarg); + r = strv_extend(&arg_property_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_TAG_MATCH: - r = strv_extend(&arg_tag_match, optarg); + OPTION_LONG("tag-match", "TAG", "Query devices with a matching tag"): + r = strv_extend(&arg_tag_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_SYSNAME_MATCH: - r = strv_extend(&arg_sysname_match, optarg); + OPTION_LONG("sysname-match", "NAME", "Query devices with this /sys path"): + r = strv_extend(&arg_sysname_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_NAME_MATCH: - r = strv_extend(&arg_name_match, optarg); + OPTION_LONG("name-match", "NAME", "Query devices with this /dev name"): + r = strv_extend(&arg_name_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_PARENT_MATCH: - r = strv_extend(&arg_parent_match, optarg); + OPTION_LONG("parent-match", "NAME", "Query devices with this parent device"): + r = strv_extend(&arg_parent_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_INITIALIZED_MATCH: + OPTION_LONG("initialized-match", NULL, + "Query devices that are already initialized"): arg_initialized_match = MATCH_INITIALIZED_YES; break; - case ARG_INITIALIZED_NOMATCH: + OPTION_LONG("initialized-nomatch", NULL, + "Query devices that are not initialized yet"): arg_initialized_match = MATCH_INITIALIZED_NO; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - r = strv_extend_strv(&arg_devices, argv + optind, /* filter_duplicates= */ false); + r = strv_extend_strv(&arg_devices, option_parser_get_args(&opts), /* filter_duplicates= */ false); if (r < 0) return log_error_errno(r, "Failed to build argument list: %m"); From 408d18f4d215747f7eba352cff1ea3b8c14fb574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 13:24:36 +0200 Subject: [PATCH 18/21] udev-builtin-blkid: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udev-builtin-blkid.c | 37 ++++++++++++++--------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index ca40b62d78..16eaced0dc 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -10,7 +10,6 @@ #endif #include -#include #include #include #include @@ -26,6 +25,7 @@ #include "fd-util.h" #include "initrd-util.h" #include "gpt.h" +#include "options.h" #include "parse-util.h" #include "string-util.h" #include "strv.h" @@ -506,13 +506,6 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { int64_t offset = 0; int r; - static const struct option options[] = { - { "offset", required_argument, NULL, 'o' }, - { "hint", required_argument, NULL, 'H' }, - { "noraid", no_argument, NULL, 'R' }, - {} - }; - r = dlopen_libblkid(LOG_DEBUG); if (r < 0) return log_device_debug_errno(dev, r, "blkid not available: %m"); @@ -522,32 +515,32 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { if (!pr) return log_device_debug_errno(dev, errno_or_else(ENOMEM), "Failed to create blkid prober: %m"); - for (;;) { - int option; + OptionParser opts = { argc, argv, .namespace = "udev-builtin-blkid" }; - option = getopt_long(argc, argv, "o:H:R", options, NULL); - if (option == -1) - break; + FOREACH_OPTION(c, &opts, /* on_error= */ return c) + switch (c) { - switch (option) { - case 'H': + OPTION_NAMESPACE("udev-builtin-blkid"): {} + + OPTION('H', "hint", "HINT", NULL): errno = 0; - r = sym_blkid_probe_set_hint(pr, optarg, 0); + r = sym_blkid_probe_set_hint(pr, opts.arg, 0); if (r < 0) - return log_device_error_errno(dev, errno_or_else(ENOMEM), "Failed to use '%s' probing hint: %m", optarg); + return log_device_error_errno(dev, errno_or_else(ENOMEM), "Failed to use '%s' probing hint: %m", opts.arg); break; - case 'o': - r = safe_atoi64(optarg, &offset); + + OPTION('o', "offset", "OFFSET", NULL): + r = safe_atoi64(opts.arg, &offset); if (r < 0) - return log_device_error_errno(dev, r, "Failed to parse '%s' as an integer: %m", optarg); + return log_device_error_errno(dev, r, "Failed to parse '%s' as an integer: %m", opts.arg); if (offset < 0) return log_device_error_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid offset %"PRIi64".", offset); break; - case 'R': + + OPTION('R', "noraid", NULL, NULL): noraid = true; break; } - } r = sd_device_get_devname(dev, &devnode); if (r < 0) From ab9acf8c8cf75906ebe0827c200f5bbd31f28580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 29 Apr 2026 12:20:58 +0200 Subject: [PATCH 19/21] shared/options: add new helper option_parser_get_arg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit option_parser_next_arg() is renamed to option_parser_peek_next_arg() to match option_parser_consume_next_arg(). A new helper is added option_parser_get_arg(…, n). It is a common pattern to only need a single arg, and getting an array and extracting a single item from it is too verbose. --- src/cryptenroll/cryptenroll.c | 13 +++++-------- src/growfs/growfs.c | 3 +-- src/nspawn/nspawn.c | 2 +- src/shared/options.c | 12 ++++++++++-- src/shared/options.h | 8 +++++++- src/test/test-options.c | 19 ++++++++++++------- 6 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 5d0c782689..f7e7ff1218 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -31,7 +31,6 @@ #include "process-util.h" #include "string-table.h" #include "string-util.h" -#include "strv.h" #include "tpm2-pcr.h" #include "tpm2-util.h" @@ -579,14 +578,12 @@ static int parse_argv(int argc, char *argv[]) { break; } - char **args = option_parser_get_args(&opts); + if (option_parser_get_n_args(&opts) > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments, refusing."); - if (strv_length(args) > 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Too many arguments, refusing."); - - if (args[0]) - r = parse_path_argument(args[0], false, &arg_node); + const char *arg = option_parser_get_arg(&opts, 0); + if (arg) + r = parse_path_argument(arg, false, &arg_node); else if (!wipe_requested()) r = determine_default_node(); else diff --git a/src/growfs/growfs.c b/src/growfs/growfs.c index 3e9eb678bf..efb94e3765 100644 --- a/src/growfs/growfs.c +++ b/src/growfs/growfs.c @@ -181,8 +181,7 @@ static int parse_argv(int argc, char *argv[]) { "%s expects exactly one argument (the mount point).", program_invocation_short_name); - arg_target = option_parser_get_args(&opts)[0]; - + arg_target = option_parser_get_arg(&opts, 0); return 1; } diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index e4e0359ce6..6c9c1050c6 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1420,7 +1420,7 @@ static int parse_argv(int argc, char *argv[]) { * the old container user functionality. To maintain backwards compatibility * with the space-separated form (--user NAME), if the next opts.arg does not look * like an option, interpret it as a user name. */ - const char *t = option_parser_next_arg(&opts); + const char *t = option_parser_peek_next_arg(&opts); if (t && t[0] != '-') { opts.arg = option_parser_consume_next_arg(&opts); log_warning("--user NAME is deprecated, use --uid=NAME instead."); diff --git a/src/shared/options.c b/src/shared/options.c index 01684fc1fc..85ab3155bf 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -344,7 +344,7 @@ int option_parse( return r; } -char* option_parser_next_arg(const OptionParser *state) { +char* option_parser_peek_next_arg(const OptionParser *state) { /* Peek at the next argument, whatever it is (option or position arg). * May return NULL. */ @@ -360,7 +360,7 @@ char* option_parser_consume_next_arg(OptionParser *state) { * so we won't try to interpret it as an option. * May return NULL. */ - char *t = option_parser_next_arg(state); + char *t = option_parser_peek_next_arg(state); if (t) shift_arg(state->argv, state->positional_offset++, state->optind++); return t; @@ -388,6 +388,14 @@ size_t option_parser_get_n_args(const OptionParser *state) { return state->argc - state->positional_offset; } +char* option_parser_get_arg(const OptionParser *state, size_t i) { + assert(state->optind > 0); + assert(state->state == OPTION_PARSER_DONE); + assert(state->positional_offset <= state->argc); + + return (size_t) (state->argc - state->positional_offset) > i ? state->argv[state->positional_offset + i] : NULL; +} + char* option_get_synopsis(const Option *opt, const char *joiner, bool show_metavar) { assert(opt); assert(!(opt->flags & (OPTION_NAMESPACE_MARKER | diff --git a/src/shared/options.h b/src/shared/options.h index a171f5f6a4..5803eb120e 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -224,11 +224,17 @@ int option_parse( break; \ } else -char* option_parser_next_arg(const OptionParser *state); +/* Those helpers are used *during* option parsing and allow looking at or taking the next item in + * the argv array, either an option or a positional parameter. */ +char* option_parser_peek_next_arg(const OptionParser *state); char* option_parser_consume_next_arg(OptionParser *state); +/* Those helpers are used *after* option parsing and return the positional arguments (and unparsed + * options in case option parsing was stopped early, e.g. via "--"). */ char** option_parser_get_args(const OptionParser *state); size_t option_parser_get_n_args(const OptionParser *state); +char* option_parser_get_arg(const OptionParser *state, size_t i); + char* option_get_synopsis(const Option *opt, const char *joiner, bool show_metavar); int _option_parser_get_help_table_full( diff --git a/src/test/test-options.c b/src/test/test-options.c index 04fddc1c34..efa3a73d69 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -58,7 +58,12 @@ static void test_option_parse_one( ASSERT_TRUE(strv_equal(args, remaining)); ASSERT_STREQ(argv[0], saved_argv0); - ASSERT_EQ(option_parser_get_n_args(&opts), strv_length(remaining)); + size_t l = strv_length(remaining); + ASSERT_EQ(option_parser_get_n_args(&opts), l); + ASSERT_STREQ(option_parser_get_arg(&opts, 0), l > 0 ? remaining[0] : NULL); + ASSERT_STREQ(option_parser_get_arg(&opts, 1), l > 1 ? remaining[1] : NULL); + ASSERT_STREQ(option_parser_get_arg(&opts, 2), l > 2 ? remaining[2] : NULL); + ASSERT_STREQ(option_parser_get_arg(&opts, 3), l > 3 ? remaining[3] : NULL); } static void test_option_invalid_one( @@ -1331,7 +1336,7 @@ TEST(option_optional_arg_consume) { /* --user without arg: next arg is positional (doesn't start with -). * The option parser returns NULL for the arg. The caller would then - * use option_parser_next_arg/consume_next_arg to grab it. */ + * use option_parser_peek_next_arg/consume_next_arg to grab it. */ { char **argv = STRV_MAKE("arg0", "--user", "someuser", "pos1"); int argc = strv_length(argv); @@ -1341,7 +1346,7 @@ TEST(option_optional_arg_consume) { ASSERT_OK_POSITIVE(option_parse(options, options + 3, &opts)); ASSERT_STREQ(opts.opt->long_code, "user"); ASSERT_NULL(opts.arg); - ASSERT_STREQ(option_parser_next_arg(&opts), "someuser"); + ASSERT_STREQ(option_parser_peek_next_arg(&opts), "someuser"); ASSERT_STREQ(option_parser_consume_next_arg(&opts), "someuser"); ASSERT_EQ(option_parse(options, options + 3, &opts), 0); @@ -1361,7 +1366,7 @@ TEST(option_optional_arg_consume) { ASSERT_OK_POSITIVE(option_parse(options, options + 3, &opts)); ASSERT_STREQ(opts.opt->long_code, "user"); ASSERT_NULL(opts.arg); - ASSERT_NULL(option_parser_next_arg(&opts)); + ASSERT_NULL(option_parser_peek_next_arg(&opts)); ASSERT_NULL(option_parser_consume_next_arg(&opts)); ASSERT_EQ(option_parse(options, options + 3, &opts), 0); @@ -1381,12 +1386,12 @@ TEST(option_optional_arg_consume) { ASSERT_OK_POSITIVE(option_parse(options, options + 3, &opts)); ASSERT_STREQ(opts.opt->long_code, "user"); ASSERT_NULL(opts.arg); - ASSERT_STREQ(option_parser_next_arg(&opts), "-u"); + ASSERT_STREQ(option_parser_peek_next_arg(&opts), "-u"); ASSERT_OK_POSITIVE(option_parse(options, options + 3, &opts)); ASSERT_STREQ(opts.opt->long_code, "uid"); ASSERT_STREQ(opts.arg, "nobody"); - ASSERT_NULL(option_parser_next_arg(&opts)); + ASSERT_NULL(option_parser_peek_next_arg(&opts)); ASSERT_NULL(option_parser_consume_next_arg(&opts)); ASSERT_EQ(option_parse(options, options + 3, &opts), 0); @@ -1413,7 +1418,7 @@ TEST(option_optional_arg_consume) { const char *arg = opts.arg; if (!arg) { - const char *t = option_parser_next_arg(&opts); + const char *t = option_parser_peek_next_arg(&opts); if (t && t[0] != '-') arg = option_parser_consume_next_arg(&opts); } From fcfd42a30c98743935b822f57a38a35bce060cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 13:24:40 +0200 Subject: [PATCH 20/21] udev-builtin-hwdb: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/udev/udev-builtin-hwdb.c | 44 +++++++++++++++--------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/src/udev/udev-builtin-hwdb.c b/src/udev/udev-builtin-hwdb.c index 082af2e603..4817c3af24 100644 --- a/src/udev/udev-builtin-hwdb.c +++ b/src/udev/udev-builtin-hwdb.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "sd-hwdb.h" @@ -9,6 +8,7 @@ #include "alloc-util.h" #include "device-util.h" #include "hwdb-util.h" +#include "options.h" #include "parse-util.h" #include "string-util.h" #include "udev-builtin.h" @@ -128,13 +128,6 @@ next: } static int builtin_hwdb(UdevEvent *event, int argc, char *argv[]) { - static const struct option options[] = { - { "filter", required_argument, NULL, 'f' }, - { "device", required_argument, NULL, 'd' }, - { "subsystem", required_argument, NULL, 's' }, - { "lookup-prefix", required_argument, NULL, 'p' }, - {} - }; const char *filter = NULL, *device = NULL, *subsystem = NULL, *prefix = NULL; _cleanup_(sd_device_unrefp) sd_device *srcdev = NULL; sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); @@ -143,35 +136,34 @@ static int builtin_hwdb(UdevEvent *event, int argc, char *argv[]) { if (!hwdb) return -EINVAL; - for (;;) { - int option; + OptionParser opts = { argc, argv, .namespace = "udev-builtin-hwdb" }; - option = getopt_long(argc, argv, "f:d:s:p:", options, NULL); - if (option == -1) + FOREACH_OPTION(c, &opts, /* on_error= */ return c) + switch (c) { + + OPTION_NAMESPACE("udev-builtin-hwdb"): {} + + OPTION('f', "filter", "FILTER", NULL): + filter = opts.arg; break; - switch (option) { - case 'f': - filter = optarg; + OPTION('d', "device", "DEVICE", NULL): + device = opts.arg; break; - case 'd': - device = optarg; + OPTION('s', "subsystem", "SUBSYSTEM", NULL): + subsystem = opts.arg; break; - case 's': - subsystem = optarg; - break; - - case 'p': - prefix = optarg; + OPTION('p', "lookup-prefix", "PREFIX", NULL): + prefix = opts.arg; break; } - } /* query a specific key given as argument */ - if (argv[optind]) { - r = udev_builtin_hwdb_lookup(event, prefix, argv[optind], filter); + char *modalias = option_parser_get_arg(&opts, 0); + if (modalias) { + r = udev_builtin_hwdb_lookup(event, prefix, modalias, filter); if (r < 0) return log_device_debug_errno(dev, r, "Failed to look up hwdb: %m"); if (r == 0) From 13f6feda78664ef55cc92cf2c71e83d43a78b23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 28 Apr 2026 14:52:26 +0200 Subject: [PATCH 21/21] udevadm: convert verb dispatch to VERB macros Co-developed-by: Claude Opus 4.7 --- src/udev/udevadm.c | 79 ++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index 23e03d6fb0..856d1fc4c2 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -14,33 +14,26 @@ #include "verbs.h" static int help(void) { - static const char *const short_descriptions[][2] = { - { "info", "Query sysfs or the udev database" }, - { "trigger", "Request events from the kernel" }, - { "settle", "Wait for pending udev events" }, - { "control", "Control the udev daemon" }, - { "monitor", "Listen to kernel and udev events" }, - { "test", "Test an event run" }, - { "test-builtin", "Test a built-in command" }, - { "verify", "Verify udev rules files" }, - { "cat", "Show udev rules files" }, - { "wait", "Wait for device or device symlink" }, - { "lock", "Lock a block device" }, - }; - - _cleanup_(table_unrefp) Table *options = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + r = option_parser_get_help_table_ns("udevadm", &options); if (r < 0) return r; + (void) table_sync_column_widths(0, verbs, options); + help_cmdline("[OPTIONS…] COMMAND [COMMAND OPTIONS…]"); help_abstract("Send control commands or test the device manager."); help_section("Commands:"); - FOREACH_ELEMENT(desc, short_descriptions) - printf(" %-12s %s\n", (*desc)[0], (*desc)[1]); + r = table_print_or_warn(verbs); + if (r < 0) + return r; help_section("Options:"); r = table_print_or_warn(options); @@ -51,6 +44,26 @@ static int help(void) { return 0; } +VERB_COMMON_HELP(help); + +VERB_SCOPE(, verb_info_main, "info", "[DEVPATH|FILE]", VERB_ANY, VERB_ANY, 0, "Query sysfs or the udev database"); +VERB_SCOPE(, verb_trigger_main, "trigger", "DEVPATH", VERB_ANY, VERB_ANY, 0, "Request events from the kernel"); +VERB_SCOPE(, verb_settle_main, "settle", NULL, VERB_ANY, VERB_ANY, 0, "Wait for pending udev events"); +VERB_SCOPE(, verb_control_main, "control", "OPTION", VERB_ANY, VERB_ANY, 0, "Control the udev daemon"); +VERB_SCOPE(, verb_monitor_main, "monitor", NULL, VERB_ANY, VERB_ANY, 0, "Listen to kernel and udev events"); +VERB_SCOPE(, verb_test_main, "test", "DEVPATH", VERB_ANY, VERB_ANY, 0, "Test an event run"); +VERB_SCOPE(, verb_builtin_main, "test-builtin", "COMMAND DEVPATH", VERB_ANY, VERB_ANY, 0, "Test a built-in command"); +VERB_SCOPE(, verb_verify_main, "verify", "[FILE…]", VERB_ANY, VERB_ANY, 0, "Verify udev rules files"); +VERB_SCOPE(, verb_cat_main, "cat", "[FILE…]", VERB_ANY, VERB_ANY, 0, "Show udev rules files"); +VERB_SCOPE(, verb_wait_main, "wait", "DEVICE [DEVICE…]", VERB_ANY, VERB_ANY, 0, "Wait for device or device symlink"); +VERB_SCOPE(, verb_lock_main, "lock", "[OPTIONS…] COMMAND", VERB_ANY, VERB_ANY, 0, "Lock a block device"); +VERB_SCOPE(, verb_hwdb_main, "hwdb", NULL, VERB_ANY, VERB_ANY, 0, /* help= */ NULL); /* deprecated */ + +VERB_NOARG(verb_version_main, "version", /* help= */ NULL); +static int verb_version_main(int argc, char *argv[], uintptr_t _data, void *userdata) { + return print_version(); +} + static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); @@ -84,36 +97,6 @@ int print_version(void) { return 0; } -static int verb_version_main(int argc, char *argv[], uintptr_t _data, void *userdata) { - return print_version(); -} - -static int verb_help_main(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} - -static int udevadm_main(char **args) { - static const Verb verbs[] = { - { "cat", VERB_ANY, VERB_ANY, 0, verb_cat_main }, - { "info", VERB_ANY, VERB_ANY, 0, verb_info_main }, - { "trigger", VERB_ANY, VERB_ANY, 0, verb_trigger_main }, - { "settle", VERB_ANY, VERB_ANY, 0, verb_settle_main }, - { "control", VERB_ANY, VERB_ANY, 0, verb_control_main }, - { "monitor", VERB_ANY, VERB_ANY, 0, verb_monitor_main }, - { "hwdb", VERB_ANY, VERB_ANY, 0, verb_hwdb_main }, - { "test", VERB_ANY, VERB_ANY, 0, verb_test_main }, - { "test-builtin", VERB_ANY, VERB_ANY, 0, verb_builtin_main }, - { "wait", VERB_ANY, VERB_ANY, 0, verb_wait_main }, - { "lock", VERB_ANY, VERB_ANY, 0, verb_lock_main }, - { "verify", VERB_ANY, VERB_ANY, 0, verb_verify_main }, - { "version", VERB_ANY, VERB_ANY, 0, verb_version_main }, - { "help", VERB_ANY, VERB_ANY, 0, verb_help_main }, - {} - }; - - return _dispatch_verb_with_args(args, verbs, verbs + ELEMENTSOF(verbs) - 1, NULL); -} - static int run(int argc, char *argv[]) { char **args = NULL; int r; @@ -132,7 +115,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return udevadm_main(args); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);