diff --git a/man/systemctl.xml b/man/systemctl.xml
index e0f499c41ea..f930034cb1d 100644
--- a/man/systemctl.xml
+++ b/man/systemctl.xml
@@ -1486,6 +1486,9 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
is specified twice the halt operation is executed by systemctl
itself, and the system manager is not contacted. This means the command should succeed even when the system
manager has crashed.
+
+ If combined with , shutdown will be scheduled after the given timestamp.
+ And will cancel the shutdown.
@@ -1497,13 +1500,8 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
users. This command is asynchronous; it will return after the power-off operation is enqueued, without
waiting for it to complete.
- If combined with , shutdown of all running services is skipped, however all
- processes are killed and all file systems are unmounted or mounted read-only, immediately followed by the
- powering off. If is specified twice, the operation is immediately executed without
- terminating any processes or unmounting any file systems. This may result in data loss. Note that when
- is specified twice the power-off operation is executed by
- systemctl itself, and the system manager is not contacted. This means the command should
- succeed even when the system manager has crashed.
+ This command honors and in a similar way
+ as halt.
@@ -1517,14 +1515,6 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
users. This command is asynchronous; it will return after the reboot operation is enqueued,
without waiting for it to complete.
- If combined with , shutdown of all running services is skipped, however all
- processes are killed and all file systems are unmounted or mounted read-only, immediately followed by the
- reboot. If is specified twice, the operation is immediately executed without
- terminating any processes or unmounting any file systems. This may result in data loss. Note that when
- is specified twice the reboot operation is executed by
- systemctl itself, and the system manager is not contacted. This means the command should
- succeed even when the system manager has crashed.
-
If the switch is given, it will be passed as the optional
argument to the reboot2
system call.
@@ -1532,6 +1522,9 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
Options , , and
can be used to select what to do after the
reboot. See the descriptions of those options for details.
+
+ This command honors and in a similar way
+ as halt.
@@ -1544,9 +1537,8 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
asynchronous; it will return after the reboot operation is enqueued, without waiting for it to
complete.
- If combined with , shutdown of all running services is skipped, however all
- processes are killed and all file systems are unmounted or mounted read-only, immediately followed by the
- reboot.
+ This command honors and in a similar way
+ as halt.
@@ -2477,6 +2469,19 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
+
+
+
+
+ When used with halt, poweroff, reboot
+ or kexec, schedule the action to be performed at the given timestamp,
+ which should adhere to the syntax documented in systemd.time7
+ section "PARSING TIMESTAMPS". Specially, if show is given, the currently scheduled
+ action will be shown, which can be canceled by passing an empty string or cancel.
+
+
+
diff --git a/src/systemctl/systemctl-compat-halt.c b/src/systemctl/systemctl-compat-halt.c
index 8a0e4e62943..b9164c27abd 100644
--- a/src/systemctl/systemctl-compat-halt.c
+++ b/src/systemctl/systemctl-compat-halt.c
@@ -149,7 +149,7 @@ int halt_main(void) {
if (arg_force == 0) {
/* always try logind first */
if (arg_when > 0)
- r = logind_schedule_shutdown();
+ r = logind_schedule_shutdown(arg_action);
else {
r = logind_check_inhibitors(arg_action);
if (r < 0)
diff --git a/src/systemctl/systemctl-logind.c b/src/systemctl/systemctl-logind.c
index 1c3b68f09f1..fd8ca09de88 100644
--- a/src/systemctl/systemctl-logind.c
+++ b/src/systemctl/systemctl-logind.c
@@ -21,6 +21,8 @@ static int logind_set_wall_message(sd_bus *bus) {
_cleanup_free_ char *m = NULL;
int r;
+ assert(bus);
+
m = strv_join(arg_wall, " ");
if (!m)
return log_oom();
@@ -55,7 +57,10 @@ int logind_reboot(enum action a) {
sd_bus *bus;
int r;
- if (a < 0 || a >= _ACTION_MAX || !actions[a])
+ assert(a >= 0);
+ assert(a < _ACTION_MAX);
+
+ if (!actions[a])
return -EINVAL;
r = acquire_bus(BUS_FULL, &bus);
@@ -106,6 +111,9 @@ int logind_check_inhibitors(enum action a) {
unsigned c = 0;
int r;
+ assert(a >= 0);
+ assert(a < _ACTION_MAX);
+
if (arg_check_inhibitors == 0 || arg_force > 0)
return 0;
@@ -283,19 +291,21 @@ int prepare_boot_loader_entry(void) {
#endif
}
-int logind_schedule_shutdown(void) {
-
+int logind_schedule_shutdown(enum action a) {
#if ENABLE_LOGIND
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
const char *action;
sd_bus *bus;
int r;
+ assert(a >= 0);
+ assert(a < _ACTION_MAX);
+
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
- action = action_table[arg_action].verb;
+ action = action_table[a].verb;
if (!action)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Scheduling not supported for this action.");
@@ -346,7 +356,7 @@ int logind_show_shutdown(void) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
sd_bus *bus;
- const char *action = NULL;
+ const char *action, *pretty_action;
uint64_t elapse;
int r;
@@ -366,17 +376,23 @@ int logind_show_shutdown(void) {
return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "No scheduled shutdown.");
if (STR_IN_SET(action, "halt", "poweroff", "exit"))
- action = "Shutdown";
+ pretty_action = "Shutdown";
else if (streq(action, "kexec"))
- action = "Reboot via kexec";
+ pretty_action = "Reboot via kexec";
else if (streq(action, "reboot"))
- action = "Reboot";
+ pretty_action = "Reboot";
+ else /* If we don't recognize the action string, we'll show it as-is */
+ pretty_action = action;
- /* If we don't recognize the action string, we'll show it as-is */
-
- log_info("%s scheduled for %s, use 'shutdown -c' to cancel.",
- action,
- FORMAT_TIMESTAMP_STYLE(elapse, arg_timestamp_style));
+ if (arg_action == ACTION_SYSTEMCTL)
+ log_info("%s scheduled for %s, use 'systemctl %s --when=cancel' to cancel.",
+ pretty_action,
+ FORMAT_TIMESTAMP_STYLE(elapse, arg_timestamp_style),
+ action);
+ else
+ log_info("%s scheduled for %s, use 'shutdown -c' to cancel.",
+ pretty_action,
+ FORMAT_TIMESTAMP_STYLE(elapse, arg_timestamp_style));
return 0;
#else
diff --git a/src/systemctl/systemctl-logind.h b/src/systemctl/systemctl-logind.h
index 925f4559c1b..516f74952fa 100644
--- a/src/systemctl/systemctl-logind.h
+++ b/src/systemctl/systemctl-logind.h
@@ -10,7 +10,7 @@ int prepare_firmware_setup(void);
int prepare_boot_loader_menu(void);
int prepare_boot_loader_entry(void);
-int logind_schedule_shutdown(void);
+int logind_schedule_shutdown(enum action a);
int logind_cancel_shutdown(void);
int logind_show_shutdown(void);
diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c
index 1c50adff6ed..93432953b04 100644
--- a/src/systemctl/systemctl-start-special.c
+++ b/src/systemctl/systemctl-start-special.c
@@ -197,22 +197,33 @@ int verb_start_special(int argc, char *argv[], void *userdata) {
ACTION_POWEROFF,
ACTION_REBOOT,
ACTION_KEXEC,
- ACTION_HALT,
- ACTION_SUSPEND,
- ACTION_HIBERNATE,
- ACTION_HYBRID_SLEEP,
- ACTION_SUSPEND_THEN_HIBERNATE)) {
+ ACTION_HALT)) {
- r = logind_reboot(a);
- if (r >= 0)
- return r;
- if (IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS))
- /* Requested operation requires auth, is not supported or already in progress */
+ if (arg_when == 0)
+ r = logind_reboot(a);
+ else if (arg_when != USEC_INFINITY)
+ r = logind_schedule_shutdown(a);
+ else /* arg_when == USEC_INFINITY */
+ r = logind_cancel_shutdown();
+ if (r >= 0 || IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS))
+ /* The latter indicates that the requested operation requires auth,
+ * is not supported or already in progress, in which cases we ignore the error. */
return r;
/* On all other errors, try low-level operation. In order to minimize the difference
* between operation with and without logind, we explicitly enable non-blocking mode
* for this, as logind's shutdown operations are always non-blocking. */
+ arg_no_block = true;
+
+ } else if (IN_SET(a,
+ ACTION_SUSPEND,
+ ACTION_HIBERNATE,
+ ACTION_HYBRID_SLEEP,
+ ACTION_SUSPEND_THEN_HIBERNATE)) {
+
+ r = logind_reboot(a);
+ if (r >= 0 || IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS))
+ return r;
arg_no_block = true;
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index 862edada089..1a5beabc722 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -323,6 +323,8 @@ static int systemctl_help(void) {
" --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"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
@@ -447,6 +449,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
ARG_MARKED,
ARG_NO_WARN,
ARG_DROP_IN,
+ ARG_WHEN,
};
static const struct option options[] = {
@@ -511,6 +514,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
{ "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 },
{}
};
@@ -975,6 +979,30 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
arg_drop_in = optarg;
break;
+ case ARG_WHEN:
+ if (streq(optarg, "show")) {
+ r = logind_show_shutdown();
+ if (r < 0 && r != -ENODATA)
+ return r;
+
+ return 0;
+ }
+
+ if (STR_IN_SET(optarg, "", "cancel")) {
+ arg_when = USEC_INFINITY;
+ break;
+ }
+
+ r = parse_timestamp(optarg, &arg_when);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --when= argument '%s': %m", optarg);
+
+ if (!timestamp_is_set(arg_when))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Invalid timestamp '%s' specified for --when=.", optarg);
+
+ break;
+
case '.':
/* Output an error mimicking getopt, and print a hint afterwards */
log_error("%s: invalid option -- '.'", program_invocation_name);
diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c
index e0386d4ec58..a7b24674528 100644
--- a/src/test/test-time-util.c
+++ b/src/test/test-time-util.c
@@ -663,6 +663,11 @@ static bool timezone_equal(usec_t today, usec_t target) {
static void test_parse_timestamp_impl(const char *tz) {
usec_t today, now_usec;
+ /* Invalid: Ensure that systemctl reboot --when=show and --when=cancel
+ * will not result in ambiguities */
+ assert_se(parse_timestamp("show", NULL) == -EINVAL);
+ assert_se(parse_timestamp("cancel", NULL) == -EINVAL);
+
/* UTC */
test_parse_timestamp_one("Thu 1970-01-01 00:01 UTC", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Thu 1970-01-01 00:00:01 UTC", 0, USEC_PER_SEC);