shutdown: convert to VERB_COMMAND and add --introspect-cli

systemd-shutdown parsed a bunch of options, but had no way to print
them. Add the standard --help and --version options and the CLI
introspection.

parse_argv() is moved before the PID 1 check, so that the new options
also work when the binary is invoked manually. As PID 1 we can never
return from main() (the kernel would panic), so in that case parse
errors still freeze(), and the help output is followed by a freeze()
too. Ctrl-Alt-Delete and console reopening are re-enabled before
freezing, so that the freeze can be escaped through the kernel and the
error is visible, as before.

The --help/--version/introspect-cli option names are already listed in
the kernel command line exclusion list in proc-cmdline.c.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-08-26 12:25:22 +02:00
parent 4927588993
commit a4145b8ca3
2 changed files with 38 additions and 7 deletions
+37 -7
View File
@@ -16,6 +16,7 @@
#include "alloc-util.h"
#include "async.h"
#include "binfmt-util.h"
#include "build.h"
#include "cgroup-setup.h"
#include "cgroup-util.h"
#include "constants.h"
@@ -49,6 +50,7 @@
#include "terminal-util.h"
#include "time-util.h"
#include "umount.h"
#include "verbs.h"
#include "virt.h"
#include "watchdog.h"
@@ -60,6 +62,13 @@ static const char *arg_verb = NULL;
static uint8_t arg_exit_code = 0;
static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC;
COMMAND(
"systemd-shutdown\0",
"Execute the final phase of system shutdown.",
.argspec = "reboot|poweroff|halt|kexec|exit\0",
.man_pages = "systemd-shutdown.8\0",
);
static int parse_argv(int argc, char *argv[]) {
assert(argc >= 1);
assert(argv);
@@ -75,6 +84,15 @@ static int parse_argv(int argc, char *argv[]) {
FOREACH_OPTION_OR_RETURN(c, &opts)
switch (c) {
OPTION_COMMON_HELP:
return command_print_help();
OPTION_COMMON_VERSION:
return version();
OPTION_COMMON_INTROSPECT_CLI:
return introspect_cli(SD_JSON_FORMAT_OFF);
OPTION_COMMON_LOG_LEVEL:
r = log_set_max_level_from_string(opts.arg);
if (r < 0)
@@ -149,7 +167,7 @@ static int parse_argv(int argc, char *argv[]) {
if (!arg_verb)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Verb argument missing.");
return 0;
return 1; /* Further action */
}
static int switch_root_initramfs(void) {
@@ -403,9 +421,17 @@ static int run(int argc, char *argv[]) {
log_set_prohibit_ipc(true);
log_parse_environment();
if (getpid_cached() != 1)
r = parse_argv(argc, argv);
if (getpid_cached() != 1) {
/* If we're a normal user space process, our work is done. Either we hit an error, or were
* called with --help, --version, or another option that was handled by parse_argv(), or it
* looks like we're being asked to do some real work, but we should do that only as part of
* the real shutdown sequence. */
if (r <= 0)
return r;
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Not executed by init (PID 1). Refusing to operate.");
}
log_set_always_reopen_console(true);
@@ -414,8 +440,7 @@ static int run(int argc, char *argv[]) {
* through kernel. */
(void) reboot(RB_ENABLE_CAD);
r = parse_argv(argc, argv);
if (r < 0)
if (r <= 0)
goto error;
log_open();
@@ -711,9 +736,14 @@ static int run(int argc, char *argv[]) {
r = log_error_errno(errno, "Failed to invoke reboot(): %m");
error:
log_struct_errno(LOG_EMERG, r,
LOG_MESSAGE("Critical error while doing system shutdown: %m"),
LOG_MESSAGE_ID(SD_MESSAGE_SHUTDOWN_ERROR_STR));
if (r == 0)
log_struct(LOG_EMERG,
LOG_MESSAGE("Non-shutdown operation requested, cannot operate."),
LOG_MESSAGE_ID(SD_MESSAGE_SHUTDOWN_ERROR_STR));
else
log_struct_errno(LOG_EMERG, r,
LOG_MESSAGE("Critical error while doing system shutdown: %m"),
LOG_MESSAGE_ID(SD_MESSAGE_SHUTDOWN_ERROR_STR));
freeze();
}
@@ -115,6 +115,7 @@ INTROSPECTABLE=(
systemd-resolved
systemd-run
systemd-sbsign
systemd-shutdown
systemd-sleep
systemd-socket-activate
systemd-socket-proxyd