From: Zbigniew Jędrzejewski-Szmek Date: Wed, 13 May 2026 15:50:24 +0000 (+0200) Subject: systemctl: convert parse_argv to OPTION macros X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=91249f8004479d59a196151de631bf1d8e50187b;p=thirdparty%2Fsystemd.git systemctl: convert parse_argv to OPTION macros The verbs[] table still lives in systemctl-main.c — only the option parsing side is migrated. systemctl_dispatch_parse_argv() gains a remaining_args out-param so run() can pass the parsed positional args to systemctl_main(), which dispatches via _dispatch_verb_with_args() instead of dispatch_verb(). The Options section of --help now renders from the OPTION declarations; the verb sections still use raw printfs and will be converted alongside the verbs[] migration. Co-developed-by: Claude Opus 4.7 --- diff --git a/src/systemctl/fuzz-systemctl-parse-argv.c b/src/systemctl/fuzz-systemctl-parse-argv.c index d30b9d8fed0..38c0decd949 100644 --- a/src/systemctl/fuzz-systemctl-parse-argv.c +++ b/src/systemctl/fuzz-systemctl-parse-argv.c @@ -56,7 +56,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { optind = 0; /* this tells the getopt machinery to reinitialize */ arg_transport = BUS_TRANSPORT_LOCAL; - r = systemctl_dispatch_parse_argv(strv_length(argv), argv); + r = systemctl_dispatch_parse_argv(strv_length(argv), argv, /* remaining_args= */ NULL); if (r < 0) log_error_errno(r, "Failed to parse args: %m"); else diff --git a/src/systemctl/systemctl-main.c b/src/systemctl/systemctl-main.c index 4d1830071f4..27f74519115 100644 --- a/src/systemctl/systemctl-main.c +++ b/src/systemctl/systemctl-main.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "dissect-image.h" @@ -11,6 +10,7 @@ #include "main-func.h" #include "mount-util.h" #include "stat-util.h" +#include "strv.h" #include "systemctl.h" #include "systemctl-add-dependency.h" #include "systemctl-cancel-job.h" @@ -47,7 +47,7 @@ #include "verbs.h" #include "virt.h" -static int systemctl_main(int argc, char *argv[]) { +static int systemctl_main(char **args) { static const Verb verbs[] = { { "list-units", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, verb_list_units }, { "list-unit-files", VERB_ANY, VERB_ANY, 0, verb_list_unit_files }, @@ -135,24 +135,25 @@ static int systemctl_main(int argc, char *argv[]) { {} }; - const Verb *verb = verbs_find_verb(argv[optind], verbs, verbs + ELEMENTSOF(verbs) - 1); + const Verb *verb = verbs_find_verb(args[0], verbs, verbs + ELEMENTSOF(verbs) - 1); if (verb && (verb->flags & VERB_ONLINE_ONLY) && arg_root) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Verb '%s' cannot be used with --root= or --image=.", - argv[optind] ?: verb->verb); + args[0] ?: verb->verb); - return dispatch_verb(argc, argv, verbs, NULL); + return _dispatch_verb_with_args(args, verbs, verbs + ELEMENTSOF(verbs) - 1, NULL); } static int run(int argc, char *argv[]) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_freep) char *mounted_dir = NULL; + char **args = STRV_EMPTY; int r; setlocale(LC_ALL, ""); log_setup(); - r = systemctl_dispatch_parse_argv(argc, argv); + r = systemctl_dispatch_parse_argv(argc, argv, &args); if (r <= 0) goto finish; @@ -200,7 +201,7 @@ static int run(int argc, char *argv[]) { switch (arg_action) { case ACTION_SYSTEMCTL: - r = systemctl_main(argc, argv); + r = systemctl_main(args); break; /* Legacy command aliases set arg_action. They provide some fallbacks, e.g. to tell sysvinit to diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 799249fe42d..8a241495c01 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -10,9 +9,11 @@ #include "bus-util.h" #include "capsule-util.h" #include "extract-word.h" +#include "format-table.h" #include "help-util.h" #include "image-policy.h" #include "install.h" +#include "options.h" #include "output-mode.h" #include "pager.h" #include "parse-argument.h" @@ -108,9 +109,16 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_kill_subgroup, freep); static int systemctl_help(void) { + _cleanup_(table_unrefp) Table *options_table = NULL; + int r; + + r = option_parser_get_help_table_ns("systemctl", &options_table); + if (r < 0) + return r; + pager_open(arg_pager_flags); - help_cmdline("[OPTIONS...] COMMAND ..."); + help_cmdline("[OPTIONS…] COMMAND …"); help_abstract("Query or send control commands to the system manager."); help_section("Unit Commands"); @@ -227,100 +235,9 @@ static int systemctl_help(void) { " time, and hibernate\n"); help_section("Options"); - printf(" -h --help Show this help\n" - " --version Show package version\n" - " --system Connect to system manager\n" - " --user Connect to user service manager\n" - " -C --capsule=NAME Connect to service manager of specified capsule\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on a local container\n" - " -t --type=TYPE List units of a particular type\n" - " --state=STATE List units with particular LOAD or SUB or ACTIVE state\n" - " --failed Shortcut for --state=failed\n" - " -p --property=NAME Show only properties by this name\n" - " -P NAME Equivalent to --value --property=NAME\n" - " -a --all Show all properties/all units currently in memory,\n" - " including dead/empty ones. To list all units installed\n" - " on the system, use 'list-unit-files' instead.\n" - " -l --full Don't ellipsize unit names on output\n" - " -r --recursive Show unit list of host and local containers\n" - " --reverse Show reverse dependencies with 'list-dependencies'\n" - " --before Show units ordered before with 'list-dependencies'\n" - " --after Show units ordered after with 'list-dependencies'\n" - " --with-dependencies Show unit dependencies with 'status', 'cat',\n" - " 'list-units', and 'list-unit-files'.\n" - " --job-mode=MODE Specify how to deal with already queued jobs, when\n" - " queueing a new job\n" - " -T --show-transaction When enqueuing a unit job, show full transaction\n" - " --show-types When showing sockets, explicitly show their type\n" - " --value When showing properties, only print the value\n" - " --check-inhibitors=MODE\n" - " Whether to check inhibitors before shutting down,\n" - " sleeping, or hibernating\n" - " -i Shortcut for --check-inhibitors=no\n" - " -s --signal=SIGNAL Which signal to send\n" - " --kill-whom=WHOM Whom to send signal to\n" - " --kill-value=INT Signal value to enqueue\n" - " --kill-subgroup=PATH\n" - " Send signal to sub-control group only\n" - " --what=RESOURCES Which types of resources to remove\n" - " --now Start or stop unit after enabling or disabling it\n" - " --dry-run Only print what would be done\n" - " Currently supported by verbs: halt, poweroff, reboot,\n" - " kexec, soft-reboot, suspend, hibernate, \n" - " suspend-then-hibernate, hybrid-sleep, default,\n" - " rescue, emergency, and exit.\n" - " -q --quiet Suppress output\n" - " -v --verbose Show unit logs while executing operation\n" - " --no-warn Suppress several warnings shown by default\n" - " --wait For (re)start, wait until service stopped again\n" - " For is-system-running, wait until startup is completed\n" - " For kill, wait until service stopped\n" - " --no-block Do not wait until operation finished\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n" - " --message=MESSAGE Specify human-readable reason for system shutdown\n" - " --no-reload Don't reload daemon after en-/dis-abling unit files\n" - " --legend=BOOL Enable/disable the legend (column headers and hints)\n" - " --no-pager Do not pipe output into a pager\n" - " --no-ask-password Do not ask for system passwords\n" - " --global Edit/enable/disable/mask default user unit files\n" - " globally\n" - " --runtime Edit/enable/disable/mask unit files temporarily until\n" - " next reboot\n" - " -f --force When enabling unit files, override existing symlinks\n" - " When shutting down, execute action immediately\n" - " --preset-mode= Apply only enable, only disable, or all presets\n" - " --root=PATH Edit/enable/disable/mask unit files in the specified\n" - " root directory\n" - " --image=PATH Edit/enable/disable/mask unit files in the specified\n" - " disk image\n" - " --image-policy=POLICY\n" - " Specify disk image dissection policy\n" - " -n --lines=INTEGER Number of journal entries to show\n" - " -o --output=STRING Change journal output mode (short, short-precise,\n" - " short-iso, short-iso-precise, short-full,\n" - " short-monotonic, short-unix, short-delta,\n" - " verbose, export, json, json-pretty, json-sse, cat)\n" - " --firmware-setup Tell the firmware to show the setup menu on next boot\n" - " --boot-loader-menu=TIME\n" - " Boot into boot loader menu on next boot\n" - " --boot-loader-entry=NAME\n" - " Boot into a specific boot loader entry on next boot\n" - " --reboot-argument=ARG\n" - " Specify argument string to pass to reboot()\n" - " --kernel-cmdline=CMDLINE\n" - " Append to the kernel command line when loading the\n" - " kernel from the booted boot loader entry\n" - " --plain Print unit dependencies as a list instead of a tree\n" - " --timestamp=FORMAT Change format of printed timestamps (pretty, unix,\n" - " us, utc, us+utc)\n" - " --read-only Create read-only bind mount\n" - " --mkdir Create directory before mounting, if missing\n" - " --marked Restart/reload previously marked units\n" - " --drop-in=NAME Edit unit files using the specified drop-in file name\n" - " --when=TIME Schedule halt/power-off/reboot/kexec action after\n" - " a certain timestamp\n" - " --stdin Read new contents of edited file from stdin\n"); + r = table_print_or_warn(options_table); + if (r < 0) + return r; help_man_page_reference("systemctl", "1"); @@ -538,134 +455,8 @@ static int parse_what_argument(const char *value, char ***clean_what) { return 1; } -static int systemctl_parse_argv(int argc, char *argv[]) { - enum { - ARG_FAIL = 0x100, /* compatibility only */ - ARG_REVERSE, - ARG_AFTER, - ARG_BEFORE, - ARG_CHECK_INHIBITORS, - ARG_DRY_RUN, - ARG_SHOW_TYPES, - ARG_IRREVERSIBLE, /* compatibility only */ - ARG_IGNORE_DEPENDENCIES, /* compatibility only */ - ARG_VALUE, - ARG_VERSION, - ARG_USER, - ARG_SYSTEM, - ARG_GLOBAL, - ARG_NO_BLOCK, - ARG_LEGEND, - ARG_NO_LEGEND, /* compatibility only */ - ARG_NO_PAGER, - ARG_NO_WALL, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_NO_RELOAD, - ARG_KILL_WHOM, - ARG_KILL_VALUE, - ARG_NO_ASK_PASSWORD, - ARG_FAILED, - ARG_RUNTIME, - ARG_PLAIN, - ARG_STATE, - ARG_JOB_MODE, - ARG_PRESET_MODE, - ARG_FIRMWARE_SETUP, - ARG_BOOT_LOADER_MENU, - ARG_BOOT_LOADER_ENTRY, - ARG_NOW, - ARG_MESSAGE, - ARG_WITH_DEPENDENCIES, - ARG_WAIT, - ARG_WHAT, - ARG_REBOOT_ARG, - ARG_KERNEL_CMDLINE, - ARG_TIMESTAMP_STYLE, - ARG_READ_ONLY, - ARG_MKDIR, - ARG_MARKED, - ARG_NO_WARN, - ARG_DROP_IN, - ARG_WHEN, - ARG_STDIN, - ARG_KILL_SUBGROUP, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "property", required_argument, NULL, 'p' }, - { "all", no_argument, NULL, 'a' }, - { "reverse", no_argument, NULL, ARG_REVERSE }, - { "after", no_argument, NULL, ARG_AFTER }, - { "before", no_argument, NULL, ARG_BEFORE }, - { "show-types", no_argument, NULL, ARG_SHOW_TYPES }, - { "failed", no_argument, NULL, ARG_FAILED }, - { "full", no_argument, NULL, 'l' }, - { "job-mode", required_argument, NULL, ARG_JOB_MODE }, - { "fail", no_argument, NULL, ARG_FAIL }, /* compatibility only */ - { "irreversible", no_argument, NULL, ARG_IRREVERSIBLE }, /* compatibility only */ - { "ignore-dependencies", no_argument, NULL, ARG_IGNORE_DEPENDENCIES }, /* compatibility only */ - { "ignore-inhibitors", no_argument, NULL, 'i' }, /* compatibility only */ - { "check-inhibitors", required_argument, NULL, ARG_CHECK_INHIBITORS }, - { "value", no_argument, NULL, ARG_VALUE }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "global", no_argument, NULL, ARG_GLOBAL }, - { "capsule", required_argument, NULL, 'C' }, - { "wait", no_argument, NULL, ARG_WAIT }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "legend", required_argument, NULL, ARG_LEGEND }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, /* compatibility only */ - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - { "dry-run", no_argument, NULL, ARG_DRY_RUN }, - { "quiet", no_argument, NULL, 'q' }, - { "verbose", no_argument, NULL, 'v' }, - { "no-warn", no_argument, NULL, ARG_NO_WARN }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "force", no_argument, NULL, 'f' }, - { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, - { "kill-whom", required_argument, NULL, ARG_KILL_WHOM }, - { "kill-value", required_argument, NULL, ARG_KILL_VALUE }, - { "signal", required_argument, NULL, 's' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "runtime", no_argument, NULL, ARG_RUNTIME }, - { "lines", required_argument, NULL, 'n' }, - { "output", required_argument, NULL, 'o' }, - { "plain", no_argument, NULL, ARG_PLAIN }, - { "state", required_argument, NULL, ARG_STATE }, - { "recursive", no_argument, NULL, 'r' }, - { "with-dependencies", no_argument, NULL, ARG_WITH_DEPENDENCIES }, - { "preset-mode", required_argument, NULL, ARG_PRESET_MODE }, - { "firmware-setup", no_argument, NULL, ARG_FIRMWARE_SETUP }, - { "boot-loader-menu", required_argument, NULL, ARG_BOOT_LOADER_MENU }, - { "boot-loader-entry", required_argument, NULL, ARG_BOOT_LOADER_ENTRY }, - { "now", no_argument, NULL, ARG_NOW }, - { "message", required_argument, NULL, ARG_MESSAGE }, - { "show-transaction", no_argument, NULL, 'T' }, - { "what", required_argument, NULL, ARG_WHAT }, - { "reboot-argument", required_argument, NULL, ARG_REBOOT_ARG }, - { "kernel-cmdline", required_argument, NULL, ARG_KERNEL_CMDLINE }, - { "timestamp", required_argument, NULL, ARG_TIMESTAMP_STYLE }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "mkdir", no_argument, NULL, ARG_MKDIR }, - { "marked", no_argument, NULL, ARG_MARKED }, - { "drop-in", required_argument, NULL, ARG_DROP_IN }, - { "when", required_argument, NULL, ARG_WHEN }, - { "stdin", no_argument, NULL, ARG_STDIN }, - { "kill-subgroup", required_argument, NULL, ARG_KILL_SUBGROUP }, - {} - }; - - int c, r; +static int systemctl_parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); @@ -673,182 +464,191 @@ static int systemctl_parse_argv(int argc, char *argv[]) { /* We default to allowing interactive authorization only in systemctl (not in the legacy commands) */ arg_ask_password = true; - while ((c = getopt_long(argc, argv, "hC:t:p:P:alqvfs:H:M:n:o:iTr.::", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "systemctl" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_NAMESPACE("systemctl"): {} + + OPTION_COMMON_HELP: return systemctl_help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Connect to the system service manager"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Connect to the user service manager"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case 'C': - r = capsule_name_is_valid(optarg); + OPTION('C', "capsule", "NAME", "Connect to service manager of specified capsule"): + r = capsule_name_is_valid(opts.arg); if (r < 0) - return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg); + return log_error_errno(r, "Unable to validate capsule name '%s': %m", opts.arg); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", opts.arg); - arg_host = optarg; + arg_host = opts.arg; arg_transport = BUS_TRANSPORT_CAPSULE; arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case 't': - r = parse_types_argument(optarg, &arg_types, &arg_states); + OPTION('t', "type", "TYPE", "List units of a particular type"): + r = parse_types_argument(opts.arg, &arg_types, &arg_states); if (r <= 0) return r; break; - case ARG_STATE: - r = parse_states_argument(optarg, &arg_states); + OPTION_LONG("state", "STATE", "List units with particular LOAD or SUB or ACTIVE state"): + r = parse_states_argument(opts.arg, &arg_states); if (r <= 0) return r; break; - case ARG_FAILED: + OPTION_LONG("failed", NULL, "Shortcut for --state=failed"): if (strv_extend(&arg_states, "failed") < 0) return log_oom(); - break; - case 'P': - SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); - _fallthrough_; - - case 'p': - r = parse_property_argument(optarg, &arg_properties); + OPTION('p', "property", "NAME", "Show only properties by this name"): {} + OPTION_SHORT('P', "NAME", "Equivalent to --value --property=NAME"): + r = parse_property_argument(opts.arg, &arg_properties); if (r < 0) return r; /* If the user asked for a particular property, show it, even if it is empty. */ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); + if (opts.opt->short_code == 'P') + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); + break; - case 'a': + OPTION('a', "all", NULL, + "Show all properties/all units currently in memory, including dead/empty ones. " + "To list all units installed on the system, use 'list-unit-files' instead"): SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); arg_all = true; break; - case 'l': + OPTION('l', "full", NULL, "Don't ellipsize unit names on output"): arg_full = true; break; - case 'r': + OPTION('r', "recursive", NULL, "Show unit list of host and local containers"): if (geteuid() != 0) return log_error_errno(SYNTHETIC_ERRNO(EPERM), - "--recursive requires root privileges."); + "--recursive requires root privileges"); arg_recursive = true; break; - case ARG_REVERSE: + OPTION_LONG("reverse", NULL, "Show reverse dependencies with 'list-dependencies'"): arg_dependency = DEPENDENCY_REVERSE; break; - case ARG_BEFORE: + OPTION_LONG("before", NULL, "Show units ordered before with 'list-dependencies'"): arg_dependency = DEPENDENCY_BEFORE; arg_jobs_before = true; break; - case ARG_AFTER: + OPTION_LONG("after", NULL, "Show units ordered after with 'list-dependencies'"): arg_dependency = DEPENDENCY_AFTER; arg_jobs_after = true; break; - case ARG_WITH_DEPENDENCIES: + OPTION_LONG("with-dependencies", NULL, + "Show unit dependencies with 'status', 'cat', 'list-units', and 'list-unit-files'"): arg_with_dependencies = true; break; - case ARG_JOB_MODE: - _arg_job_mode = optarg; + OPTION_LONG("job-mode", "MODE", + "Specify how to deal with already queued jobs, when queueing a new job"): + _arg_job_mode = opts.arg; break; - case 'T': + OPTION('T', "show-transaction", NULL, "When enqueuing a unit job, show full transaction"): arg_show_transaction = true; break; - case ARG_SHOW_TYPES: + OPTION_LONG("show-types", NULL, "When showing sockets, explicitly show their type"): arg_show_types = true; break; - case ARG_VALUE: + OPTION_LONG("value", NULL, "When showing properties, only print the value"): SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); break; - case ARG_CHECK_INHIBITORS: - r = parse_tristate_argument_with_auto("--check-inhibitors=", optarg, &arg_check_inhibitors); + OPTION_LONG("check-inhibitors", "MODE", + "Whether to check inhibitors before shutting down, sleeping, or hibernating"): + r = parse_tristate_argument_with_auto("--check-inhibitors=", opts.arg, &arg_check_inhibitors); if (r < 0) return r; break; - case 'i': + OPTION_LONG("ignore-inhibitors", NULL, /* help= */ NULL): {} + + OPTION_SHORT('i', NULL, "Shortcut for --check-inhibitors=no"): arg_check_inhibitors = 0; break; - case 's': - r = parse_signal_argument(optarg, &arg_signal); + OPTION('s', "signal", "SIGNAL", "Which signal to send"): + r = parse_signal_argument(opts.arg, &arg_signal); if (r <= 0) return r; break; - case ARG_KILL_WHOM: - arg_kill_whom = optarg; + OPTION_LONG("kill-whom", "WHOM", "Whom to send signal to"): + arg_kill_whom = opts.arg; break; - case ARG_KILL_VALUE: { + OPTION_LONG("kill-value", "INT", "Signal value to enqueue"): { unsigned u; - if (isempty(optarg)) { + if (isempty(opts.arg)) { arg_kill_value_set = false; return 0; } /* First, try to parse unsigned, so that we can support the prefixes 0x, 0o, 0b */ - r = safe_atou_full(optarg, 0, &u); + r = safe_atou_full(opts.arg, 0, &u); if (r < 0) /* If this didn't work, try as signed integer, without those prefixes */ - r = safe_atoi(optarg, &arg_kill_value); + r = safe_atoi(opts.arg, &arg_kill_value); else if (u > INT_MAX) r = -ERANGE; else arg_kill_value = (int) u; if (r < 0) - return log_error_errno(r, "Unable to parse signal queue value: %s", optarg); + return log_error_errno(r, "Unable to parse signal queue value: %s", opts.arg); arg_kill_value_set = true; break; } - case ARG_KILL_SUBGROUP: { - if (empty_or_root(optarg)) { + OPTION_LONG("kill-subgroup", "PATH", "Send signal to sub-control group only"): { + if (empty_or_root(opts.arg)) { arg_kill_subgroup = mfree(arg_kill_subgroup); break; } _cleanup_free_ char *p = NULL; - if (path_simplify_alloc(optarg, &p) < 0) + if (path_simplify_alloc(opts.arg, &p) < 0) return log_oom(); if (!path_is_safe(p)) @@ -858,21 +658,24 @@ static int systemctl_parse_argv(int argc, char *argv[]) { break; } - case ARG_WHAT: - r = parse_what_argument(optarg, &arg_clean_what); + OPTION_LONG("what", "RESOURCES", "Which types of resources to remove"): + r = parse_what_argument(opts.arg, &arg_clean_what); if (r <= 0) return r; break; - case ARG_NOW: + OPTION_LONG("now", NULL, "Start or stop unit after enabling or disabling it"): arg_now = true; break; - case ARG_DRY_RUN: + OPTION_LONG("dry-run", NULL, + "Only print what would be done. Currently supported by verbs: halt, poweroff, " + "reboot, kexec, soft-reboot, suspend, hibernate, suspend-then-hibernate, " + "hybrid-sleep, default, rescue, emergency, and exit."): arg_dry_run = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress output"): arg_quiet = true; if (arg_legend < 0) @@ -880,107 +683,118 @@ static int systemctl_parse_argv(int argc, char *argv[]) { break; - case 'v': + OPTION('v', "verbose", NULL, "Show unit logs while executing operation"): arg_verbose = true; break; - case ARG_NO_WARN: + OPTION_LONG("no-warn", NULL, "Suppress several warnings shown by default"): arg_no_warn = true; break; - case ARG_WAIT: + OPTION_LONG("wait", NULL, + "For (re)start, wait until service stopped again. " + "For is-system-running, wait until startup is completed. " + "For kill, wait until service stopped."): arg_wait = true; break; - case ARG_NO_BLOCK: + OPTION_LONG("no-block", NULL, "Do not wait until operation finished"): arg_no_block = true; break; - case ARG_NO_WALL: + OPTION_LONG("no-wall", NULL, "Don't send wall message before halt/power-off/reboot"): arg_no_wall = true; break; - case ARG_MESSAGE: - if (strv_extend(&arg_wall, optarg) < 0) + OPTION_LONG("message", "MESSAGE", "Specify human-readable reason for system shutdown"): + if (strv_extend(&arg_wall, opts.arg) < 0) return log_oom(); break; - case ARG_NO_RELOAD: + OPTION_LONG("no-reload", NULL, "Don't reload daemon after en-/dis-abling unit files"): arg_no_reload = true; break; - case ARG_LEGEND: - r = parse_boolean_argument("--legend", optarg, NULL); + OPTION_LONG("legend", "BOOL", "Enable/disable the legend (column headers and hints)"): + r = parse_boolean_argument("--legend", opts.arg, NULL); if (r < 0) return r; arg_legend = r; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case ARG_GLOBAL: + OPTION_LONG("global", NULL, "Edit/enable/disable/mask default user unit files globally"): arg_runtime_scope = RUNTIME_SCOPE_GLOBAL; break; - case ARG_RUNTIME: + OPTION_LONG("runtime", NULL, + "Edit/enable/disable/mask unit files temporarily until next reboot"): arg_runtime = true; break; - case 'f': + OPTION('f', "force", NULL, + "When enabling unit files, override existing symlinks. " + "When shutting down, execute action immediately."): arg_force++; break; - case ARG_PRESET_MODE: - if (streq(optarg, "help")) + OPTION_LONG("preset-mode", "MODE", "Apply only enable, only disable, or all presets"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(unit_file_preset_mode, UnitFilePresetMode, _UNIT_FILE_PRESET_MODE_MAX); - arg_preset_mode = unit_file_preset_mode_from_string(optarg); + arg_preset_mode = unit_file_preset_mode_from_string(opts.arg); if (arg_preset_mode < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse preset mode: %s.", optarg); + "Failed to parse preset mode: %s.", opts.arg); break; - case ARG_ROOT: - r = parse_path_argument(optarg, false, &arg_root); + OPTION_LONG("root", "PATH", + "Edit/enable/disable/mask unit files in the specified root directory"): + r = parse_path_argument(opts.arg, false, &arg_root); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, false, &arg_image); + OPTION_LONG("image", "PATH", + "Edit/enable/disable/mask unit files in the specified disk image"): + r = parse_path_argument(opts.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(opts.arg, &arg_image_policy); if (r < 0) return r; break; - case 'n': - if (safe_atou(optarg, &arg_lines) < 0) + OPTION('n', "lines", "INTEGER", "Number of journal entries to show"): + if (safe_atou(opts.arg, &arg_lines) < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse lines '%s'", - optarg); + opts.arg); break; - case 'o': - if (streq(optarg, "help")) + OPTION('o', "output", "STRING", + "Change journal output mode (short, short-precise, short-iso, short-iso-precise, " + "short-full, short-monotonic, short-unix, short-delta, verbose, export, json, " + "json-pretty, json-sse, cat)"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); - arg_output = output_mode_from_string(optarg); + arg_output = output_mode_from_string(opts.arg); if (arg_output < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown output '%s'.", - optarg); + opts.arg); if (OUTPUT_MODE_IS_JSON(arg_output)) { arg_legend = false; @@ -988,21 +802,20 @@ static int systemctl_parse_argv(int argc, char *argv[]) { } break; - case ARG_FIRMWARE_SETUP: + OPTION_LONG("firmware-setup", NULL, "Tell the firmware to show the setup menu on next boot"): arg_firmware_setup = true; break; - case ARG_BOOT_LOADER_MENU: - - r = parse_sec(optarg, &arg_boot_loader_menu); + OPTION_LONG("boot-loader-menu", "TIME", "Boot into boot loader menu on next boot"): + r = parse_sec(opts.arg, &arg_boot_loader_menu); if (r < 0) - return log_error_errno(r, "Failed to parse --boot-loader-menu= argument '%s': %m", optarg); + return log_error_errno(r, "Failed to parse --boot-loader-menu= argument '%s': %m", opts.arg); break; - case ARG_BOOT_LOADER_ENTRY: - - if (streq(optarg, "help")) { /* Yes, this means, "help" is not a valid boot loader entry name we can deal with */ + OPTION_LONG("boot-loader-entry", "NAME", + "Boot into a specific boot loader entry on next boot"): + if (streq(opts.arg, "help")) { /* Yes, this means, "help" is not a valid boot loader entry name we can deal with */ r = help_boot_loader_entry(); if (r < 0) return r; @@ -1010,119 +823,117 @@ static int systemctl_parse_argv(int argc, char *argv[]) { return 0; } - arg_boot_loader_entry = empty_to_null(optarg); + arg_boot_loader_entry = empty_to_null(opts.arg); break; - case ARG_REBOOT_ARG: - arg_reboot_argument = optarg; + OPTION_LONG("reboot-argument", "ARG", "Specify argument string to pass to reboot()"): + arg_reboot_argument = opts.arg; break; - case ARG_KERNEL_CMDLINE: - if (isempty(optarg)) { + OPTION_LONG("kernel-cmdline", "CMDLINE", + "Append to the kernel command line when loading the kernel " + "from the booted boot loader entry"): + if (isempty(opts.arg)) { arg_kernel_cmdline = mfree(arg_kernel_cmdline); break; } - if (!string_is_safe(optarg, STRING_ALLOW_GLOBS|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES)) + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--kernel-cmdline= argument contains invalid characters: %s", optarg); + "--kernel-cmdline= argument contains invalid characters: %s", opts.arg); - r = free_and_strdup_warn(&arg_kernel_cmdline, optarg); + r = free_and_strdup_warn(&arg_kernel_cmdline, opts.arg); if (r < 0) return r; break; - case ARG_PLAIN: + OPTION_LONG("plain", NULL, "Print unit dependencies as a list instead of a tree"): arg_plain = true; break; - case ARG_TIMESTAMP_STYLE: - if (streq(optarg, "help")) + OPTION_LONG("timestamp", "FORMAT", + "Change format of printed timestamps (pretty, unix, us, utc, us+utc)"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(timestamp_style, TimestampStyle, _TIMESTAMP_STYLE_MAX); - arg_timestamp_style = timestamp_style_from_string(optarg); + arg_timestamp_style = timestamp_style_from_string(opts.arg); if (arg_timestamp_style < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid value: %s.", optarg); + "Invalid value: %s.", opts.arg); break; - case ARG_READ_ONLY: + OPTION_LONG("read-only", NULL, "Create read-only bind mount"): arg_read_only = true; break; - case ARG_MKDIR: + OPTION_LONG("mkdir", NULL, "Create directory before mounting, if missing"): arg_mkdir = true; break; - case ARG_MARKED: + OPTION_LONG("marked", NULL, "Restart/reload previously marked units"): arg_marked = true; break; - case ARG_DROP_IN: - arg_drop_in = optarg; + OPTION_LONG("drop-in", "NAME", "Edit unit files using the specified drop-in file name"): + arg_drop_in = opts.arg; break; - case ARG_WHEN: - if (streq(optarg, "show")) { + OPTION_LONG("when", "TIME", + "Schedule halt/power-off/reboot/kexec action after a certain timestamp"): + if (streq(opts.arg, "show")) { arg_action = ACTION_SYSTEMCTL_SHOW_SHUTDOWN; return 1; } - if (STR_IN_SET(optarg, "", "cancel")) { + if (STR_IN_SET(opts.arg, "", "cancel")) { arg_action = ACTION_CANCEL_SHUTDOWN; return 1; } - if (streq(optarg, "auto")) { + if (streq(opts.arg, "auto")) { arg_when = USEC_INFINITY; /* logind chooses on server side */ break; } - r = parse_timestamp(optarg, &arg_when); + r = parse_timestamp(opts.arg, &arg_when); if (r < 0) - return log_error_errno(r, "Failed to parse --when= argument '%s': %m", optarg); + return log_error_errno(r, "Failed to parse --when= argument '%s': %m", opts.arg); if (!timestamp_is_set(arg_when)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid timestamp '%s' specified for --when=.", optarg); + "Invalid timestamp '%s' specified for --when=.", opts.arg); break; - case ARG_STDIN: + OPTION_LONG("stdin", NULL, "Read new contents of edited file from stdin"): arg_stdin = true; break; /* Compatibility-only options, not shown in --help. */ - case ARG_FAIL: + OPTION_LONG("fail", NULL, /* help= */ NULL): _arg_job_mode = "fail"; break; - case ARG_IRREVERSIBLE: + OPTION_LONG("irreversible", NULL, /* help= */ NULL): _arg_job_mode = "replace-irreversibly"; break; - case ARG_IGNORE_DEPENDENCIES: + OPTION_LONG("ignore-dependencies", NULL, /* help= */ NULL): _arg_job_mode = "ignore-dependencies"; break; - case ARG_NO_LEGEND: + OPTION_LONG("no-legend", NULL, /* help= */ NULL): arg_legend = false; break; - case '.': + OPTION_SHORT_FLAGS(OPTION_OPTIONAL_ARG, '.', "ARG", /* help= */ NULL): /* Output an error mimicking getopt, and print a hint afterwards */ log_error("%s: invalid option -- '.'", program_invocation_name); log_notice("Hint: to specify units starting with a dash, use \"--\":\n" - " %s [OPTIONS...] COMMAND -- -.%s ...", - program_invocation_name, optarg ?: "mount"); - _fallthrough_; - - case '?': + " %s [OPTIONS…] COMMAND -- -.%s …", + program_invocation_name, opts.arg ?: "mount"); return -EINVAL; - - default: - assert_not_reached(); } /* If we are in --user mode, there's no point in talking to PolicyKit or the infra to query system @@ -1142,17 +953,20 @@ static int systemctl_parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--wait may not be combined with --no-block."); - bool do_reload_or_restart = streq_ptr(argv[optind], "reload-or-restart"); + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + + bool do_reload_or_restart = streq_ptr(args[0], "reload-or-restart"); if (arg_marked) { if (!do_reload_or_restart) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--marked may only be used with 'reload-or-restart'."); - if (optind + 1 < argc) + if (n_args > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No additional arguments allowed with 'reload-or-restart --marked'."); } else if (do_reload_or_restart) { - if (optind + 1 >= argc) + if (n_args <= 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "List of units to restart/reload is required."); } @@ -1161,10 +975,12 @@ static int systemctl_parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); + if (remaining_args) + *remaining_args = args; return 1; } -int systemctl_dispatch_parse_argv(int argc, char *argv[]) { +int systemctl_dispatch_parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); @@ -1183,8 +999,9 @@ int systemctl_dispatch_parse_argv(int argc, char *argv[]) { } else if (invoked_as(argv, "shutdown")) { arg_action = ACTION_POWEROFF; return shutdown_parse_argv(argc, argv); + } else { + arg_action = ACTION_SYSTEMCTL; + return systemctl_parse_argv(argc, argv, remaining_args); } - arg_action = ACTION_SYSTEMCTL; - return systemctl_parse_argv(argc, argv); } diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h index bc52e96fe3b..3789fbb5ea0 100644 --- a/src/systemctl/systemctl.h +++ b/src/systemctl/systemctl.h @@ -102,4 +102,4 @@ static inline const char* arg_job_mode(void) { return _arg_job_mode ?: "replace"; } -int systemctl_dispatch_parse_argv(int argc, char *argv[]); +int systemctl_dispatch_parse_argv(int argc, char *argv[], char ***remaining_args);