diff --git a/NEWS b/NEWS index 85cc7ac2d1..cdf3c3a0ad 100644 --- a/NEWS +++ b/NEWS @@ -14264,7 +14264,7 @@ CHANGES WITH 235: the "utmp" group already, and it appears to be generally understood that members of "utmp" can modify/flush the utmp/wtmp/lastlog/btmp databases. Previously this was implemented correctly for all these - databases excepts btmp, which has been opened up like this now + databases except btmp, which has been opened up like this now too. Note that while the other databases are world-readable (i.e. 0644), btmp is not and remains more restrictive. diff --git a/man/systemd-update-done.service.xml b/man/systemd-update-done.service.xml index d9d78262a1..8bb92ca5b5 100644 --- a/man/systemd-update-done.service.xml +++ b/man/systemd-update-done.service.xml @@ -79,6 +79,7 @@ + diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index 13382b9994..ec07a914c5 100644 --- a/src/ac-power/ac-power.c +++ b/src/ac-power/ac-power.c @@ -1,13 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" #include "ansi-color.h" #include "battery-util.h" #include "build.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pretty-print.h" #include "string-util.h" @@ -20,77 +20,56 @@ static enum { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-ac-power", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTION]\n" - "\n%2$sReport whether we are connected to an external power source.%3$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -v --verbose Show state as text\n" - " --low Check if battery is discharging and low\n" - "\nSee the %4$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\n%sReport whether we are connected to an external power source.%s\n" + "\nOptions:\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + 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_LOW, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "verbose", no_argument, NULL, 'v' }, - { "low", no_argument, NULL, ARG_LOW }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0) + OptionParser state = {}; + const char *arg; + FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) switch (c) { + OPTION_COMMON_HELP: + return help(); - case 'h': - help(); - return 0; - - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'v': + OPTION('v', "verbose", NULL, "Show state as text"): arg_verbose = true; break; - case ARG_LOW: + OPTION_LONG("low", NULL, "Check if battery is discharging and low"): arg_action = ACTION_LOW; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s takes no arguments.", - program_invocation_short_name); + if (option_parser_get_n_args(&state, argc, argv) > 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 f6634b54f6..13e2368795 100644 --- a/src/ask-password/ask-password.c +++ b/src/ask-password/ask-password.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-varlink.h" @@ -10,10 +9,12 @@ #include "build.h" #include "bus-polkit.h" #include "constants.h" +#include "format-table.h" #include "hashmap.h" #include "json-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "string-table.h" @@ -39,124 +40,72 @@ STATIC_DESTRUCTOR_REGISTER(arg_message, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-ask-password", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] MESSAGE\n\n" - "%3$sQuery the user for a passphrase, via the TTY or a UI agent.%4$s\n\n" - " -h --help Show this help\n" - " --icon=NAME Icon name\n" - " --id=ID Query identifier (e.g. \"cryptsetup:/dev/sda5\")\n" - " --keyname=NAME Kernel key name for caching passwords (e.g. \"cryptsetup\")\n" - " --credential=NAME\n" - " Credential name for ImportCredential=, LoadCredential= or\n" - " SetCredential= credentials\n" - " --timeout=SEC Timeout in seconds\n" - " --echo=yes|no|masked\n" - " Control whether to show password while typing (echo)\n" - " -e --echo Equivalent to --echo=yes\n" - " --emoji=yes|no|auto\n" - " Show a lock and key emoji\n" - " --no-tty Ask question via agent even on TTY\n" - " --accept-cached Accept cached passwords\n" - " --multiple List multiple passwords if available\n" - " --no-output Do not print password to standard output\n" - " -n Do not suffix password written to standard output with\n" - " newline\n" - " --user Ask only our own user's agents\n" - " --system Ask agents of the system and of all users\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] MESSAGE\n" + "\n%sQuery the user for a passphrase, via the TTY or a UI agent.%s\n" + "\nOptions:\n", program_invocation_short_name, - link, 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_ICON = 0x100, - ARG_TIMEOUT, - ARG_EMOJI, - ARG_NO_TTY, - ARG_ACCEPT_CACHED, - ARG_MULTIPLE, - ARG_ID, - ARG_KEYNAME, - ARG_NO_OUTPUT, - ARG_VERSION, - ARG_CREDENTIAL, - ARG_USER, - ARG_SYSTEM, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "icon", required_argument, NULL, ARG_ICON }, - { "timeout", required_argument, NULL, ARG_TIMEOUT }, - { "echo", optional_argument, NULL, 'e' }, - { "emoji", required_argument, NULL, ARG_EMOJI }, - { "no-tty", no_argument, NULL, ARG_NO_TTY }, - { "accept-cached", no_argument, NULL, ARG_ACCEPT_CACHED }, - { "multiple", no_argument, NULL, ARG_MULTIPLE }, - { "id", required_argument, NULL, ARG_ID }, - { "keyname", required_argument, NULL, ARG_KEYNAME }, - { "no-output", no_argument, NULL, ARG_NO_OUTPUT }, - { "credential", required_argument, NULL, ARG_CREDENTIAL }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - {} - }; - const char *emoji = NULL; - int c, r; + int r; assert(argc >= 0); assert(argv); - /* Note the asymmetry: the long option --echo= allows an optional argument, the short option does - * not. */ - - /* 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, "+hen", options, NULL)) >= 0) + OptionParser state = {}; + const char *arg; + FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) switch (c) { - - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ICON: - arg_icon = optarg; + OPTION_LONG("icon", "NAME", "Icon name"): + arg_icon = arg; break; - case ARG_TIMEOUT: - r = parse_sec(optarg, &arg_timeout); + OPTION_LONG("timeout", "SEC", "Timeout in seconds"): + r = parse_sec(arg, &arg_timeout); 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", arg); break; - case 'e': - if (!optarg) { + /* Note the asymmetry: the long option --echo= allows an optional argument, + * the short option does not. */ + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "echo", "yes|no|masked", + "Control whether to show password while typing"): {} + OPTION('e', "echo", NULL, "Equivalent to --echo=yes"): + if (!arg) { /* Short option -e is used, or no argument to long option --echo= */ arg_flags |= ASK_PASSWORD_ECHO; arg_flags &= ~ASK_PASSWORD_SILENT; - } else if (isempty(optarg) || streq(optarg, "masked")) + } else if (isempty(arg) || streq(arg, "masked")) /* Empty argument or explicit string "masked" for default behaviour. */ arg_flags &= ~(ASK_PASSWORD_ECHO|ASK_PASSWORD_SILENT); else { - r = parse_boolean_argument("--echo=", optarg, NULL); + r = parse_boolean_argument("--echo=", arg, NULL); if (r < 0) return r; @@ -165,55 +114,50 @@ static int parse_argv(int argc, char *argv[]) { } break; - case ARG_EMOJI: - emoji = optarg; + OPTION_LONG("emoji", "yes|no|auto", "Show a lock and key emoji"): + emoji = arg; break; - case ARG_NO_TTY: + OPTION_LONG("no-tty", NULL, "Ask question via agent even on TTY"): arg_flags |= ASK_PASSWORD_NO_TTY; break; - case ARG_ACCEPT_CACHED: + OPTION_LONG("accept-cached", NULL, "Accept cached passwords"): arg_flags |= ASK_PASSWORD_ACCEPT_CACHED; break; - case ARG_MULTIPLE: + OPTION_LONG("multiple", NULL, "List multiple passwords if available"): arg_multiple = true; break; - case ARG_ID: - arg_id = optarg; + OPTION_LONG("id", "ID", "Query identifier (e.g. \"cryptsetup:/dev/sda5\")"): + arg_id = arg; break; - case ARG_KEYNAME: - arg_key_name = optarg; + OPTION_LONG("keyname", "NAME", "Kernel key name for caching passwords"): + arg_key_name = arg; break; - case ARG_NO_OUTPUT: + OPTION_LONG("no-output", NULL, "Do not print password to standard output"): arg_no_output = true; break; - case ARG_CREDENTIAL: - arg_credential_name = optarg; + OPTION_LONG("credential", "NAME", + "Credential name for ImportCredential=, LoadCredential= or SetCredential= credentials"): + arg_credential_name = arg; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Ask only our own user's agents"): arg_flags |= ASK_PASSWORD_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Ask agents of the system and of all users"): arg_flags &= ~ASK_PASSWORD_USER; break; - case 'n': + OPTION_SHORT('n', NULL, "Do not suffix password written to standard output with newline"): arg_newline = false; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (isempty(emoji) || streq(emoji, "auto")) @@ -226,8 +170,10 @@ static int parse_argv(int argc, char *argv[]) { SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, !r); } - if (argc > optind) { - arg_message = strv_join(argv + optind, " "); + char **args = option_parser_get_args(&state, argc, argv); + + if (!strv_isempty(args)) { + arg_message = strv_join(args, " "); if (!arg_message) return log_oom(); } else if (FLAGS_SET(arg_flags, ASK_PASSWORD_ECHO)) { diff --git a/src/binfmt/binfmt.c b/src/binfmt/binfmt.c index ee7d2a4d07..bdd62398e5 100644 --- a/src/binfmt/binfmt.c +++ b/src/binfmt/binfmt.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -12,8 +11,10 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "path-util.h" #include "pretty-print.h" @@ -108,88 +109,69 @@ static int cat_config(char **files) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-binfmt.service", "8", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" - "Registers binary formats with the kernel.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --cat-config Show configuration files\n" - " --tldr Show non-comment parts of configuration\n" - " --no-pager Do not pipe output into a pager\n" - " --unregister Unregister all existing entries\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n" + "\n%sRegisters binary formats with the kernel.%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_CAT_CONFIG, - ARG_TLDR, - ARG_NO_PAGER, - ARG_UNREGISTER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "cat-config", no_argument, NULL, ARG_CAT_CONFIG }, - { "tldr", no_argument, NULL, ARG_TLDR }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "unregister", no_argument, NULL, ARG_UNREGISTER }, - {} - }; - - int c; +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = {}; + const char *arg; + FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) switch (c) { - - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_CAT_CONFIG: + OPTION_COMMON_CAT_CONFIG: arg_cat_flags = CAT_CONFIG_ON; break; - case ARG_TLDR: + OPTION_COMMON_TLDR: arg_cat_flags = CAT_TLDR; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_UNREGISTER: + OPTION_LONG("unregister", NULL, "Unregister all existing entries"): arg_unregister = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if ((arg_unregister || arg_cat_flags != CAT_CONFIG_OFF) && argc > optind) + char **args = option_parser_get_args(&state, argc, argv); + + if ((arg_unregister || arg_cat_flags != CAT_CONFIG_OFF) && !strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Positional arguments are not allowed with --cat-config/--tldr or --unregister."); + *ret_args = args; return 1; } @@ -208,7 +190,8 @@ static int binfmt_mounted_and_writable_warn(void) { static int run(int argc, char *argv[]) { int r; - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -221,13 +204,13 @@ static int run(int argc, char *argv[]) { if (arg_unregister) return disable_binfmt(); - if (argc > optind) { + if (!strv_isempty(args)) { r = binfmt_mounted_and_writable_warn(); if (r <= 0) return r; - for (int i = optind; i < argc; i++) - RET_GATHER(r, apply_file(argv[i], false)); + STRV_FOREACH(f, args) + RET_GATHER(r, apply_file(*f, false)); } else { _cleanup_strv_free_ char **files = NULL; diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index c4a9eeee76..daabff405f 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" @@ -9,9 +8,11 @@ #include "efivars.h" #include "fd-util.h" #include "find-esp.h" +#include "format-table.h" #include "fs-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-util.h" #include "path-util.h" #include "pretty-print.h" @@ -34,76 +35,65 @@ typedef enum Status { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-bless-boot.service", "8", &link); if (r < 0) return log_oom(); + 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_width(options, 0, verbs, 0); + printf("%s [OPTIONS...] COMMAND\n" "\n%sMark the boot process as good or bad.%s\n" - "\nCommands:\n" - " status Show status of current boot loader entry\n" - " good Mark this boot as good\n" - " bad Mark this boot as bad\n" - " indeterminate Undo any marking as good or bad\n" - "\nOptions:\n" - " -h --help Show this help\n" - " --version Print version\n" - " --path=PATH Path to the $BOOT partition (may be used multiple times)\n" - "\nSee the %s for details.\n", + "\nCommands:\n", program_invocation_short_name, ansi_highlight(), - 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_PATH = 0x100, - ARG_VERSION, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "path", required_argument, NULL, ARG_PATH }, - {} - }; - - 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, "h", options, NULL)) >= 0) - switch (c) { + OptionParser state = {}; + const char *arg; - case 'h': + FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + switch (c) { + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_PATH: - r = strv_extend(&arg_path, optarg); + OPTION_LONG("path", "PATH", "Path to the $BOOT partition (may be used multiple times)"): + r = strv_extend(&arg_path, arg); if (r < 0) return log_oom(); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state, argc, argv); return 1; } @@ -344,6 +334,7 @@ static int make_bad(const char *prefix, uint64_t done, const char *suffix, char return 0; } +VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, "Show status of current boot loader entry"); static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL; uint64_t left, done; @@ -451,6 +442,12 @@ static int rename_in_dir_idempotent(int fd, const char *from, const char *to) { return 1; } +VERB_FULL(verb_set, "good", NULL, VERB_ANY, 1, 0, STATUS_GOOD, + "Mark this boot as good"); +VERB_FULL(verb_set, "bad", NULL, VERB_ANY, 1, 0, STATUS_BAD, + "Mark this boot as bad"); +VERB_FULL(verb_set, "indeterminate", NULL, VERB_ANY, 1, 0, STATUS_INDETERMINATE, + "Undo any marking as good or bad"); static int verb_set(int argc, char *argv[], uintptr_t data, void *userdata) { _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL; const char *target, *source1, *source2; @@ -561,20 +558,12 @@ exists: } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "good", VERB_ANY, 1, 0, verb_set, STATUS_GOOD }, - { "bad", VERB_ANY, 1, 0, verb_set, STATUS_BAD }, - { "indeterminate", VERB_ANY, 1, 0, verb_set, STATUS_INDETERMINATE }, - {} - }; - + char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -586,7 +575,7 @@ static int run(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Marking a boot is only supported on EFI systems."); - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index 39004183d9..1941e88d37 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -143,6 +143,7 @@ #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/growfs/growfs.c b/src/growfs/growfs.c index d991b82d67..8481257ab7 100644 --- a/src/growfs/growfs.c +++ b/src/growfs/growfs.c @@ -189,7 +189,7 @@ static int parse_argv(int argc, char *argv[]) { if (optind + 1 != argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s excepts exactly one argument (the mount point).", + "%s expects exactly one argument (the mount point).", program_invocation_short_name); arg_target = argv[optind]; diff --git a/src/shared/options.c b/src/shared/options.c index 903ed62440..8ea22c9b7a 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -21,7 +21,8 @@ static bool option_arg_required(const Option *opt) { static bool option_is_metadata(const Option *opt) { /* A metadata entry that is not a real option, like the group marker */ - return FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_GROUP_MARKER); + return FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_GROUP_MARKER) || + FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_HELP_ENTRY); } static void kill_arg(char* argv[], int argc, int index) { @@ -240,7 +241,7 @@ int option_parse( return option->id; } -char** option_parser_get_args(OptionParser *state, int argc, char *argv[]) { +char** option_parser_get_args(const OptionParser *state, int argc, char *argv[]) { /* Returns positional args as a strv. * If "--" was found, it has been removed. */ @@ -274,8 +275,6 @@ int _option_parser_get_help_table( if (group_marker) break; /* End of group */ - assert(!option_is_metadata(opt)); - if (!opt->help) /* No help string — we do not show the option */ continue; diff --git a/src/shared/options.h b/src/shared/options.h index f548538bd0..fd2d048db0 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -2,11 +2,13 @@ #pragma once #include "shared-forward.h" +#include "strv.h" typedef enum OptionFlags { OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */ OPTION_STOPS_PARSING = 1U << 1, /* This option acts like "--" */ OPTION_GROUP_MARKER = 1U << 2, /* Fake option entry to separate groups */ + OPTION_HELP_ENTRY = 1U << 3, /* Fake option entry to insert an additional help line */ } OptionFlags; typedef struct Option { @@ -43,7 +45,9 @@ typedef struct Option { #define OPTION_FULL(fl, sc, lc, mv, h) _OPTION(__COUNTER__, fl, sc, lc, mv, h) #define OPTION(sc, lc, mv, h) OPTION_FULL(/* fl= */ 0, sc, lc, mv, h) #define OPTION_LONG(lc, mv, h) OPTION(/* sc= */ 0, lc, mv, h) +#define OPTION_LONG_FLAGS(fl, lc, mv, h) OPTION_FULL(fl, /* sc= */ 0, lc, mv, h) #define OPTION_SHORT(sc, mv, h) OPTION(sc, /* lc= */ NULL, mv, h) +#define OPTION_SHORT_FLAGS(fl, sc, mv, h) OPTION_FULL(fl, sc, /* lc= */ NULL, mv, h) #define OPTION_COMMON_HELP \ OPTION('h', "help", NULL, "Show this help") @@ -53,6 +57,10 @@ typedef struct Option { OPTION_LONG("no-pager", NULL, "Do not start a pager") #define OPTION_COMMON_NO_LEGEND \ OPTION_LONG("no-legend", NULL, "Do not show headers and footers") +#define OPTION_COMMON_CAT_CONFIG \ + 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_JSON \ OPTION_LONG("json", "FORMAT", "Generate JSON output (pretty, short, or off)") @@ -89,7 +97,11 @@ int option_parse( #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) -char** option_parser_get_args(OptionParser *state, int argc, char *argv[]); +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)); +} + int _option_parser_get_help_table( const Option options[], const Option options_end[], diff --git a/src/shared/verbs.c b/src/shared/verbs.c index bf2d440bb0..47f219fab6 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -160,6 +160,9 @@ int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **re for (const Verb *verb = verbs; verb < verbs_end; verb++) { assert(verb->dispatch); + if (!verb->help) + continue; + /* We indent the option string by two spaces. We could set the minimum cell width and * right-align for a similar result, but that'd be more work. This is only used for * display. */ @@ -170,8 +173,7 @@ int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **re if (r < 0) return table_log_add_error(r); - const char *help = verb->help ?: "FIXME"; - _cleanup_strv_free_ char **s = strv_split(help, /* separators= */ NULL); + _cleanup_strv_free_ char **s = strv_split(verb->help, /* separators= */ NULL); if (!s) return log_oom(); diff --git a/src/shared/verbs.h b/src/shared/verbs.h index 062e49466f..7980db62fe 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -21,13 +21,15 @@ typedef struct { } 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 \ _section_("SYSTEMD_VERBS") \ _alignptr_ \ _used_ \ _retain_ \ _variable_no_sanitize_address_ \ - static const Verb CONCATENATE(d, _data) = { \ + static const Verb CONCATENATE3(d, _data_, __COUNTER__) = { \ .verb = v, \ .min_args = amin, \ .max_args = amax, \ @@ -66,8 +68,15 @@ int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **re #define verbs_get_help_table(ret) \ _verbs_get_help_table(ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, ret) -#define VERB_COMMON_HELP(impl) \ - VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, "Show this help"); \ +#define _VERB_COMMON_HELP_IMPL(impl) \ static int verb_help(int argc, char **argv, uintptr_t data, void *userdata) { \ return impl(); \ } + +#define VERB_COMMON_HELP(impl) \ + VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, "Show this help"); \ + _VERB_COMMON_HELP_IMPL(impl) + +#define VERB_COMMON_HELP_HIDDEN(impl) \ + VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, NULL); \ + _VERB_COMMON_HELP_IMPL(impl) diff --git a/src/test/test-options.c b/src/test/test-options.c index ab3a429369..201d16d51f 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -57,6 +57,8 @@ static void test_option_parse_one( char **args = option_parser_get_args(&state, argc, argv); 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)); } static void test_option_invalid_one( diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index c50acca045..03e5479aae 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -1,16 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" +#include "build.h" #include "chase.h" #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "label-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "string-util.h" @@ -61,65 +63,56 @@ static int save_timestamp(const char *dir, struct timespec *ts) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-update-done", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...]\n\n" - "%5$sMark /etc/ and /var/ as fully updated.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --root=PATH Operate on root directory PATH\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...]\n" + "\n%sMark /etc/ and /var/ as fully updated.%s\n" + "\n%sOptions:%s\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + ansi_underline(), + 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_ROOT = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "root", required_argument, NULL, ARG_ROOT }, - {}, - }; - - int r, c; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = {}; + const char *arg; + FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) switch (c) { - - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); + OPTION_COMMON_VERSION: + return version(); + + OPTION_LONG("root", "PATH", "Operate on root directory PATH"): + r = parse_path_argument(arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state, argc, argv) > 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 74a784d8ab..c507e4d98a 100644 --- a/src/validatefs/validatefs.c +++ b/src/validatefs/validatefs.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-device.h" #include "alloc-util.h" @@ -12,11 +10,13 @@ #include "device-util.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" #include "gpt.h" #include "initrd-util.h" #include "log.h" #include "main-func.h" #include "mountpoint-util.h" +#include "options.h" #include "parse-argument.h" #include "path-util.h" #include "pretty-print.h" @@ -32,93 +32,77 @@ STATIC_DESTRUCTOR_REGISTER(arg_target, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); static int help(void) { + _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - _cleanup_free_ char *link = NULL; r = terminal_urlify_man("systemd-validatefs@.service", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] /path/to/mountpoint\n" - "\n%3$sCheck file system validation constraints.%4$s\n\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - " --root=PATH|auto Operate relative to the specified path\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] /path/to/mountpoint\n" + "\n%sCheck file system validation constraints.%s\n" + "\nOptions:\n", program_invocation_short_name, - link, 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_ROOT, - }; - - int c, r; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version" , no_argument, NULL, ARG_VERSION }, - { "root", required_argument, NULL, ARG_ROOT }, - {} - }; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = {}; + const char *arg; + + FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ROOT: - if (streq(optarg, "auto")) { - arg_root = mfree(arg_root); + OPTION_LONG("root", "PATH|auto", "Operate relative to the specified path"): + if (streq(arg, "auto")) + r = free_and_strdup_warn(&arg_root, in_initrd() ? "/sysroot" : NULL); + else { + if (!path_is_absolute(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--root= argument must be 'auto' or absolute path, got: %s", arg); - if (in_initrd()) { - arg_root = strdup("/sysroot"); - if (!arg_root) - return log_oom(); - } - - break; + r = parse_path_argument(arg, /* suppress_root= */ true, &arg_root); } - - if (!path_is_absolute(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--root= argument must be 'auto' or absolute path, got: %s", optarg); - - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind + 1 != argc) + char **args = option_parser_get_args(&state, argc, argv); + + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s excepts exactly one argument (the mount point).", + "%s expects exactly one argument (the mount point).", program_invocation_short_name); - arg_target = strdup(argv[optind]); + arg_target = strdup(args[0]); if (!arg_target) return log_oom(); if (arg_root && !path_startswith(arg_target, arg_root)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path '%s' does not start with specified root '%s', refusing.", arg_target, arg_root); - + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Specified path '%s' does not start with specified root '%s', refusing.", + arg_target, arg_root); return 1; } diff --git a/test/test-network/conf/25-routing-policy-rule-test1.network b/test/test-network/conf/25-routing-policy-rule-test1.network index 66ea59d3a9..4c83fd5fb7 100644 --- a/test/test-network/conf/25-routing-policy-rule-test1.network +++ b/test/test-network/conf/25-routing-policy-rule-test1.network @@ -67,7 +67,7 @@ Priority=202 Table=22 # The four routing policy rules below intentionally have the same config -# excepts for their To= addresses. See issue #35874. +# except for their To= addresses. See issue #35874. [RoutingPolicyRule] To=192.0.2.0/26 Table=1001