mirror of
https://github.com/systemd/systemd.git
synced 2026-08-03 06:30:30 +00:00
Merge pull request #26683 from YHNdnzj/systemctl-shutdown-when
systemctl: add --when for scheduled poweroff/halt/reboot/kexec support
This commit is contained in:
@@ -1486,6 +1486,9 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
|
||||
<option>--force</option> is specified twice the halt operation is executed by <command>systemctl</command>
|
||||
itself, and the system manager is not contacted. This means the command should succeed even when the system
|
||||
manager has crashed.</para>
|
||||
|
||||
<para>If combined with <option>--when=</option>, shutdown will be scheduled after the given timestamp.
|
||||
And <option>--when=cancel</option> will cancel the shutdown.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
@@ -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.</para>
|
||||
|
||||
<para>If combined with <option>--force</option>, 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 <option>--force</option> 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
|
||||
<option>--force</option> is specified twice the power-off operation is executed by
|
||||
<command>systemctl</command> itself, and the system manager is not contacted. This means the command should
|
||||
succeed even when the system manager has crashed.</para>
|
||||
<para>This command honors <option>--force</option> and <option>--when=</option> in a similar way
|
||||
as <command>halt</command>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
@@ -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.</para>
|
||||
|
||||
<para>If combined with <option>--force</option>, 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 <option>--force</option> 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
|
||||
<option>--force</option> is specified twice the reboot operation is executed by
|
||||
<command>systemctl</command> itself, and the system manager is not contacted. This means the command should
|
||||
succeed even when the system manager has crashed.</para>
|
||||
|
||||
<para>If the switch <option>--reboot-argument=</option> is given, it will be passed as the optional
|
||||
argument to the <citerefentry><refentrytitle>reboot</refentrytitle><manvolnum>2</manvolnum></citerefentry>
|
||||
system call.</para>
|
||||
@@ -1532,6 +1522,9 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
|
||||
<para>Options <option>--boot-loader-entry=</option>, <option>--boot-loader-menu=</option>, and
|
||||
<option>--firmware-setup</option> can be used to select what to do <emphasis>after</emphasis> the
|
||||
reboot. See the descriptions of those options for details.</para>
|
||||
|
||||
<para>This command honors <option>--force</option> and <option>--when=</option> in a similar way
|
||||
as <command>halt</command>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
@@ -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.</para>
|
||||
|
||||
<para>If combined with <option>--force</option>, 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.</para>
|
||||
<para>This command honors <option>--force</option> and <option>--when=</option> in a similar way
|
||||
as <command>halt</command>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
@@ -2477,6 +2469,19 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--when=</option></term>
|
||||
|
||||
<listitem>
|
||||
<para>When used with <command>halt</command>, <command>poweroff</command>, <command>reboot</command>
|
||||
or <command>kexec</command>, schedule the action to be performed at the given timestamp,
|
||||
which should adhere to the syntax documented in <citerefentry
|
||||
project='man-pages'><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>
|
||||
section "PARSING TIMESTAMPS". Specially, if <literal>show</literal> is given, the currently scheduled
|
||||
action will be shown, which can be canceled by passing an empty string or <literal>cancel</literal>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="user-system-options.xml" xpointer="host" />
|
||||
<xi:include href="user-system-options.xml" xpointer="machine" />
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user