diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index ec07a914c5..1ca1048c5a 100644 --- a/src/ac-power/ac-power.c +++ b/src/ac-power/ac-power.c @@ -48,10 +48,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -68,7 +68,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - if (option_parser_get_n_args(&state, argc, argv) > 0) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); return 1; diff --git a/src/ask-password/ask-password.c b/src/ask-password/ask-password.c index 13e2368795..2c032c1afb 100644 --- a/src/ask-password/ask-password.c +++ b/src/ask-password/ask-password.c @@ -71,10 +71,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -170,7 +170,7 @@ static int parse_argv(int argc, char *argv[]) { SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, !r); } - char **args = option_parser_get_args(&state, argc, argv); + char **args = option_parser_get_args(&state); if (!strv_isempty(args)) { arg_message = strv_join(args, " "); diff --git a/src/binfmt/binfmt.c b/src/binfmt/binfmt.c index bdd62398e5..23c09fe349 100644 --- a/src/binfmt/binfmt.c +++ b/src/binfmt/binfmt.c @@ -137,10 +137,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -165,7 +165,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - char **args = option_parser_get_args(&state, argc, argv); + char **args = option_parser_get_args(&state); if ((arg_unregister || arg_cat_flags != CAT_CONFIG_OFF) && !strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index daabff405f..b82be92dbd 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -50,7 +50,7 @@ static int help(void) { if (r < 0) return r; - (void) table_sync_column_width(options, 0, verbs, 0); + (void) table_sync_column_widths(0, options, verbs); printf("%s [OPTIONS...] COMMAND\n" "\n%sMark the boot process as good or bad.%s\n" @@ -75,10 +75,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -93,7 +93,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - *ret_args = option_parser_get_args(&state, argc, argv); + *ret_args = option_parser_get_args(&state); return 1; } diff --git a/src/detect-virt/detect-virt.c b/src/detect-virt/detect-virt.c index d28e302480..912f6fbdd6 100644 --- a/src/detect-virt/detect-virt.c +++ b/src/detect-virt/detect-virt.c @@ -1,12 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "confidential-virt.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pretty-print.h" #include "string-table.h" #include "virt.h" @@ -23,109 +24,76 @@ static enum { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-detect-virt", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...]\n\n" - "Detect execution in a virtualized environment.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -c --container Only detect whether we are run in a container\n" - " -v --vm Only detect whether we are run in a VM\n" - " -r --chroot Detect whether we are run in a chroot() environment\n" - " --private-users Only detect whether we are running in a user namespace\n" - " --cvm Only detect whether we are run in a confidential VM\n" - " -q --quiet Don't output anything, just set return value\n" - " --list List all known and detectable types of virtualization\n" - " --list-cvm List all known and detectable types of confidential \n" - " virtualization\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...]\n" + "\n%sDetect execution in a virtualized environment.%s\n" + "\nOptions:\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal()); + table_print(options, stdout); + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_PRIVATE_USERS, - ARG_LIST, - ARG_CVM, - ARG_LIST_CVM, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "container", no_argument, NULL, 'c' }, - { "vm", no_argument, NULL, 'v' }, - { "chroot", no_argument, NULL, 'r' }, - { "private-users", no_argument, NULL, ARG_PRIVATE_USERS }, - { "quiet", no_argument, NULL, 'q' }, - { "cvm", no_argument, NULL, ARG_CVM }, - { "list", no_argument, NULL, ARG_LIST }, - { "list-cvm", no_argument, NULL, ARG_LIST_CVM }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hqcvr", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'q': + OPTION('q', "quiet", NULL, "Don't output anything, just set return value"): arg_quiet = true; break; - case 'c': + OPTION('c', "container", NULL, "Only detect whether we are run in a container"): arg_mode = ONLY_CONTAINER; break; - case ARG_PRIVATE_USERS: + OPTION_LONG("private-users", NULL, "Only detect whether we are running in a user namespace"): arg_mode = ONLY_PRIVATE_USERS; break; - case 'v': + OPTION('v', "vm", NULL, "Only detect whether we are run in a VM"): arg_mode = ONLY_VM; break; - case 'r': + OPTION('r', "chroot", NULL, "Detect whether we are run in a chroot() environment"): arg_mode = ONLY_CHROOT; break; - case ARG_LIST: + OPTION_LONG("list", NULL, "List all known and detectable types of virtualization"): return DUMP_STRING_TABLE(virtualization, Virtualization, _VIRTUALIZATION_MAX); - case ARG_CVM: + OPTION_LONG("cvm", NULL, "Only detect whether we are run in a confidential VM"): arg_mode = ONLY_CVM; return 1; - case ARG_LIST_CVM: + OPTION_LONG("list-cvm", NULL, "List all known and detectable types of confidential virtualization"): return DUMP_STRING_TABLE(confidential_virtualization, ConfidentialVirtualization, _CONFIDENTIAL_VIRTUALIZATION_MAX); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s takes no arguments.", program_invocation_short_name); diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index d36f31e5c7..bcfd7f5a81 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -141,7 +141,7 @@ static int help(void) { return r; /* Make the 1st column same width in both tables */ - (void) table_sync_column_width(options, 0, commands, 0); + (void) table_sync_column_widths(0, options, commands); printf("%1$s [OPTIONS...] IMAGE\n" "%1$s [OPTIONS...] --mount IMAGE PATH\n" @@ -207,11 +207,11 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const Option *current; const char *arg; - FOREACH_OPTION_FULL(&state, c, argc, argv, ¤t, &arg, /* on_error= */ return c) + FOREACH_OPTION_FULL(&state, c, ¤t, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_NO_PAGER: @@ -495,7 +495,7 @@ static int parse_argv(int argc, char *argv[]) { arg_runtime_scope = system_scope_requested && user_scope_requested ? _RUNTIME_SCOPE_INVALID : system_scope_requested ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER; - char **args = option_parser_get_args(&state, argc, argv); + char **args = option_parser_get_args(&state); switch (arg_action) { diff --git a/src/factory-reset/factory-reset-tool.c b/src/factory-reset/factory-reset-tool.c index 76aec05764..e7eabd8757 100644 --- a/src/factory-reset/factory-reset-tool.c +++ b/src/factory-reset/factory-reset-tool.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-json.h" #include "sd-varlink.h" @@ -12,9 +10,11 @@ #include "efivars.h" #include "errno-util.h" #include "factory-reset.h" +#include "format-table.h" #include "fs-util.h" #include "json-util.h" #include "main-func.h" +#include "options.h" #include "os-util.h" #include "pretty-print.h" #include "udev-util.h" @@ -28,76 +28,62 @@ static bool arg_varlink = false; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-factory-reset", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND\n" - "\n%5$sQuery, request, cancel factory reset operation.%6$s\n" - "\n%3$sCommands:%4$s\n" - " status Report current factory reset status\n" - " request Request a factory reset on next boot\n" - " cancel Cancel a prior factory reset request for next boot\n" - " complete Mark a factory reset as complete\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --retrigger Retrigger block devices\n" - " -q --quiet Suppress output\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options, verbs); + + printf("%s [OPTIONS...] COMMAND\n" + "\n%sQuery, request, cancel factory reset operation.%s\n" + "\nCommands:\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + table_print(verbs, stdout); + printf("\nOptions:\n"); + table_print(options, stdout); + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_RETRIGGER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "retrigger", no_argument, NULL, ARG_RETRIGGER }, - { "quiet", no_argument, NULL, 'q' }, - {} - }; - - int r, c; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hq", options, NULL)) >= 0) - switch (c) { + OptionParser state = { argc, argv }; + const char *arg; - case 'h': + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + switch (c) { + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_RETRIGGER: + OPTION_LONG("retrigger", NULL, "Retrigger block devices"): arg_retrigger = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress output"): arg_quiet = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); @@ -106,9 +92,11 @@ static int parse_argv(int argc, char *argv[]) { if (r > 0) arg_varlink = true; + *ret_args = option_parser_get_args(&state); return 1; } +VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, "Report current factory reset status"); static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { static const int exit_status_table[_FACTORY_RESET_MODE_MAX] = { /* Report current mode also as via exit status, but only return a subset of states */ @@ -130,6 +118,7 @@ static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) return exit_status_table[f]; } +VERB_NOARG(verb_request, "request", "Request a factory reset on next boot"); static int verb_request(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -197,6 +186,7 @@ static int verb_request(int argc, char *argv[], uintptr_t _data, void *userdata) return 0; } +VERB_NOARG(verb_cancel, "cancel", "Cancel a prior factory reset request for next boot"); static int verb_cancel(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -269,6 +259,7 @@ static int retrigger_block_devices(void) { return 0; } +VERB_NOARG(verb_complete, "complete", "Mark a factory reset as complete"); static int verb_complete(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -359,26 +350,19 @@ static int varlink_service(void) { } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "request", VERB_ANY, 1, 0, verb_request }, - { "cancel", VERB_ANY, 1, 0, verb_cancel }, - { "complete", VERB_ANY, 1, 0, verb_complete }, - {} - }; - int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; if (arg_varlink) return varlink_service(); - return dispatch_verb(argc, argv, verbs, /* userdata= */ NULL); + return dispatch_verb_with_args(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index d99a00c9bf..5690859cf4 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -143,7 +143,6 @@ #define XCONCATENATE(x, y) x ## y #define CONCATENATE(x, y) XCONCATENATE(x, y) -#define CONCATENATE3(x, y, z) CONCATENATE(x, CONCATENATE(y, z)) #define assert_cc(expr) _Static_assert(expr, #expr) diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index 75a17a13ae..52fa3319d7 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -23,6 +22,7 @@ #include "hostname-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "polkit-agent.h" #include "pretty-print.h" @@ -549,6 +549,7 @@ static int get_hostname_based_on_flag(sd_bus *bus) { return get_one_name(bus, attr, NULL); } +VERB(verb_show_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, "Show current hostname settings"); static int verb_show_status(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = userdata; int r; @@ -687,26 +688,36 @@ static int verb_set_hostname(int argc, char *argv[], uintptr_t _data, void *user return ret; } +VERB(verb_get_or_set_hostname, "hostname", "[NAME]", VERB_ANY, 2, 0, "Get/set system hostname"); +VERB(verb_get_or_set_hostname, "set-hostname", "NAME", 2, 2, 0, NULL); /* obsolete */ static int verb_get_or_set_hostname(int argc, char *argv[], uintptr_t data, void *userdata) { return argc == 1 ? get_hostname_based_on_flag(userdata) : verb_set_hostname(argc, argv, data, userdata); } +VERB(verb_get_or_set_icon_name, "icon-name", "[NAME]", VERB_ANY, 2, 0, "Get/set icon name for host"); +VERB(verb_get_or_set_icon_name, "set-icon-name", "NAME", 2, 2, 0, NULL); /* obsolete */ static int verb_get_or_set_icon_name(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "IconName", NULL) : set_simple_string(userdata, "icon", "SetIconName", argv[1]); } +VERB(verb_get_or_set_chassis, "chassis", "[NAME]", VERB_ANY, 2, 0, "Get/set chassis type for host"); +VERB(verb_get_or_set_chassis, "set-chassis", "NAME", 2, 2, 0, NULL); /* obsolete */ static int verb_get_or_set_chassis(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "Chassis", NULL) : set_simple_string(userdata, "chassis", "SetChassis", argv[1]); } +VERB(verb_get_or_set_deployment, "deployment", "[NAME]", VERB_ANY, 2, 0, "Get/set deployment environment for host"); +VERB(verb_get_or_set_deployment, "set-deployment", "NAME", 2, 2, 0, NULL); /* obsolete */ static int verb_get_or_set_deployment(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "Deployment", NULL) : set_simple_string(userdata, "deployment", "SetDeployment", argv[1]); } +VERB(verb_get_or_set_location, "location", "[NAME]", VERB_ANY, 2, 0, "Get/set location for host"); +VERB(verb_get_or_set_location, "set-location", "NAME", 2, 2, 0, NULL); /* obsolete */ static int verb_get_or_set_location(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "Location", NULL) : set_simple_string(userdata, "location", "SetLocation", argv[1]); @@ -714,164 +725,108 @@ static int verb_get_or_set_location(int argc, char *argv[], uintptr_t _data, voi static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("hostnamectl", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%2$sQuery or change system hostname.%3$s\n" - "\n%4$sCommands:%5$s\n" - " status Show current hostname settings\n" - " hostname [NAME] Get/set system hostname\n" - " icon-name [NAME] Get/set icon name for host\n" - " chassis [NAME] Get/set chassis type for host\n" - " deployment [NAME] Get/set deployment environment for host\n" - " location [NAME] Get/set location for host\n" - "\n%4$sOptions:%5$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-ask-password Do not prompt for password\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --transient Only set transient hostname\n" - " --static Only set static hostname\n" - " --pretty Only set pretty hostname\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " -j Same as --json=pretty on tty, --json=short otherwise\n" - "\nSee the %6$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options, verbs); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sQuery or change system hostname.%s\n" + "\nCommands:\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - ansi_underline(), - ansi_normal(), - link); + ansi_normal()); + table_print(verbs, stdout); + printf("\nOptions:\n"); + table_print(options, stdout); + + printf("\nSee the %s for details.\n", link); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} +VERB_COMMON_HELP_HIDDEN(help); -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_ASK_PASSWORD, - ARG_TRANSIENT, - ARG_STATIC, - ARG_PRETTY, - ARG_JSON, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "transient", no_argument, NULL, ARG_TRANSIENT }, - { "static", no_argument, NULL, ARG_STATIC }, - { "pretty", no_argument, NULL, ARG_PRETTY }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "json", required_argument, NULL, ARG_JSON }, - {} - }; - - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hH:M:j", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_HOST: + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = arg; + break; + + OPTION_COMMON_MACHINE: + r = parse_machine_argument(arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case ARG_TRANSIENT: + OPTION_LONG("transient", NULL, "Only set transient hostname"): arg_transient = true; break; - case ARG_PRETTY: - arg_pretty = true; - break; - - case ARG_STATIC: + OPTION_LONG("static", NULL, "Only set static hostname"): arg_static = true; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION_LONG("pretty", NULL, "Only set pretty hostname"): + arg_pretty = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case 'j': + OPTION_COMMON_LOWERCASE_J: arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } -static int hostnamectl_main(sd_bus *bus, int argc, char *argv[]) { - - static const Verb verbs[] = { - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_show_status }, - { "hostname", VERB_ANY, 2, 0, verb_get_or_set_hostname }, - { "set-hostname", 2, 2, 0, verb_get_or_set_hostname }, /* obsolete */ - { "icon-name", VERB_ANY, 2, 0, verb_get_or_set_icon_name }, - { "set-icon-name", 2, 2, 0, verb_get_or_set_icon_name }, /* obsolete */ - { "chassis", VERB_ANY, 2, 0, verb_get_or_set_chassis }, - { "set-chassis", 2, 2, 0, verb_get_or_set_chassis }, /* obsolete */ - { "deployment", VERB_ANY, 2, 0, verb_get_or_set_deployment }, - { "set-deployment", 2, 2, 0, verb_get_or_set_deployment }, /* obsolete */ - { "location", VERB_ANY, 2, 0, verb_get_or_set_location }, - { "set-location", 2, 2, 0, verb_get_or_set_location }, /* obsolete */ - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it is created. */ - {} - }; - - return dispatch_verb(argc, argv, verbs, bus); -} - static int run(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + char **args = NULL; int r; setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -879,7 +834,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM); - return hostnamectl_main(bus, argc, argv); + return dispatch_verb_with_args(args, bus); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/id128/id128.c b/src/id128/id128.c index ebed02913f..f23403b3f8 100644 --- a/src/id128/id128.c +++ b/src/id128/id128.c @@ -208,7 +208,7 @@ static int help(void) { return r; /* Make the 1st column same width in both tables */ - (void) table_sync_column_width(options, 0, verbs, 0); + (void) table_sync_column_widths(0, options, verbs); printf("%s [OPTIONS...] COMMAND\n\n" "%sGenerate and print 128-bit identifiers.%s\n" @@ -233,10 +233,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -259,8 +259,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return r; break; - OPTION_SHORT('j', NULL, - "Equivalent to --json=pretty (on TTY) or --json=short (otherwise)"): + OPTION_COMMON_LOWERCASE_J: arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; @@ -288,7 +287,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - *ret_args = option_parser_get_args(&state, argc, argv); + *ret_args = option_parser_get_args(&state); return 1; } diff --git a/src/notify/notify.c b/src/notify/notify.c index 5535296760..a06f5ce773 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -158,10 +158,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -283,7 +283,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { bool have_env = arg_ready || arg_stopping || arg_reloading || arg_status || pidref_is_set(&arg_pid) || !fdset_isempty(arg_fds); - char **args = option_parser_get_args(&state, argc, argv); + char **args = option_parser_get_args(&state); switch (arg_action) { diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 279e7fda68..04552b5b56 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -2185,26 +2185,35 @@ int table_set_column_width(Table *t, size_t column, size_t width) { return r; } -int table_sync_column_width(Table *a, size_t column_a, Table *b, size_t column_b) { - size_t w1, w2; - int r; +int _table_sync_column_widths(size_t column, Table *a, ...) { + size_t max = 0; + va_list ap; + int r = 0; assert(a); - assert(b); - /* Make both tables have specified columns of same width */ + /* Make the specified column have the same width in the tables. */ - r = table_data_requested_width(a, column_a, &w1); - if (r < 0) - return log_error_errno(r, "Failed to query table column width: %m"); + va_start(ap, a); + for (Table *t = a; t; t = va_arg(ap, Table*)) { + size_t w; - r = table_data_requested_width(b, column_b, &w2); + r = table_data_requested_width(t, column, &w); + if (r < 0) + break; + + max = MAX(max, w); + } + va_end(ap); if (r < 0) return log_error_errno(r, "Failed to query table column width: %m"); r = 0; - RET_GATHER(r, table_set_column_width(a, column_a, MAX(w1, w2))); - RET_GATHER(r, table_set_column_width(b, column_b, MAX(w1, w2))); + va_start(ap, a); + for (Table *t = a; t; t = va_arg(ap, Table*)) + RET_GATHER(r, table_set_column_width(t, column, max)); + va_end(ap); + return r; } diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 9a11fb7c30..bb5a68b7e9 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -144,7 +144,8 @@ int table_hide_column_from_display_internal(Table *t, ...); int table_data_requested_width(Table *table, size_t column, size_t *ret); int table_set_column_width(Table *t, size_t column, size_t width); -int table_sync_column_width(Table *a, size_t column_a, Table *b, size_t column_b); +int _table_sync_column_widths(size_t column, Table *a, ...); +#define table_sync_column_widths(column, a, ...) _table_sync_column_widths(column, a, __VA_ARGS__, NULL) int table_print(Table *t, FILE *f); int table_format(Table *t, char **ret); diff --git a/src/shared/options.c b/src/shared/options.c index 8ea22c9b7a..c5118f3342 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -25,14 +25,6 @@ static bool option_is_metadata(const Option *opt) { FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_HELP_ENTRY); } -static void kill_arg(char* argv[], int argc, int index) { - assert(index < argc); - assert(!argv[argc]); - - /* Eliminate argv[index] */ - memmove(argv + index, argv + index + 1, (argc - index) * sizeof(char*)); -} - static void shift_arg(char* argv[], int target, int source) { assert(argv); assert(target <= source); @@ -77,7 +69,6 @@ int option_parse( const Option options[], const Option options_end[], OptionParser *state, - int argc, char *argv[], const Option **ret_option, const char **ret_arg) { @@ -85,13 +76,10 @@ int option_parse( /* Check and initialize */ if (state->optind == 0) { - if (argc < 1 || strv_isempty(argv)) + if (state->argc < 1 || strv_isempty(state->argv)) return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty"); - *state = (OptionParser) { - .optind = 1, - .positional_offset = 1, - }; + state->optind = state->positional_offset = 1; } /* Look for the next option */ @@ -104,20 +92,21 @@ int option_parse( if (state->short_option_offset == 0) { /* Skip over non-option parameters */ for (;;) { - if (state->optind == argc) + if (state->optind == state->argc) return 0; - if (streq(argv[state->optind], "--")) { - /* No more options. Eliminate "--" so that the list of positional args is clean. */ - kill_arg(argv, argc, state->optind); - return 0; + if (streq(state->argv[state->optind], "--")) { + /* No more options. Move "--" before positional args so that + * the list of positional args is clean. */ + shift_arg(state->argv, state->positional_offset++, state->optind++); + state->parsing_stopped = true; } if (state->parsing_stopped) return 0; - if (argv[state->optind][0] == '-' && - argv[state->optind][1] != '\0') + if (state->argv[state->optind][0] == '-' && + state->argv[state->optind][1] != '\0') /* Looks like we found an option parameter */ break; @@ -126,13 +115,13 @@ int option_parse( /* Find matching option entry. * First, figure out if we have a long option or a short option. */ - assert(argv[state->optind][0] == '-'); + assert(state->argv[state->optind][0] == '-'); - if (argv[state->optind][1] == '-') { + if (state->argv[state->optind][1] == '-') { /* We have a long option. */ - char *eq = strchr(argv[state->optind], '='); + char *eq = strchr(state->argv[state->optind], '='); if (eq) { - optname = _optname = strndup(argv[state->optind], eq - argv[state->optind]); + optname = _optname = strndup(state->argv[state->optind], eq - state->argv[state->optind]); if (!_optname) return log_oom(); @@ -140,7 +129,7 @@ int option_parse( optval = eq + 1; } else /* argument (if any) is separate */ - optname = argv[state->optind]; + optname = state->argv[state->optind]; const Option *last_partial = NULL; unsigned n_partial_matches = 0; /* The commandline option matches a defined prefix. */ @@ -179,7 +168,7 @@ int option_parse( } if (state->short_option_offset > 0) { - char optchar = argv[state->optind][state->short_option_offset]; + char optchar = state->argv[state->optind][state->short_option_offset]; if (asprintf(&_optname, "-%c", optchar) < 0) return log_oom(); @@ -194,7 +183,7 @@ int option_parse( if (option_is_metadata(option) || optchar != option->short_code) continue; - const char *rest = argv[state->optind] + state->short_option_offset + 1; + const char *rest = state->argv[state->optind] + state->short_option_offset + 1; if (option_takes_arg(option) && !isempty(rest)) { /* The rest of this parameter is the value. */ @@ -216,19 +205,19 @@ int option_parse( "%s: option '%s' doesn't allow an argument", program_invocation_short_name, optname); if (!optval && option_arg_required(option)) { - if (!argv[state->optind + 1]) + if (!state->argv[state->optind + 1]) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s: option '%s' requires an argument", program_invocation_short_name, optname); - optval = argv[state->optind + 1]; + optval = state->argv[state->optind + 1]; separate_optval = true; } if (state->short_option_offset == 0) { /* We're done with this option. Adjust the array and position. */ - shift_arg(argv, state->positional_offset++, state->optind++); + shift_arg(state->argv, state->positional_offset++, state->optind++); if (separate_optval) - shift_arg(argv, state->positional_offset++, state->optind++); + shift_arg(state->argv, state->positional_offset++, state->optind++); } if (FLAGS_SET(option->flags, OPTION_STOPS_PARSING)) @@ -241,12 +230,25 @@ int option_parse( return option->id; } -char** option_parser_get_args(const OptionParser *state, int argc, char *argv[]) { +char** option_parser_get_args(const OptionParser *state) { /* Returns positional args as a strv. - * If "--" was found, it has been removed. */ + * If "--" was found, it has been moved before state->positional_offset. + * The array is only valid, i.e. clean without any options, after parsing + * has naturally finished. */ assert(state->optind > 0); - return argv + state->positional_offset; + assert(state->optind == state->argc || state->parsing_stopped); + assert(state->positional_offset <= state->argc); + + return state->argv + state->positional_offset; +} + +size_t option_parser_get_n_args(const OptionParser *state) { + assert(state->optind > 0); + assert(state->optind == state->argc || state->parsing_stopped); + assert(state->positional_offset <= state->argc); + + return state->argc - state->positional_offset; } int _option_parser_get_help_table( @@ -264,7 +266,7 @@ int _option_parser_get_help_table( bool in_group = group == NULL; /* Are we currently in the section on the array that forms * group ? The first part is the default group, so - * the group was not specified, we are in. */ + * if the group was not specified, we are in. */ for (const Option *opt = options; opt < options_end; opt++) { bool group_marker = FLAGS_SET(opt->flags, OPTION_GROUP_MARKER); diff --git a/src/shared/options.h b/src/shared/options.h index fd2d048db0..7980b69448 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -2,7 +2,6 @@ #pragma once #include "shared-forward.h" -#include "strv.h" typedef enum OptionFlags { OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */ @@ -61,14 +60,27 @@ typedef struct Option { OPTION_LONG("cat-config", NULL, "Show configuration files") #define OPTION_COMMON_TLDR \ OPTION_LONG("tldr", NULL, "Show non-comment parts of configuration") +#define OPTION_COMMON_NO_ASK_PASSWORD \ + OPTION_LONG("no-ask-password", NULL, "Do not prompt for password") +#define OPTION_COMMON_HOST \ + OPTION('H', "host", "[USER@]HOST", "Operate on remote host") +#define OPTION_COMMON_MACHINE \ + OPTION('M', "machine", "CONTAINER", "Operate on local container") #define OPTION_COMMON_JSON \ OPTION_LONG("json", "FORMAT", "Generate JSON output (pretty, short, or off)") +#define OPTION_COMMON_LOWERCASE_J \ + OPTION_SHORT('j', NULL, \ + "Equivalent to --json=pretty (on TTY) or --json=short (otherwise)") /* This is magically mapped to the beginning and end of the section */ extern const Option __start_SYSTEMD_OPTIONS[]; extern const Option __stop_SYSTEMD_OPTIONS[]; typedef struct OptionParser { + /* Those two should stay first so that it's possible to initialize the struct as { argc, argv }. */ + int argc; /* The original argc. */ + char **argv; /* The argv array, possibly reordered. */ + int optind; /* Position of the parameter being handled. * 0 → option parsing hasn't been started yet. */ int short_option_offset; /* Set when we're parsing an argument with one or more short options. @@ -82,25 +94,22 @@ int option_parse( const Option options[], const Option options_end[], OptionParser *state, - int argc, char *argv[], const Option **ret_option, const char **ret_arg); /* Iterate over options. */ -#define FOREACH_OPTION_FULL(parser, opt, argc, argv, ret_o, ret_a, on_error) \ - for (int opt; (opt = option_parse(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, parser, argc, argv, ret_o, ret_a)) != 0; ) \ +#define FOREACH_OPTION_FULL(parser, opt, ret_o, ret_a, on_error) \ + for (int opt; (opt = option_parse(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, parser, ret_o, ret_a)) != 0; ) \ if (opt < 0) { \ on_error; \ break; \ } else -#define FOREACH_OPTION(parser, opt, argc, argv, ret_a, on_error) \ - FOREACH_OPTION_FULL(parser, opt, argc, argv, /* ret_o= */ NULL, ret_a, on_error) +#define FOREACH_OPTION(parser, opt, ret_a, on_error) \ + FOREACH_OPTION_FULL(parser, opt, /* ret_o= */ NULL, ret_a, on_error) -char** option_parser_get_args(const OptionParser *state, int argc, char *argv[]); -static inline size_t option_parser_get_n_args(const OptionParser *state, int argc, char *argv[]) { - return strv_length(option_parser_get_args(state, argc, argv)); -} +char** option_parser_get_args(const OptionParser *state); +size_t option_parser_get_n_args(const OptionParser *state); int _option_parser_get_help_table( const Option options[], diff --git a/src/shared/verbs.c b/src/shared/verbs.c index 47f219fab6..dfecf04861 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -58,12 +58,21 @@ bool should_bypass(const char *env_prefix) { return true; } +static bool verb_is_metadata(const Verb *verb) { + /* A metadata entry that is not a real verb, like the group marker */ + return FLAGS_SET(ASSERT_PTR(verb)->flags, VERB_GROUP_MARKER); +} + const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]) { assert(verbs); - for (const Verb *verb = verbs; verb < verbs_end; verb++) + for (const Verb *verb = verbs; verb < verbs_end; verb++) { + if (verb_is_metadata(verb)) + continue; + if (name ? streq(name, verb->verb) : FLAGS_SET(verb->flags, VERB_DEFAULT)) return verb; + } /* At the end of the list? */ return NULL; @@ -85,6 +94,9 @@ int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_e _cleanup_strv_free_ char **verb_strv = NULL; for (verb = verbs; verb < verbs_end; verb++) { + if (verb_is_metadata(verb)) + continue; + r = strv_extend(&verb_strv, verb->verb); if (r < 0) return log_oom(); @@ -142,13 +154,17 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { assert(argc >= optind); size_t n = 0; - while (verbs[n].dispatch) + while (verbs[n].verb) n++; return _dispatch_verb_with_args(strv_skip(argv, optind), verbs, verbs + n, userdata); } -int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **ret) { +int _verbs_get_help_table( + const Verb verbs[], + const Verb verbs_end[], + const char *group, + Table **ret) { int r; assert(ret); @@ -157,10 +173,23 @@ int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **re if (!table) return log_oom(); + bool in_group = group == NULL; /* Are we currently in the section on the array that forms + * group ? The first part is the default group, so + * if the group was not specified, we are in. */ + for (const Verb *verb = verbs; verb < verbs_end; verb++) { - assert(verb->dispatch); + assert(verb->verb); + + bool group_marker = FLAGS_SET(verb->flags, VERB_GROUP_MARKER); + if (!in_group) { + in_group = group_marker && streq(group, verb->verb); + continue; + } + if (group_marker) + break; /* End of group */ if (!verb->help) + /* No help string — we do not show the verb */ continue; /* We indent the option string by two spaces. We could set the minimum cell width and diff --git a/src/shared/verbs.h b/src/shared/verbs.h index 7980db62fe..6b380041b8 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -8,6 +8,7 @@ typedef enum VerbFlags { VERB_DEFAULT = 1 << 0, /* The verb to run if no verb is specified */ VERB_ONLINE_ONLY = 1 << 1, /* Just do nothing when running in chroot or offline */ + VERB_GROUP_MARKER = 1 << 2, /* Fake verb entry to separate groups */ } VerbFlags; typedef struct { @@ -20,16 +21,13 @@ typedef struct { const char *help; } Verb; -#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \ - DISABLE_WARNING_REDUNDANT_DECLS \ - static int d(int, char**, uintptr_t, void*); \ - REENABLE_WARNING \ +#define _VERB_DATA(d, v, a, amin, amax, f, dat, h) \ _section_("SYSTEMD_VERBS") \ _alignptr_ \ _used_ \ _retain_ \ _variable_no_sanitize_address_ \ - static const Verb CONCATENATE3(d, _data_, __COUNTER__) = { \ + static const Verb CONCATENATE(verb_data_, __COUNTER__) = { \ .verb = v, \ .min_args = amin, \ .max_args = amax, \ @@ -40,6 +38,12 @@ typedef struct { .help = h, \ } +#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \ + DISABLE_WARNING_REDUNDANT_DECLS \ + static int d(int, char**, uintptr_t, void*); \ + REENABLE_WARNING \ + _VERB_DATA(d, v, a, amin, amax, f, dat, h) + /* The same as VERB_FULL, but without the data argument */ #define VERB(d, v, a, amin, amax, f, h) \ VERB_FULL(d, v, a, amin, amax, f, /* dat= */ 0, h) @@ -48,6 +52,11 @@ typedef struct { #define VERB_NOARG(d, v, h) \ VERB(d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ 0, h) +/* Magic entry in the table (which will not be returned) that designates the start of the group . */ +#define VERB_GROUP(gr) \ + _VERB_DATA(/* d= */ NULL, /* v= */ gr, /* a= */ NULL, /* amin= */ 0, /* amax= */ 0, \ + /* f= */ VERB_GROUP_MARKER, /* dat= */ 0, /* h= */ NULL) + /* This is magically mapped to the beginning and end of the section */ extern const Verb __start_SYSTEMD_VERBS[]; extern const Verb __stop_SYSTEMD_VERBS[]; @@ -64,9 +73,15 @@ int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_e int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata); -int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **ret); -#define verbs_get_help_table(ret) \ - _verbs_get_help_table(ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, ret) +int _verbs_get_help_table( + const Verb verbs[], + const Verb verbs_end[], + const char *group, + Table **ret); +#define verbs_get_help_table_group(group, ret) \ + _verbs_get_help_table(ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, group, ret) +#define verbs_get_help_table(ret) \ + verbs_get_help_table_group(/* group= */ NULL, ret) #define _VERB_COMMON_HELP_IMPL(impl) \ static int verb_help(int argc, char **argv, uintptr_t data, void *userdata) { \ diff --git a/src/test/test-options.c b/src/test/test-options.c index 201d16d51f..fb1f61f358 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -31,10 +31,10 @@ static void test_option_parse_one( for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) n_entries++; - OptionParser state = {}; + OptionParser state = { argc, argv }; const Option *opt; const char *arg; - for (int c; (c = option_parse(options, options + n_options, &state, argc, argv, &opt, &arg)) != 0; ) { + for (int c; (c = option_parse(options, options + n_options, &state, &opt, &arg)) != 0; ) { ASSERT_OK(c); ASSERT_NOT_NULL(opt); @@ -54,11 +54,11 @@ static void test_option_parse_one( ASSERT_EQ(i, n_entries); - char **args = option_parser_get_args(&state, argc, argv); + char **args = option_parser_get_args(&state); ASSERT_TRUE(strv_equal(args, remaining)); ASSERT_STREQ(argv[0], saved_argv0); - ASSERT_EQ(option_parser_get_n_args(&state, argc, argv), strv_length(remaining)); + ASSERT_EQ(option_parser_get_n_args(&state), strv_length(remaining)); } static void test_option_invalid_one( @@ -77,11 +77,11 @@ static void test_option_invalid_one( for (const Option *o = options; o->short_code != 0 || o->long_code; o++) n_options++; - OptionParser state = {}; + OptionParser state = { argc, argv }; const Option *opt; const char *arg; - int c = option_parse(options, options + n_options, &state, argc, argv, &opt, &arg); + int c = option_parse(options, options + n_options, &state, &opt, &arg); ASSERT_ERROR(c, EINVAL); } @@ -691,11 +691,11 @@ static void test_macros_parse_one( for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) n_entries++; - OptionParser state = {}; + OptionParser state = { argc, argv }; const Option *opt; const char *arg; - FOREACH_OPTION_FULL(&state, c, argc, argv, &opt, &arg, ASSERT_TRUE(false)) { + FOREACH_OPTION_FULL(&state, c, &opt, &arg, ASSERT_TRUE(false)) { log_debug("%c %s: %s=%s", opt->short_code != 0 ? opt->short_code : ' ', opt->long_code ?: "", @@ -751,7 +751,7 @@ static void test_macros_parse_one( ASSERT_EQ(i, n_entries); - char **args = option_parser_get_args(&state, argc, argv); + char **args = option_parser_get_args(&state); ASSERT_TRUE(strv_equal(args, remaining)); ASSERT_STREQ(argv[0], saved_argv0); } diff --git a/src/test/test-verbs.c b/src/test/test-verbs.c index 79c5c27dd9..41ae5a87f3 100644 --- a/src/test/test-verbs.c +++ b/src/test/test-verbs.c @@ -16,14 +16,16 @@ static int noop_dispatcher(int argc, char *argv[], uintptr_t _data, void *userda TEST(verbs) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, - { "list", VERB_ANY, 2, VERB_DEFAULT, noop_dispatcher }, - { "status", 2, VERB_ANY, 0, noop_dispatcher }, - { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, - { "login", 2, 2, 0, noop_dispatcher }, - { "copy-to", 3, 4, 0, noop_dispatcher }, + { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, + { "list", VERB_ANY, 2, VERB_DEFAULT, noop_dispatcher }, + { "status", 2, VERB_ANY, 0, noop_dispatcher }, + { "Group2", VERB_ANY, VERB_ANY, VERB_GROUP_MARKER, NULL }, + { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, + { "Group3", 0, 0, VERB_GROUP_MARKER, NULL }, + { "login", 2, 2, 0, noop_dispatcher }, + { "copy-to", 3, 4, 0, noop_dispatcher }, {} }; @@ -44,6 +46,12 @@ TEST(verbs) { /* no verb, but a default is set */ test_dispatch_one(STRV_EMPTY, verbs, 0); + + /* the group entry shall not be found */ + test_dispatch_one(STRV_MAKE("Group2"), verbs, -EINVAL); + + /* the group entry shall not be found */ + test_dispatch_one(STRV_MAKE("Group3"), verbs, -EINVAL); } TEST(verbs_no_default) { @@ -60,14 +68,15 @@ TEST(verbs_no_default) { TEST(verbs_no_default_many) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, - { "list", VERB_ANY, 2, 0, noop_dispatcher }, - { "status", 2, VERB_ANY, 0, noop_dispatcher }, - { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, - { "login", 2, 2, 0, noop_dispatcher }, - { "copy-to", 3, 4, 0, noop_dispatcher }, + { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, + { "list", VERB_ANY, 2, 0, noop_dispatcher }, + { "status", 2, VERB_ANY, 0, noop_dispatcher }, + { "Specials", VERB_ANY, VERB_ANY, VERB_GROUP_MARKER, NULL }, + { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, + { "login", 2, 2, 0, noop_dispatcher }, + { "copy-to", 3, 4, 0, noop_dispatcher }, {} }; @@ -75,6 +84,7 @@ TEST(verbs_no_default_many) { test_dispatch_one(STRV_MAKE("hel"), verbs, -EINVAL); test_dispatch_one(STRV_MAKE("helpp"), verbs, -EINVAL); test_dispatch_one(STRV_MAKE("hgrejgoraoiosafso"), verbs, -EINVAL); + test_dispatch_one(STRV_MAKE("Specials"), verbs, -EINVAL); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index 03e5479aae..b3c45c352b 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -94,10 +94,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -112,7 +112,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - if (option_parser_get_n_args(&state, argc, argv) > 0) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); return 1; diff --git a/src/validatefs/validatefs.c b/src/validatefs/validatefs.c index c507e4d98a..9645fd187f 100644 --- a/src/validatefs/validatefs.c +++ b/src/validatefs/validatefs.c @@ -62,10 +62,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -88,7 +88,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - char **args = option_parser_get_args(&state, argc, argv); + char **args = option_parser_get_args(&state); if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL),