diff --git a/man/systemd-cgls.xml b/man/systemd-cgls.xml index 5280992c8c..60cf6fa41a 100644 --- a/man/systemd-cgls.xml +++ b/man/systemd-cgls.xml @@ -114,22 +114,23 @@ + - Controls whether to include information about extended attributes of the listed - control groups in the output. With the long option, expects a boolean value. Defaults to no. + control groups in the output. With the long option only, optionally accepts a boolean value. Defaults + to no. + - Controls whether to include the numeric ID of the listed control groups in the - output. With the long option, expects a boolean value. Defaults to no. + output. With the long option only, optionally accepts a boolean value. Defaults to no. diff --git a/src/cgls/cgls.c b/src/cgls/cgls.c index c224a892e4..60d8e77012 100644 --- a/src/cgls/cgls.c +++ b/src/cgls/cgls.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" @@ -10,8 +9,10 @@ #include "bus-util.h" #include "cgroup-show.h" #include "cgroup-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "output-mode.h" #include "pager.h" #include "parse-util.h" @@ -35,155 +36,132 @@ static char **arg_names = NULL; static int arg_full = -1; static const char* arg_machine = NULL; -STATIC_DESTRUCTOR_REGISTER(arg_names, freep); /* don't free the strings */ +STATIC_DESTRUCTOR_REGISTER(arg_names, strv_freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-cgls", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] [CGROUP...]\n\n" - "Recursively show control group contents.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " -a --all Show all groups, including empty\n" - " -u --unit Show the subtrees of specified system units\n" - " --user-unit Show the subtrees of specified user units\n" - " -x --xattr=BOOL Show cgroup extended attributes\n" - " -c --cgroup-id=BOOL Show cgroup ID\n" - " -l --full Do not ellipsize output\n" - " -k Include kernel threads in output\n" - " -M --machine=NAME Show container NAME\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...] [CGROUP...]\n\n" + "%sRecursively show control group contents.%s\n\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_NO_PAGER = 0x100, - ARG_VERSION, - ARG_USER_UNIT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "all", no_argument, NULL, 'a' }, - { "full", no_argument, NULL, 'l' }, - { "machine", required_argument, NULL, 'M' }, - { "unit", optional_argument, NULL, 'u' }, - { "user-unit", optional_argument, NULL, ARG_USER_UNIT }, - { "xattr", required_argument, NULL, 'x' }, - { "cgroup-id", required_argument, NULL, 'c' }, - {} - }; - - int c, r; - assert(argc >= 1); 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, "-hkalM:u::xc", options, NULL)) >= 0) + OptionParser state = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case 'a': + OPTION('a', "all", NULL, "Show all groups, including empty"): arg_output_flags |= OUTPUT_SHOW_ALL; break; - case 'u': + OPTION_FULL(OPTION_OPTIONAL_ARG, 'u', "unit", "UNIT", + "Show the subtrees of specified system units"): if (arg_show_unit == SHOW_UNIT_USER) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --unit with --user-unit."); arg_show_unit = SHOW_UNIT_SYSTEM; - if (strv_push(&arg_names, optarg) < 0) /* push optarg if not empty */ + if (strv_extend(&arg_names, arg) < 0) /* push arg if not empty */ return log_oom(); break; - case ARG_USER_UNIT: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "user-unit", "UNIT", + "Show the subtrees of specified user units"): if (arg_show_unit == SHOW_UNIT_SYSTEM) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --user-unit with --unit."); arg_show_unit = SHOW_UNIT_USER; - if (strv_push(&arg_names, optarg) < 0) /* push optarg if not empty */ + if (strv_extend(&arg_names, arg) < 0) /* push arg if not empty */ return log_oom(); break; - case 1: - /* positional argument */ - if (strv_push(&arg_names, optarg) < 0) - return log_oom(); - break; - - case 'l': - arg_full = true; - break; - - case 'k': - arg_output_flags |= OUTPUT_KERNEL_THREADS; - break; - - case 'M': - arg_machine = optarg; - break; - - case 'x': - if (optarg) { - r = parse_boolean(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "xattr", "BOOL", + "Show cgroup extended attributes"): {} + OPTION_SHORT('x', NULL, "Same as --xattr=true"): + if (arg) { + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --xattr= value: %s", optarg); + return log_error_errno(r, "Failed to parse --xattr= value: %s", arg); } else r = true; SET_FLAG(arg_output_flags, OUTPUT_CGROUP_XATTRS, r); break; - case 'c': - if (optarg) { - r = parse_boolean(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "cgroup-id", "BOOL", + "Show cgroup ID"): {} + OPTION_SHORT('c', NULL, "Same as --cgroup-id=true"): + if (arg) { + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --cgroup-id= value: %s", optarg); + return log_error_errno(r, "Failed to parse --cgroup-id= value: %s", arg); } else r = true; SET_FLAG(arg_output_flags, OUTPUT_CGROUP_ID, r); break; - case '?': - return -EINVAL; + OPTION('l', "full", NULL, "Do not ellipsize output"): + arg_full = true; + break; - default: - assert_not_reached(); + OPTION_SHORT('k', NULL, "Include kernel threads in output"): + arg_output_flags |= OUTPUT_KERNEL_THREADS; + break; + + OPTION_COMMON_MACHINE: + arg_machine = arg; + break; + + OPTION_POSITIONAL: + if (strv_extend(&arg_names, arg) < 0) /* push arg */ + return log_oom(); + break; } if (arg_machine && arg_show_unit != SHOW_UNIT_NONE) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --unit or --user-unit with --machine=."); + assert(option_parser_get_n_args(&state) == 0); + return 1; } diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index 0c93339a02..b8194de3d3 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -10,9 +9,11 @@ #include "cgroup-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "hashmap.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" @@ -72,13 +73,15 @@ typedef enum { _CPU_INVALID = -EINVAL, } CPUType; -static unsigned arg_depth = 3; +#define DEFAULT_MAXIMUM_DEPTH 3 + +static unsigned arg_depth = DEFAULT_MAXIMUM_DEPTH; static unsigned arg_iterations = UINT_MAX; static bool arg_batch = false; static bool arg_raw = false; static usec_t arg_delay = 1*USEC_PER_SEC; -static char* arg_machine = NULL; -static char* arg_root = NULL; +static const char *arg_machine = NULL; +static const char *arg_root = NULL; static bool arg_recursive = true; static bool arg_recursive_unset = false; static PidsCount arg_count = COUNT_PIDS; @@ -687,194 +690,151 @@ static void display(Hashmap *a) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-cgtop", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] [CGROUP]\n\n" - "Show top control groups by their resource usage.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - - " --order=path|tasks|cpu|memory|io\n" - " Order by specified property\n" - " -p Same as --order=path, order by path\n" - " -t Same as --order=tasks, order by number of\n" - " tasks/processes\n" - " -c Same as --order=cpu, order by CPU load\n" - " -m Same as --order=memory, order by memory load\n" - " -i Same as --order=io, order by IO load\n" - " -r --raw Provide raw (not human-readable) numbers\n" - " --cpu[=percentage]\n" - " Show CPU usage as percentage (default)\n" - " --cpu=time Show CPU usage as time\n" - " -P Count userspace processes instead of tasks (excl. kernel)\n" - " -k Count all processes instead of tasks (incl. kernel)\n" - " --recursive=BOOL Sum up process count recursively\n" - " -d --delay=DELAY Delay between updates\n" - " -n --iterations=N Run for N iterations before exiting\n" - " -1 Shortcut for --iterations=1\n" - " -b --batch Run in batch mode, accepting no input\n" - " --depth=DEPTH Maximum traversal depth (default: %u)\n" - " -M --machine= Show container\n" - "\nSee the %s for details.\n", + "%sShow top control groups by their resource usage.%s\n\n", program_invocation_short_name, - arg_depth, - link); + ansi_highlight(), + ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_DEPTH, - ARG_CPU_TYPE, - ARG_ORDER, - ARG_RECURSIVE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "delay", required_argument, NULL, 'd' }, - { "iterations", required_argument, NULL, 'n' }, - { "batch", no_argument, NULL, 'b' }, - { "raw", no_argument, NULL, 'r' }, - { "depth", required_argument, NULL, ARG_DEPTH }, - { "cpu", optional_argument, NULL, ARG_CPU_TYPE }, - { "order", required_argument, NULL, ARG_ORDER }, - { "recursive", required_argument, NULL, ARG_RECURSIVE }, - { "machine", required_argument, NULL, 'M' }, - {} - }; - - int c, r; - assert(argc >= 1); assert(argv); - while ((c = getopt_long(argc, argv, "hptcmin:brd:kPM:1", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_CPU_TYPE: - if (optarg) { - arg_cpu_type = cpu_type_from_string(optarg); - if (arg_cpu_type < 0) - return log_error_errno(arg_cpu_type, - "Unknown argument to --cpu=: %s", - optarg); - } else - arg_cpu_type = CPU_TIME; - - break; - - case ARG_DEPTH: - r = safe_atou(optarg, &arg_depth); - if (r < 0) - return log_error_errno(r, "Failed to parse depth parameter '%s': %m", optarg); - - break; - - case 'd': - r = parse_sec(optarg, &arg_delay); - if (r < 0) - return log_error_errno(r, "Failed to parse delay parameter '%s': %m", optarg); - if (arg_delay <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid delay parameter '%s'", - optarg); - - break; - - case 'n': - r = safe_atou(optarg, &arg_iterations); - if (r < 0) - return log_error_errno(r, "Failed to parse iterations parameter '%s': %m", optarg); - - break; - - case '1': - arg_iterations = 1; - break; - - case 'b': - arg_batch = true; - break; - - case 'r': - arg_raw = true; - break; - - case 'p': - arg_order = ORDER_PATH; - break; - - case 't': - arg_order = ORDER_TASKS; - break; - - case 'c': - arg_order = ORDER_CPU; - break; - - case 'm': - arg_order = ORDER_MEMORY; - break; - - case 'i': - arg_order = ORDER_IO; - break; - - case ARG_ORDER: - arg_order = order_from_string(optarg); + OPTION_LONG("order", "PROPERTY", + "Order by specified property (path, tasks, cpu, memory, io)"): + arg_order = order_from_string(arg); if (arg_order < 0) return log_error_errno(arg_order, "Invalid argument to --order=: %s", - optarg); + arg); break; - case 'k': - arg_count = COUNT_ALL_PROCESSES; + OPTION_SHORT('p', NULL, "Same as --order=path, order by path"): + arg_order = ORDER_PATH; break; - case 'P': + OPTION_SHORT('t', NULL, "Same as --order=tasks, order by number of tasks/processes"): + arg_order = ORDER_TASKS; + break; + + OPTION_SHORT('c', NULL, "Same as --order=cpu, order by CPU load"): + arg_order = ORDER_CPU; + break; + + OPTION_SHORT('m', NULL, "Same as --order=memory, order by memory load"): + arg_order = ORDER_MEMORY; + break; + + OPTION_SHORT('i', NULL, "Same as --order=io, order by IO load"): + arg_order = ORDER_IO; + break; + + OPTION('r', "raw", NULL, "Provide raw (not human-readable) numbers"): + arg_raw = true; + break; + + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "cpu", "percentage|time", + "Show CPU usage as percentage (default) or time"): + if (arg) { + arg_cpu_type = cpu_type_from_string(arg); + if (arg_cpu_type < 0) + return log_error_errno(arg_cpu_type, + "Unknown argument to --cpu=: %s", + arg); + } else + arg_cpu_type = CPU_TIME; + break; + + OPTION_SHORT('P', NULL, "Count userspace processes instead of tasks (excl. kernel)"): arg_count = COUNT_USERSPACE_PROCESSES; break; - case ARG_RECURSIVE: - r = parse_boolean_argument("--recursive=", optarg, &arg_recursive); + OPTION_SHORT('k', NULL, "Count all processes instead of tasks (incl. kernel)"): + arg_count = COUNT_ALL_PROCESSES; + break; + + OPTION_LONG("recursive", "BOOL", "Sum up process count recursively"): + r = parse_boolean_argument("--recursive=", arg, &arg_recursive); if (r < 0) return r; arg_recursive_unset = !r; break; - case 'M': - arg_machine = optarg; + OPTION('d', "delay", "DELAY", "Delay between updates"): + r = parse_sec(arg, &arg_delay); + if (r < 0) + return log_error_errno(r, "Failed to parse delay parameter '%s': %m", arg); + if (arg_delay <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid delay parameter '%s'", + arg); break; - case '?': - return -EINVAL; + OPTION('n', "iterations", "N", "Run for N iterations before exiting"): + r = safe_atou(arg, &arg_iterations); + if (r < 0) + return log_error_errno(r, "Failed to parse iterations parameter '%s': %m", arg); + break; - default: - assert_not_reached(); + OPTION_SHORT('1', NULL, "Shortcut for --iterations=1"): + arg_iterations = 1; + break; + + OPTION('b', "batch", NULL, "Run in batch mode, accepting no input"): + arg_batch = true; + break; + + OPTION_LONG("depth", "DEPTH", + "Maximum traversal depth (default: "STRINGIFY(DEFAULT_MAXIMUM_DEPTH)")"): + r = safe_atou(arg, &arg_depth); + if (r < 0) + return log_error_errno(r, "Failed to parse depth parameter '%s': %m", arg); + break; + + OPTION_COMMON_MACHINE: + arg_machine = arg; + break; } - if (optind == argc - 1) - arg_root = argv[optind]; - else if (optind < argc) + size_t n_args = option_parser_get_n_args(&state); + if (n_args > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments."); + if (n_args == 1) + arg_root = option_parser_get_args(&state)[0]; return 1; } diff --git a/src/creds/creds.c b/src/creds/creds.c index 3b1cc8e86b..6988d39ca4 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -24,6 +23,7 @@ #include "log.h" #include "main-func.h" #include "memory-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -305,6 +305,8 @@ static int add_credentials_to_table(Table *t, bool encrypted) { return 1; /* Creds dir set */ } +VERB(verb_list, "list", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Show list of passed credentials"); static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *t = NULL; int r, q; @@ -465,6 +467,8 @@ static int write_blob(FILE *f, const void *data, size_t size) { return 0; } +VERB(verb_cat, "cat", "CREDENTIAL...", 2, VERB_ANY, 0, + "Show contents of specified credentials"); static int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata) { usec_t timestamp; int r, ret = 0; @@ -551,6 +555,8 @@ static int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata) { return ret; } +VERB(verb_encrypt, "encrypt", "INPUT OUTPUT", 3, 3, 0, + "Encrypt plaintext credential file and write to ciphertext credential file"); static int verb_encrypt(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(iovec_done_erase) struct iovec plaintext = {}, output = {}; _cleanup_free_ char *base64_buf = NULL, *fname = NULL; @@ -659,6 +665,8 @@ static int verb_encrypt(int argc, char *argv[], uintptr_t _data, void *userdata) return EXIT_SUCCESS; } +VERB(verb_decrypt, "decrypt", "INPUT [OUTPUT]", 2, 3, 0, + "Decrypt ciphertext credential file and write to plaintext credential file"); static int verb_decrypt(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(iovec_done_erase) struct iovec input = {}, plaintext = {}; _cleanup_free_ char *fname = NULL; @@ -741,6 +749,8 @@ static int verb_decrypt(int argc, char *argv[], uintptr_t _data, void *userdata) return EXIT_SUCCESS; } +VERB_NOARG(verb_setup, "setup", + "Generate credentials host key, if not existing yet"); static int verb_setup(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(iovec_done_erase) struct iovec host_key = {}; int r; @@ -754,212 +764,169 @@ static int verb_setup(int argc, char *argv[], uintptr_t _data, void *userdata) { return EXIT_SUCCESS; } +/* For backward compatibility. Hidden from help. */ +VERB(verb_has_tpm2, "has-tpm2", NULL, VERB_ANY, 1, 0, /* help= */ NULL); static int verb_has_tpm2(int argc, char *argv[], uintptr_t _data, void *userdata) { if (!arg_quiet) - log_notice("The 'systemd-creds %1$s' command has been replaced by 'systemd-analyze %1$s'. Redirecting invocation.", argv[optind]); + log_notice("The 'systemd-creds %1$s' command has been replaced by 'systemd-analyze %1$s'. Redirecting invocation.", argv[0]); return verb_has_tpm2_generic(arg_quiet); } static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-creds", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sDisplay and Process Credentials.%6$s\n" - "\n%3$sCommands:%4$s\n" - " list Show list of passed credentials\n" - " cat CREDENTIAL... Show contents of specified credentials\n" - " setup Generate credentials host key, if not existing yet\n" - " encrypt INPUT OUTPUT Encrypt plaintext credential file and write to\n" - " ciphertext credential file\n" - " decrypt INPUT [OUTPUT] Decrypt ciphertext credential file and write to\n" - " plaintext credential file\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " --system Show credentials passed to system\n" - " --transcode=base64|unbase64|hex|unhex\n" - " Transcode credential data\n" - " --newline=auto|yes|no\n" - " Suffix output with newline\n" - " -p --pretty Output as SetCredentialEncrypted= line\n" - " --name=NAME Override filename included in encrypted credential\n" - " --timestamp=TIME Include specified timestamp in encrypted credential\n" - " --not-after=TIME Include specified invalidation time in encrypted\n" - " credential\n" - " --with-key=host|tpm2|host+tpm2|null|auto|auto-initrd\n" - " Which keys to encrypt with\n" - " -H Shortcut for --with-key=host\n" - " -T Shortcut for --with-key=tpm2\n" - " --tpm2-device=PATH\n" - " Pick TPM2 device\n" - " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n" - " Specify TPM2 PCRs to seal against (fixed hash)\n" - " --tpm2-public-key=PATH\n" - " Specify PEM certificate to seal against\n" - " --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n" - " Specify TPM2 PCRs to seal against (public key)\n" - " --tpm2-signature=PATH\n" - " Specify signature for public key PCR policy\n" - " --user Select user-scoped credential encryption\n" - " --uid=UID Select user for scoped credentials\n" - " --allow-null Allow decrypting credentials with null key\n" - " --refuse-null Refuse decrypting credentials with null key\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sDisplay and Process Credentials.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_JSON, - ARG_SYSTEM, - ARG_TRANSCODE, - ARG_NEWLINE, - ARG_WITH_KEY, - ARG_TPM2_DEVICE, - ARG_TPM2_PCRS, - ARG_TPM2_PUBLIC_KEY, - ARG_TPM2_PUBLIC_KEY_PCRS, - ARG_TPM2_SIGNATURE, - ARG_NAME, - ARG_TIMESTAMP, - ARG_NOT_AFTER, - ARG_USER, - ARG_UID, - ARG_ALLOW_NULL, - ARG_REFUSE_NULL, - ARG_NO_ASK_PASSWORD, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "json", required_argument, NULL, ARG_JSON }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "transcode", required_argument, NULL, ARG_TRANSCODE }, - { "newline", required_argument, NULL, ARG_NEWLINE }, - { "pretty", no_argument, NULL, 'p' }, - { "with-key", required_argument, NULL, ARG_WITH_KEY }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, - { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY }, - { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS }, - { "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE }, - { "name", required_argument, NULL, ARG_NAME }, - { "timestamp", required_argument, NULL, ARG_TIMESTAMP }, - { "not-after", required_argument, NULL, ARG_NOT_AFTER }, - { "quiet", no_argument, NULL, 'q' }, - { "user", no_argument, NULL, ARG_USER }, - { "uid", required_argument, NULL, ARG_UID }, - { "allow-null", no_argument, NULL, ARG_ALLOW_NULL }, - { "refuse-null", no_argument, NULL, ARG_REFUSE_NULL }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - {} - }; - - int c, r; +VERB_COMMON_HELP(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hHTpq", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; 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 ARG_SYSTEM: + OPTION_LONG("system", NULL, "Show credentials passed to system"): arg_system = true; break; - case ARG_TRANSCODE: - if (streq(optarg, "help")) { + OPTION_LONG("transcode", "METHOD", + "Transcode credential data (base64, unbase64, hex, unhex)"): + if (streq(arg, "help")) { if (arg_legend) puts("Supported transcode types:"); return DUMP_STRING_TABLE(transcode_mode, TranscodeMode, _TRANSCODE_MAX); } - if (parse_boolean(optarg) == 0) /* If specified as "false", turn transcoding off */ + if (parse_boolean(arg) == 0) /* If specified as "false", turn transcoding off */ arg_transcode = TRANSCODE_OFF; else { TranscodeMode m; - m = transcode_mode_from_string(optarg); + m = transcode_mode_from_string(arg); if (m < 0) return log_error_errno(m, "Failed to parse transcode mode: %m"); arg_transcode = m; } - break; - case ARG_NEWLINE: - r = parse_tristate_argument_with_auto("--newline=", optarg, &arg_newline); + OPTION_LONG("newline", "auto|yes|no", "Suffix output with newline"): + r = parse_tristate_argument_with_auto("--newline=", arg, &arg_newline); if (r < 0) return r; break; - case 'p': + OPTION('p', "pretty", NULL, "Output as SetCredentialEncrypted= line"): arg_pretty = true; break; - case ARG_WITH_KEY: - if (streq(optarg, "help")) { + OPTION_LONG("name", "NAME", + "Override filename included in encrypted credential"): + if (isempty(arg)) { + arg_name = NULL; + arg_name_any = true; + break; + } + + if (!credential_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", arg); + + arg_name = arg; + arg_name_any = false; + break; + + OPTION_LONG("timestamp", "TIME", + "Include specified timestamp in encrypted credential"): + r = parse_timestamp(arg, &arg_timestamp); + if (r < 0) + return log_error_errno(r, "Failed to parse timestamp: %s", arg); + break; + + OPTION_LONG("not-after", "TIME", + "Include specified invalidation time in encrypted credential"): + r = parse_timestamp(arg, &arg_not_after); + if (r < 0) + return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", arg); + break; + + OPTION_LONG("with-key", "KEY", + "Which keys to encrypt with (host, tpm2, host+tpm2, null, auto, auto-initrd)"): + if (streq(arg, "help")) { if (arg_legend) puts("Supported key types:"); return DUMP_STRING_TABLE(cred_key_type, CredKeyType, _CRED_KEY_TYPE_MAX); } - if (isempty(optarg)) + if (isempty(arg)) arg_with_key = _CRED_AUTO; else { - CredKeyType t = cred_key_type_from_string(optarg); + CredKeyType t = cred_key_type_from_string(arg); if (t < 0) return log_error_errno(t, "Failed to parse key type: %m"); @@ -967,90 +934,63 @@ static int parse_argv(int argc, char *argv[]) { } break; - case 'H': + OPTION_SHORT('H', NULL, "Shortcut for --with-key=host"): arg_with_key = CRED_AES256_GCM_BY_HOST; break; - case 'T': + OPTION_SHORT('T', NULL, "Shortcut for --with-key=tpm2"): arg_with_key = _CRED_AUTO_TPM2; break; - case ARG_TPM2_DEVICE: - if (streq(optarg, "list")) + OPTION_LONG("tpm2-device", "PATH", "Pick TPM2 device"): + if (streq(arg, "list")) return tpm2_list_devices(arg_legend, arg_quiet); - arg_tpm2_device = streq(optarg, "auto") ? NULL : optarg; + arg_tpm2_device = streq(arg, "auto") ? NULL : arg; break; - case ARG_TPM2_PCRS: /* For fixed hash PCR policies only */ - r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_pcr_mask); + OPTION_LONG("tpm2-pcrs", "PCR1+PCR2+PCR3+…", + "Specify TPM2 PCRs to seal against (fixed hash)"): + /* For fixed hash PCR policies only */ + r = tpm2_parse_pcr_argument_to_mask(arg, &arg_tpm2_pcr_mask); if (r < 0) return r; - break; - case ARG_TPM2_PUBLIC_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key); + OPTION_LONG("tpm2-public-key", "PATH", + "Specify PEM certificate to seal against"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_public_key); if (r < 0) return r; - break; - case ARG_TPM2_PUBLIC_KEY_PCRS: /* For public key PCR policies only */ - r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); + OPTION_LONG("tpm2-public-key-pcrs", "PCR1+PCR2+…", + "Specify TPM2 PCRs to seal against (public key)"): + /* For public key PCR policies only */ + r = tpm2_parse_pcr_argument_to_mask(arg, &arg_tpm2_public_key_pcr_mask); if (r < 0) return r; - break; - case ARG_TPM2_SIGNATURE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_signature); + OPTION_LONG("tpm2-signature", "PATH", + "Specify signature for public key PCR policy"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_signature); if (r < 0) return r; - break; - case ARG_NAME: - if (isempty(optarg)) { - arg_name = NULL; - arg_name_any = true; - break; - } - - if (!credential_name_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", optarg); - - arg_name = optarg; - arg_name_any = false; - break; - - case ARG_TIMESTAMP: - r = parse_timestamp(optarg, &arg_timestamp); - if (r < 0) - return log_error_errno(r, "Failed to parse timestamp: %s", optarg); - - break; - - case ARG_NOT_AFTER: - r = parse_timestamp(optarg, &arg_not_after); - if (r < 0) - return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", optarg); - - break; - - case ARG_USER: + OPTION_LONG("user", NULL, "Select user-scoped credential encryption"): if (!uid_is_valid(arg_uid)) arg_uid = getuid(); - break; - case ARG_UID: - if (isempty(optarg)) + OPTION_LONG("uid", "UID", "Select user for scoped credentials"): + if (isempty(arg)) arg_uid = UID_INVALID; - else if (streq(optarg, "self")) + else if (streq(arg, "self")) arg_uid = getuid(); else { - const char *name = optarg; + const char *name = arg; r = get_user_creds( &name, @@ -1061,35 +1001,30 @@ static int parse_argv(int argc, char *argv[]) { /* flags= */ 0); if (r < 0) return log_error_errno(r, "Failed to resolve user '%s': %s", - optarg, STRERROR_USER(r)); + arg, STRERROR_USER(r)); } break; - case ARG_ALLOW_NULL: + OPTION_LONG("allow-null", NULL, + "Allow decrypting credentials with null key"): arg_credential_flags &= ~CREDENTIAL_REFUSE_NULL; arg_credential_flags |= CREDENTIAL_ALLOW_NULL; break; - case ARG_REFUSE_NULL: + OPTION_LONG("refuse-null", NULL, + "Refuse decrypting credentials with null key"): arg_credential_flags |= CREDENTIAL_REFUSE_NULL; arg_credential_flags &= ~CREDENTIAL_ALLOW_NULL; break; - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress informational messages"): arg_quiet = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } SET_FLAG(arg_credential_flags, CREDENTIAL_IPC_ALLOW_INTERACTIVE, arg_ask_password); @@ -1118,25 +1053,10 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); arg_varlink = r; + *ret_args = option_parser_get_args(&state); return 1; } -static int creds_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list }, - { "cat", 2, VERB_ANY, 0, verb_cat }, - { "encrypt", 3, 3, 0, verb_encrypt }, - { "decrypt", 2, 3, 0, verb_decrypt }, - { "setup", VERB_ANY, 1, 0, verb_setup }, - { "help", VERB_ANY, 1, 0, verb_help }, - { "has-tpm2", VERB_ANY, 1, 0, verb_has_tpm2 }, /* for backward compatibility */ - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - #define TIMESTAMP_FRESH_MAX (30*USEC_PER_SEC) static bool timestamp_is_fresh(usec_t x) { @@ -1552,14 +1472,15 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; if (arg_varlink) return vl_server(); - return creds_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 8cb81e7f06..ea4dd5f184 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "sd-bus.h" @@ -24,6 +23,7 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "glyph-util.h" #include "hostname-util.h" @@ -38,9 +38,9 @@ #include "main-func.h" #include "memory-util.h" #include "mount-util.h" +#include "options.h" #include "os-util.h" #include "parse-argument.h" -#include "parse-util.h" #include "password-quality-util.h" #include "path-util.h" #include "plymouth-util.h" @@ -1240,380 +1240,252 @@ static int process_reset(int rfd) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-firstboot", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...]\n" - "\n%3$sConfigures basic settings of the system.%4$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY Specify disk image dissection policy\n" - " --locale=LOCALE Set primary locale (LANG=)\n" - " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n" - " --keymap=KEYMAP Set keymap\n" - " --timezone=TIMEZONE Set timezone\n" - " --hostname=NAME Set hostname\n" - " --setup-machine-id Set a random machine ID\n" - " --machine-id=ID Set specified machine ID\n" - " --root-password=PASSWORD Set root password from plaintext password\n" - " --root-password-file=FILE Set root password from file\n" - " --root-password-hashed=HASH Set root password from hashed password\n" - " --root-shell=SHELL Set root shell\n" - " --kernel-command-line=CMDLINE\n" - " Set kernel command line\n" - " --prompt-locale Prompt the user for locale settings\n" - " --prompt-keymap Prompt the user for keymap settings\n" - " --prompt-keymap-auto Prompt the user for keymap settings if invoked\n" - " on local console\n" - " --prompt-timezone Prompt the user for timezone\n" - " --prompt-hostname Prompt the user for hostname\n" - " --prompt-root-password Prompt the user for root password\n" - " --prompt-root-shell Prompt the user for root shell\n" - " --prompt Prompt for all of the above\n" - " --copy-locale Copy locale from host\n" - " --copy-keymap Copy keymap from host\n" - " --copy-timezone Copy timezone from host\n" - " --copy-root-password Copy root password from host\n" - " --copy-root-shell Copy root shell from host\n" - " --copy Copy locale, keymap, timezone, root password\n" - " --force Overwrite existing files\n" - " --delete-root-password Delete root password\n" - " --welcome=no Disable the welcome text\n" - " --chrome=no Don't show color bar at top and bottom of\n" - " terminal\n" - " --mute-console=yes Tell kernel/PID 1 to not write to the console\n" - " while running\n" - " --reset Remove existing files\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n\n" + "%sConfigures basic settings of the system.%s\n\n", program_invocation_short_name, - link, ansi_highlight(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_LOCALE, - ARG_LOCALE_MESSAGES, - ARG_KEYMAP, - ARG_TIMEZONE, - ARG_HOSTNAME, - ARG_SETUP_MACHINE_ID, - ARG_MACHINE_ID, - ARG_ROOT_PASSWORD, - ARG_ROOT_PASSWORD_FILE, - ARG_ROOT_PASSWORD_HASHED, - ARG_ROOT_SHELL, - ARG_KERNEL_COMMAND_LINE, - ARG_PROMPT, - ARG_PROMPT_LOCALE, - ARG_PROMPT_KEYMAP, - ARG_PROMPT_KEYMAP_AUTO, - ARG_PROMPT_TIMEZONE, - ARG_PROMPT_HOSTNAME, - ARG_PROMPT_ROOT_PASSWORD, - ARG_PROMPT_ROOT_SHELL, - ARG_COPY, - ARG_COPY_LOCALE, - ARG_COPY_KEYMAP, - ARG_COPY_TIMEZONE, - ARG_COPY_ROOT_PASSWORD, - ARG_COPY_ROOT_SHELL, - ARG_FORCE, - ARG_DELETE_ROOT_PASSWORD, - ARG_WELCOME, - ARG_CHROME, - ARG_RESET, - ARG_MUTE_CONSOLE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "locale", required_argument, NULL, ARG_LOCALE }, - { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES }, - { "keymap", required_argument, NULL, ARG_KEYMAP }, - { "timezone", required_argument, NULL, ARG_TIMEZONE }, - { "hostname", required_argument, NULL, ARG_HOSTNAME }, - { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID }, - { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, - { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD }, - { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE }, - { "root-password-hashed", required_argument, NULL, ARG_ROOT_PASSWORD_HASHED }, - { "root-shell", required_argument, NULL, ARG_ROOT_SHELL }, - { "kernel-command-line", required_argument, NULL, ARG_KERNEL_COMMAND_LINE }, - { "prompt", no_argument, NULL, ARG_PROMPT }, - { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE }, - { "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP }, - { "prompt-keymap-auto", no_argument, NULL, ARG_PROMPT_KEYMAP_AUTO }, - { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE }, - { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME }, - { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD }, - { "prompt-root-shell", no_argument, NULL, ARG_PROMPT_ROOT_SHELL }, - { "copy", no_argument, NULL, ARG_COPY }, - { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE }, - { "copy-keymap", no_argument, NULL, ARG_COPY_KEYMAP }, - { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE }, - { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD }, - { "copy-root-shell", no_argument, NULL, ARG_COPY_ROOT_SHELL }, - { "force", no_argument, NULL, ARG_FORCE }, - { "delete-root-password", no_argument, NULL, ARG_DELETE_ROOT_PASSWORD }, - { "welcome", required_argument, NULL, ARG_WELCOME }, - { "chrome", required_argument, NULL, ARG_CHROME }, - { "reset", no_argument, NULL, ARG_RESET }, - { "mute-console", required_argument, NULL, ARG_MUTE_CONSOLE }, - {} - }; - - int r, c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ROOT: - r = parse_path_argument(optarg, true, &arg_root); + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(arg, true, &arg_root); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, false, &arg_image); + OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): + r = parse_path_argument(arg, false, &arg_image); if (r < 0) return r; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_LOCALE: - r = free_and_strdup(&arg_locale, optarg); + OPTION_LONG("locale", "LOCALE", "Set primary locale (LANG=)"): + r = free_and_strdup_warn(&arg_locale, arg); if (r < 0) - return log_oom(); - + return r; break; - case ARG_LOCALE_MESSAGES: - r = free_and_strdup(&arg_locale_messages, optarg); + OPTION_LONG("locale-messages", "LOCALE", "Set message locale (LC_MESSAGES=)"): + r = free_and_strdup_warn(&arg_locale_messages, arg); if (r < 0) - return log_oom(); - + return r; break; - case ARG_KEYMAP: - if (!keymap_is_valid(optarg)) + OPTION_LONG("keymap", "KEYMAP", "Set keymap"): + if (!keymap_is_valid(arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Keymap %s is not valid.", optarg); + "Keymap %s is not valid.", arg); - r = free_and_strdup(&arg_keymap, optarg); + r = free_and_strdup_warn(&arg_keymap, arg); if (r < 0) - return log_oom(); - + return r; break; - case ARG_TIMEZONE: - if (!timezone_is_valid(optarg, LOG_ERR)) + OPTION_LONG("timezone", "TIMEZONE", "Set timezone"): + if (!timezone_is_valid(arg, LOG_ERR)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Timezone %s is not valid.", optarg); + "Timezone %s is not valid.", arg); - r = free_and_strdup(&arg_timezone, optarg); + r = free_and_strdup_warn(&arg_timezone, arg); if (r < 0) - return log_oom(); - + return r; break; - case ARG_ROOT_PASSWORD: - r = free_and_strdup(&arg_root_password, optarg); - if (r < 0) - return log_oom(); - - arg_root_password_is_hashed = false; - break; - - case ARG_ROOT_PASSWORD_FILE: - arg_root_password = mfree(arg_root_password); - - r = read_one_line_file(optarg, &arg_root_password); - if (r < 0) - return log_error_errno(r, "Failed to read %s: %m", optarg); - - arg_root_password_is_hashed = false; - break; - - case ARG_ROOT_PASSWORD_HASHED: - r = free_and_strdup(&arg_root_password, optarg); - if (r < 0) - return log_oom(); - - arg_root_password_is_hashed = true; - break; - - case ARG_ROOT_SHELL: - r = free_and_strdup(&arg_root_shell, optarg); - if (r < 0) - return log_oom(); - - break; - - case ARG_HOSTNAME: - if (!hostname_is_valid(optarg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK)) + OPTION_LONG("hostname", "NAME", "Set hostname"): + if (!hostname_is_valid(arg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Host name %s is not valid.", optarg); + "Host name %s is not valid.", arg); - r = free_and_strdup(&arg_hostname, optarg); + r = free_and_strdup_warn(&arg_hostname, arg); if (r < 0) - return log_oom(); + return r; hostname_cleanup(arg_hostname); break; - case ARG_SETUP_MACHINE_ID: + OPTION_LONG("setup-machine-id", NULL, "Set a random machine ID"): r = sd_id128_randomize(&arg_machine_id); if (r < 0) return log_error_errno(r, "Failed to generate randomized machine ID: %m"); - break; - case ARG_MACHINE_ID: - r = sd_id128_from_string(optarg, &arg_machine_id); + OPTION_LONG("machine-id", "ID", "Set specified machine ID"): + r = sd_id128_from_string(arg, &arg_machine_id); if (r < 0) - return log_error_errno(r, "Failed to parse machine id %s.", optarg); - + return log_error_errno(r, "Failed to parse machine id %s.", arg); break; - case ARG_KERNEL_COMMAND_LINE: - r = free_and_strdup(&arg_kernel_cmdline, optarg); + OPTION_LONG("root-password", "PASSWORD", "Set root password from plaintext password"): + r = free_and_strdup_warn(&arg_root_password, arg); if (r < 0) - return log_oom(); + return r; + arg_root_password_is_hashed = false; break; - case ARG_PROMPT: + OPTION_LONG("root-password-file", "FILE", "Set root password from file"): + arg_root_password = mfree(arg_root_password); + + r = read_one_line_file(arg, &arg_root_password); + if (r < 0) + return log_error_errno(r, "Failed to read %s: %m", arg); + + arg_root_password_is_hashed = false; + break; + + OPTION_LONG("root-password-hashed", "HASH", "Set root password from hashed password"): + r = free_and_strdup_warn(&arg_root_password, arg); + if (r < 0) + return r; + + arg_root_password_is_hashed = true; + break; + + OPTION_LONG("root-shell", "SHELL", "Set root shell"): + r = free_and_strdup_warn(&arg_root_shell, arg); + if (r < 0) + return r; + break; + + OPTION_LONG("kernel-command-line", "CMDLINE", "Set kernel command line"): + r = free_and_strdup_warn(&arg_kernel_cmdline, arg); + if (r < 0) + return r; + break; + + OPTION_LONG("prompt-locale", NULL, "Prompt the user for locale settings"): + arg_prompt_locale = true; + break; + + OPTION_LONG("prompt-keymap", NULL, "Prompt the user for keymap settings"): + arg_prompt_keymap = true; + arg_prompt_keymap_auto = false; + break; + + OPTION_LONG("prompt-keymap-auto", NULL, + "Prompt the user for keymap settings if invoked on local console"): + arg_prompt_keymap_auto = true; + break; + + OPTION_LONG("prompt-timezone", NULL, "Prompt the user for timezone"): + arg_prompt_timezone = true; + break; + + OPTION_LONG("prompt-hostname", NULL, "Prompt the user for hostname"): + arg_prompt_hostname = true; + break; + + OPTION_LONG("prompt-root-password", NULL, "Prompt the user for root password"): + arg_prompt_root_password = true; + break; + + OPTION_LONG("prompt-root-shell", NULL, "Prompt the user for root shell"): + arg_prompt_root_shell = true; + break; + + OPTION_LONG("prompt", NULL, "Prompt for all of the above"): arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = arg_prompt_root_shell = true; arg_prompt_keymap_auto = false; break; - case ARG_PROMPT_LOCALE: - arg_prompt_locale = true; + OPTION_LONG("copy-locale", NULL, "Copy locale from host"): + arg_copy_locale = true; break; - case ARG_PROMPT_KEYMAP: - arg_prompt_keymap = true; - arg_prompt_keymap_auto = false; + OPTION_LONG("copy-keymap", NULL, "Copy keymap from host"): + arg_copy_keymap = true; break; - case ARG_PROMPT_KEYMAP_AUTO: - arg_prompt_keymap_auto = true; + OPTION_LONG("copy-timezone", NULL, "Copy timezone from host"): + arg_copy_timezone = true; break; - case ARG_PROMPT_TIMEZONE: - arg_prompt_timezone = true; + OPTION_LONG("copy-root-password", NULL, "Copy root password from host"): + arg_copy_root_password = true; break; - case ARG_PROMPT_HOSTNAME: - arg_prompt_hostname = true; + OPTION_LONG("copy-root-shell", NULL, "Copy root shell from host"): + arg_copy_root_shell = true; break; - case ARG_PROMPT_ROOT_PASSWORD: - arg_prompt_root_password = true; - break; - - case ARG_PROMPT_ROOT_SHELL: - arg_prompt_root_shell = true; - break; - - case ARG_COPY: + OPTION_LONG("copy", NULL, "Copy all of the above"): arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = arg_copy_root_shell = true; break; - case ARG_COPY_LOCALE: - arg_copy_locale = true; - break; - - case ARG_COPY_KEYMAP: - arg_copy_keymap = true; - break; - - case ARG_COPY_TIMEZONE: - arg_copy_timezone = true; - break; - - case ARG_COPY_ROOT_PASSWORD: - arg_copy_root_password = true; - break; - - case ARG_COPY_ROOT_SHELL: - arg_copy_root_shell = true; - break; - - case ARG_FORCE: + OPTION_LONG("force", NULL, "Overwrite existing files"): arg_force = true; break; - case ARG_DELETE_ROOT_PASSWORD: + OPTION_LONG("delete-root-password", NULL, "Delete root password"): arg_delete_root_password = true; break; - case ARG_WELCOME: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --welcome= argument: %s", optarg); - - arg_welcome = r; - break; - - case ARG_CHROME: - r = parse_boolean_argument("--chrome=", optarg, &arg_chrome); + OPTION_LONG("welcome", "BOOL", "Whether to show the welcome text"): + r = parse_boolean_argument("--welcome=", arg, &arg_welcome); if (r < 0) return r; - break; - case ARG_RESET: + OPTION_LONG("chrome", "BOOL", + "Whether to show a color bar at top and bottom of terminal"): + r = parse_boolean_argument("--chrome=", arg, &arg_chrome); + if (r < 0) + return r; + break; + + OPTION_LONG("mute-console", "BOOL", + "Whether to disallow kernel/PID 1 writes to the console while running"): + r = parse_boolean_argument("--mute-console=", arg, &arg_mute_console); + if (r < 0) + return r; + break; + + OPTION_LONG("reset", NULL, "Remove existing files"): arg_reset = true; break; - - case ARG_MUTE_CONSOLE: - r = parse_boolean_argument("--mute-console=", optarg, &arg_mute_console); - if (r < 0) - return r; - - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_delete_root_password && (arg_copy_root_password || arg_root_password || arg_prompt_root_password)) diff --git a/src/journal/cat.c b/src/journal/cat.c index e2419d36ab..62249663c5 100644 --- a/src/journal/cat.c +++ b/src/journal/cat.c @@ -59,7 +59,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true }; + OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; const char *arg; int r; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index b4cc14d5a3..8db17968ab 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -593,7 +593,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true }; + OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; const Option *opt; const char *arg; diff --git a/src/shared/options.c b/src/shared/options.c index dc2fb34cf6..fbf9a31181 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -22,6 +22,7 @@ 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 ASSERT_PTR(opt)->flags & (OPTION_GROUP_MARKER | + OPTION_POSITIONAL_ENTRY | OPTION_HELP_ENTRY | OPTION_HELP_ENTRY_VERBATIM); } @@ -75,6 +76,8 @@ int option_parse( /* Check and initialize */ if (state->optind == 0) { + assert(state->mode >= 0 && state->mode < _OPTION_PARSER_MODE_MAX); + if (state->argc < 1 || strv_isempty(state->argv)) return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty"); @@ -87,9 +90,10 @@ int option_parse( const char *optname = NULL, *optval = NULL; _cleanup_free_ char *_optname = NULL; /* allocated option name */ bool separate_optval = false; + bool handling_positional_arg = false; if (state->short_option_offset == 0) { - /* Skip over non-option parameters */ + /* Handle non-option parameters */ for (;;) { if (state->optind == state->argc) return 0; @@ -109,19 +113,36 @@ int option_parse( /* Looks like we found an option parameter */ break; - if (state->stop_at_first_nonoption) { + if (state->mode == OPTION_PARSER_STOP_AT_FIRST_NONOPTION) { state->parsing_stopped = true; return 0; } + if (state->mode == OPTION_PARSER_RETURN_POSITIONAL_ARGS) { + handling_positional_arg = true; + optval = state->argv[state->optind]; + break; + } + state->optind++; } /* Find matching option entry. * First, figure out if we have a long option or a short option. */ - assert(state->argv[state->optind][0] == '-'); + assert(handling_positional_arg || state->argv[state->optind][0] == '-'); - if (state->argv[state->optind][1] == '-') { + if (handling_positional_arg) + /* We are supposed to return the positional arg to be handled. */ + for (option = options;; option++) { + /* If OPTION_PARSER_RETURN_POSITIONAL_ARGS is specified, + * OPTION_POSITIONAL must be used. */ + assert(option < options_end); + + if (FLAGS_SET(option->flags, OPTION_POSITIONAL_ENTRY)) + break; + } + + else if (state->argv[state->optind][1] == '-') { /* We have a long option. */ char *eq = strchr(state->argv[state->optind], '='); if (eq) { @@ -204,11 +225,11 @@ int option_parse( assert(option); - if (optval && !option_takes_arg(option)) + if (!handling_positional_arg && optval && !option_takes_arg(option)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s: option '%s' doesn't allow an argument", program_invocation_short_name, optname); - if (!optval && option_arg_required(option)) { + if (!handling_positional_arg && !optval && option_arg_required(option)) { if (!state->argv[state->optind + 1]) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s: option '%s' requires an argument", @@ -218,7 +239,13 @@ int option_parse( } if (state->short_option_offset == 0) { - /* We're done with this option. Adjust the array and position. */ + /* We're done with this parameter. Adjust the array and position. */ + if (handling_positional_arg) { + /* Sanity check */ + assert(state->positional_offset == state->optind); + assert(!separate_optval); + } + shift_arg(state->argv, state->positional_offset++, state->optind++); if (separate_optval) shift_arg(state->argv, state->positional_offset++, state->optind++); diff --git a/src/shared/options.h b/src/shared/options.h index 262196c8d9..cd9f64fd49 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -6,10 +6,11 @@ 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 */ - OPTION_HELP_ENTRY_VERBATIM = 1U << 4, /* Same, but use the long_code in the first column as written */ + OPTION_POSITIONAL_ENTRY = 1U << 1, /* The "option" to handle positional arguments */ + OPTION_STOPS_PARSING = 1U << 2, /* This option acts like "--" */ + OPTION_GROUP_MARKER = 1U << 3, /* Fake option entry to separate groups */ + OPTION_HELP_ENTRY = 1U << 4, /* Fake option entry to insert an additional help line */ + OPTION_HELP_ENTRY_VERBATIM = 1U << 5, /* Same, but use the long_code in the first column as written */ } OptionFlags; typedef struct Option { @@ -50,6 +51,7 @@ typedef struct Option { #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_POSITIONAL OPTION_FULL(OPTION_POSITIONAL_ENTRY, /* sc= */ 0, "(positional)", /* mv= */ NULL, /* h= */ NULL) #define OPTION_HELP_VERBATIM(lc, h) OPTION_FULL(OPTION_HELP_ENTRY_VERBATIM, /* sc= */ 0, lc, /* mv= */ NULL, h) #define OPTION_COMMON_HELP \ @@ -92,13 +94,26 @@ typedef struct Option { extern const Option __start_SYSTEMD_OPTIONS[]; extern const Option __stop_SYSTEMD_OPTIONS[]; +typedef enum OptionParserMode { + /* The default mode. This is the implicit default and doesn't have to be specified. */ + OPTION_PARSER_NORMAL = 0, + + /* Same as "+…" for getopt_long — only parse options before the first positional argument. */ + OPTION_PARSER_STOP_AT_FIRST_NONOPTION, + + /* Same as "-…" for getopt_long — return positional arguments as "options" to be handled by the + * option handler specified with OPTION_POSITIONAL. */ + OPTION_PARSER_RETURN_POSITIONAL_ARGS, + + _OPTION_PARSER_MODE_MAX, +} OptionParserMode; + typedef struct OptionParser { /* Those three should stay first so that it's possible to initialize the struct as { argc, argv } - * or { argc, argv, true/false }. */ + * or { argc, argv, mode }. */ int argc; /* The original argc. */ char **argv; /* The argv array, possibly reordered. */ - bool stop_at_first_nonoption; /* Same as "+…" for getopt_long — only parse options before the first - * positional argument. */ + OptionParserMode mode; bool parsing_stopped; /* We processed "--" or an option that terminates option parsing. */ int optind; /* Position of the parameter being handled. diff --git a/src/test/test-options.c b/src/test/test-options.c index b91ac54884..05fd4bcbe0 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -15,7 +15,7 @@ static void test_option_parse_one( const Option options[], const Entry *entries, char **remaining, - bool stop_at_first_nonoption) { + OptionParserMode mode) { _cleanup_free_ char *joined = strv_join(argv, ", "); log_debug("/* %s(%s) */", __func__, joined); @@ -32,7 +32,7 @@ static void test_option_parse_one( for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) n_entries++; - OptionParser state = { argc, argv, stop_at_first_nonoption }; + OptionParser state = { argc, argv, mode }; const Option *opt; const char *arg; for (int c; (c = option_parse(options, options + n_options, &state, &opt, &arg)) != 0; ) { @@ -101,7 +101,7 @@ TEST(option_parse) { options, NULL, NULL, - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -114,7 +114,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "--", @@ -128,7 +128,7 @@ TEST(option_parse) { "--help", "-h", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -142,7 +142,7 @@ TEST(option_parse) { "string2", "--", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -156,7 +156,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "--help"), @@ -166,7 +166,7 @@ TEST(option_parse) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "--help", @@ -179,7 +179,7 @@ TEST(option_parse) { }, STRV_MAKE("string1", "--help"), - true); + OPTION_PARSER_STOP_AT_FIRST_NONOPTION); test_option_parse_one(STRV_MAKE("arg0", "-h"), @@ -189,7 +189,7 @@ TEST(option_parse) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "--help", @@ -206,7 +206,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "-h", @@ -223,7 +223,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -240,7 +240,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -257,7 +257,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -274,7 +274,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -291,7 +291,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "--required1", "reqarg1"), @@ -301,7 +301,7 @@ TEST(option_parse) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "-r", "reqarg1"), @@ -311,7 +311,7 @@ TEST(option_parse) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -324,7 +324,7 @@ TEST(option_parse) { }, STRV_MAKE("string1", "string2"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -335,7 +335,7 @@ TEST(option_parse) { STRV_MAKE("string1", "string2", "-r", "reqarg1"), - true); + OPTION_PARSER_STOP_AT_FIRST_NONOPTION); test_option_parse_one(STRV_MAKE("arg0", "--optional1=optarg1"), @@ -345,7 +345,7 @@ TEST(option_parse) { {} }, NULL, - true); + OPTION_PARSER_STOP_AT_FIRST_NONOPTION); test_option_parse_one(STRV_MAKE("arg0", "--optional1", "string1"), @@ -355,7 +355,7 @@ TEST(option_parse) { {} }, STRV_MAKE("string1"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "-ooptarg1"), @@ -365,7 +365,7 @@ TEST(option_parse) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "-o", "string1"), @@ -375,7 +375,7 @@ TEST(option_parse) { {} }, STRV_MAKE("string1"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -445,7 +445,7 @@ TEST(option_parse) { "--help", "--required1", "--optional1"), - false); + OPTION_PARSER_NORMAL); } TEST(option_stops_parsing) { @@ -469,7 +469,7 @@ TEST(option_stops_parsing) { }, STRV_MAKE("--help", "foo"), - false); + OPTION_PARSER_NORMAL); /* Options before --exec are still parsed */ test_option_parse_one(STRV_MAKE("arg0", @@ -485,7 +485,7 @@ TEST(option_stops_parsing) { }, STRV_MAKE("--version", "bar"), - false); + OPTION_PARSER_NORMAL); /* --exec with no trailing args */ test_option_parse_one(STRV_MAKE("arg0", @@ -496,7 +496,7 @@ TEST(option_stops_parsing) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* --exec after positional args */ test_option_parse_one(STRV_MAKE("arg0", @@ -513,7 +513,7 @@ TEST(option_stops_parsing) { "--help", "--required", "val"), - false); + OPTION_PARSER_NORMAL); /* "--" after --exec: "--" is still consumed as end-of-options marker. This is needed for * backwards compatibility, systemd-dissect implemented this behaviour. But also, it makes @@ -529,7 +529,7 @@ TEST(option_stops_parsing) { {} }, STRV_MAKE("--help"), - false); + OPTION_PARSER_NORMAL); /* "--" before --exec: "--" terminates first, --exec is positional */ test_option_parse_one(STRV_MAKE("arg0", @@ -540,7 +540,7 @@ TEST(option_stops_parsing) { NULL, STRV_MAKE("--exec", "--help"), - false); + OPTION_PARSER_NORMAL); /* Multiple options then --exec then more option-like args */ test_option_parse_one(STRV_MAKE("arg0", @@ -559,7 +559,7 @@ TEST(option_stops_parsing) { STRV_MAKE("-h", "--required", "val2"), - false); + OPTION_PARSER_NORMAL); } TEST(option_group_marker) { @@ -584,7 +584,7 @@ TEST(option_group_marker) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* Check that group marker name is ignored */ test_option_parse_one(STRV_MAKE("arg0", @@ -597,7 +597,7 @@ TEST(option_group_marker) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* Verify that the group marker is not mistaken for an option */ test_option_invalid_one(STRV_MAKE("arg0", @@ -625,7 +625,7 @@ TEST(option_group_marker) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* Partial match with multiple candidates */ test_option_invalid_one(STRV_MAKE("arg0", @@ -649,7 +649,7 @@ TEST(option_optional_arg) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* Long option without = does NOT consume the next arg */ test_option_parse_one(STRV_MAKE("arg0", @@ -660,7 +660,7 @@ TEST(option_optional_arg) { {} }, STRV_MAKE("foo.txt"), - false); + OPTION_PARSER_NORMAL); /* Short option with inline arg */ test_option_parse_one(STRV_MAKE("arg0", @@ -671,7 +671,7 @@ TEST(option_optional_arg) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* Short option without inline arg does NOT consume the next arg */ test_option_parse_one(STRV_MAKE("arg0", @@ -682,7 +682,7 @@ TEST(option_optional_arg) { {} }, STRV_MAKE("foo.txt"), - false); + OPTION_PARSER_NORMAL); /* Optional arg option at end of argv */ test_option_parse_one(STRV_MAKE("arg0", @@ -693,7 +693,7 @@ TEST(option_optional_arg) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* Mixed: optional arg with other options */ test_option_parse_one(STRV_MAKE("arg0", @@ -708,7 +708,7 @@ TEST(option_optional_arg) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* Short combo: -ho (h then o with no arg) */ test_option_parse_one(STRV_MAKE("arg0", @@ -720,7 +720,7 @@ TEST(option_optional_arg) { {} }, STRV_MAKE("pos1"), - false); + OPTION_PARSER_NORMAL); /* Short combo: -hobar (h then o with inline arg "bar") */ test_option_parse_one(STRV_MAKE("arg0", @@ -732,7 +732,7 @@ TEST(option_optional_arg) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); } /* Test the OPTION, OPTION_LONG, OPTION_SHORT, OPTION_FULL, OPTION_GROUP macros @@ -741,7 +741,8 @@ TEST(option_optional_arg) { static void test_macros_parse_one( char **argv, const Entry *entries, - char **remaining) { + char **remaining, + OptionParserMode mode) { _cleanup_free_ char *joined = strv_join(argv, ", "); log_debug("/* %s(%s) */", __func__, joined); @@ -755,7 +756,7 @@ static void test_macros_parse_one( for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) n_entries++; - OptionParser state = { argc, argv }; + OptionParser state = { argc, argv, mode }; const Option *opt; const char *arg; @@ -807,6 +808,9 @@ static void test_macros_parse_one( OPTION_LONG("debug", NULL, "Enable debug mode"): break; + OPTION_POSITIONAL: + break; + default: log_error("Unexpected option id: %d", c); ASSERT_TRUE(false); @@ -828,7 +832,8 @@ TEST(option_macros) { { "help" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION: short form */ test_macros_parse_one(STRV_MAKE("arg0", @@ -837,7 +842,8 @@ TEST(option_macros) { { "help" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION_LONG: only accessible via long form */ test_macros_parse_one(STRV_MAKE("arg0", @@ -846,7 +852,8 @@ TEST(option_macros) { { "version" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION_SHORT: only accessible via short form */ test_macros_parse_one(STRV_MAKE("arg0", @@ -855,7 +862,8 @@ TEST(option_macros) { { .short_code = 'v' }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION with required arg: long --required=ARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -864,7 +872,8 @@ TEST(option_macros) { { "required", "val1" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION with required arg: long --required ARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -873,7 +882,8 @@ TEST(option_macros) { { "required", "val1" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION with required arg: short -r ARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -882,7 +892,8 @@ TEST(option_macros) { { "required", "val1" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION with required arg: short -rARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -891,7 +902,8 @@ TEST(option_macros) { { "required", "val1" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: long with = */ test_macros_parse_one(STRV_MAKE("arg0", @@ -900,7 +912,8 @@ TEST(option_macros) { { "optional", "val1" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: long without = doesn't consume next */ test_macros_parse_one(STRV_MAKE("arg0", @@ -909,7 +922,8 @@ TEST(option_macros) { { "optional", NULL }, {} }, - STRV_MAKE("pos1")); + STRV_MAKE("pos1"), + OPTION_PARSER_NORMAL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: short inline */ test_macros_parse_one(STRV_MAKE("arg0", @@ -918,7 +932,8 @@ TEST(option_macros) { { "optional", "val1" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: short without inline */ test_macros_parse_one(STRV_MAKE("arg0", @@ -927,7 +942,8 @@ TEST(option_macros) { { "optional", NULL }, {} }, - STRV_MAKE("pos1")); + STRV_MAKE("pos1"), + OPTION_PARSER_NORMAL); /* OPTION_FULL with OPTION_STOPS_PARSING: stops further option parsing */ test_macros_parse_one(STRV_MAKE("arg0", @@ -939,7 +955,8 @@ TEST(option_macros) { {} }, STRV_MAKE("--help", - "--version")); + "--version"), + OPTION_PARSER_NORMAL); /* OPTION_STOPS_PARSING: options before are still parsed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -953,7 +970,8 @@ TEST(option_macros) { {} }, STRV_MAKE("-h", - "--debug")); + "--debug"), + OPTION_PARSER_NORMAL); /* OPTION_STOPS_PARSING with "--": "--" after exec is still consumed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -964,7 +982,8 @@ TEST(option_macros) { { "exec" }, {} }, - STRV_MAKE("--help")); + STRV_MAKE("--help"), + OPTION_PARSER_NORMAL); /* OPTION_STOPS_PARSING with "--": "--" before exec takes precedence */ test_macros_parse_one(STRV_MAKE("arg0", @@ -975,7 +994,8 @@ TEST(option_macros) { {} }, STRV_MAKE("--exec", - "--help")); + "--help"), + OPTION_PARSER_NORMAL); /* OPTION_GROUP: group marker is transparent to parsing, --debug in Advanced group works */ test_macros_parse_one(STRV_MAKE("arg0", @@ -984,7 +1004,8 @@ TEST(option_macros) { { "debug" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* Mixed: all macro types together */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1010,7 +1031,8 @@ TEST(option_macros) { {} }, STRV_MAKE("pos1", - "pos2")); + "pos2"), + OPTION_PARSER_NORMAL); /* Short option combos with macros: -hv (help + verbose) */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1020,7 +1042,8 @@ TEST(option_macros) { { .short_code = 'v' }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* Short option combo with required arg: -hrval (help + required with arg "val") */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1030,7 +1053,8 @@ TEST(option_macros) { { "required", "val" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* Short option combo with optional arg: -hoval (help + optional with arg "val") */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1040,7 +1064,8 @@ TEST(option_macros) { { "optional", "val" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION_STOPS_PARSING then "--": "--" is still consumed after exec */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1055,7 +1080,8 @@ TEST(option_macros) { {} }, STRV_MAKE("--version", - "-h")); + "-h"), + OPTION_PARSER_NORMAL); /* OPTION_STOPS_PARSING then later "--": "--" is not consumed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1071,7 +1097,8 @@ TEST(option_macros) { }, STRV_MAKE("--version", "--", - "-h")); + "-h"), + OPTION_PARSER_NORMAL); /* OPTION_STOPS_PARSING then "--" twice: second "--" is not consumed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1088,7 +1115,39 @@ TEST(option_macros) { }, STRV_MAKE("--", "--version", - "-h")); + "-h"), + OPTION_PARSER_NORMAL); + + /* Basic OPTION_POSITIONAL use */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help", + "arg1", + "--debug", + "arg2"), + (Entry[]) { + { "help" }, + { "(positional)", "arg1" }, + { "debug" }, + { "(positional)", "arg2" }, + {} + }, + NULL, + OPTION_PARSER_RETURN_POSITIONAL_ARGS); + + /* OPTION_POSITIONAL combined with OPTION_STOPS_PARSING */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help", + "arg1", + "--exec", + "arg2"), + (Entry[]) { + { "help" }, + { "(positional)", "arg1" }, + { "exec" }, + {} + }, + STRV_MAKE("arg2"), + OPTION_PARSER_RETURN_POSITIONAL_ARGS); } /* Test the pattern used by nspawn's --user: an optional-arg option that also @@ -1110,7 +1169,7 @@ TEST(option_optional_arg_consume) { {} }, NULL, - /* stop_at_first_nonoption= */ false); + OPTION_PARSER_NORMAL); /* --user without arg: next arg is an option, so no consumption */ test_option_parse_one(STRV_MAKE("arg0", @@ -1123,7 +1182,7 @@ TEST(option_optional_arg_consume) { {} }, NULL, - /* stop_at_first_nonoption= */ false); + OPTION_PARSER_NORMAL); /* --user without arg: next arg is positional (doesn't start with -). * The option parser returns NULL for the arg. The caller would then diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 548a083fac..5c70809e47 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -326,7 +326,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true }; + OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; const Option *current; const char *arg;