pid1: add "soft-reboot" reboot method

This adds a new mechanism for rebooting, a form of "userspace reboot"
hereby dubbed "soft-reboot". It will stop all services as in a usual
shutdown, possibly transition into a new root fs and then issue a fresh
initial transaction. The kernel is not replaced.

File descriptors can be passed over, thus opening the door for leaving
certain resources around between such reboots.

Usecase: this is an extremely quick way to reset userspace fully when
updating image based systems, without going through a full
hardware/firmware/boot loader/kernel/initrd cycle. It minimizes "grayout time"
for OS updates. (In particular when combined with kernel live patching)
This commit is contained in:
Lennart Poettering 2023-04-27 17:23:18 +02:00
parent 8f9a307fec
commit 13ffc60749
13 changed files with 161 additions and 26 deletions

View file

@ -14,6 +14,7 @@
#define SPECIAL_HALT_TARGET "halt.target"
#define SPECIAL_POWEROFF_TARGET "poweroff.target"
#define SPECIAL_REBOOT_TARGET "reboot.target"
#define SPECIAL_SOFT_REBOOT_TARGET "soft-reboot.target"
#define SPECIAL_KEXEC_TARGET "kexec.target"
#define SPECIAL_EXIT_TARGET "exit.target"
#define SPECIAL_SUSPEND_TARGET "suspend.target"

View file

@ -1713,6 +1713,45 @@ static int method_reboot(sd_bus_message *message, void *userdata, sd_bus_error *
return sd_bus_reply_method_return(message, NULL);
}
static int method_soft_reboot(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_free_ char *rt = NULL;
Manager *m = ASSERT_PTR(userdata);
const char *root;
int r;
assert(message);
r = verify_run_space_permissive("soft reboot may fail", error);
if (r < 0)
return r;
r = mac_selinux_access_check(message, "reboot", error);
if (r < 0)
return r;
r = sd_bus_message_read(message, "s", &root);
if (r < 0)
return r;
if (!isempty(root)) {
if (!path_is_valid(root))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"New root directory '%s' must be a valid path.", root);
if (!path_is_absolute(root))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"New root directory path '%s' is not absolute.", root);
rt = strdup(root);
if (!rt)
return -ENOMEM;
}
free_and_replace(m->switch_root, rt);
m->objective = MANAGER_SOFT_REBOOT;
return sd_bus_reply_method_return(message, NULL);
}
static int method_poweroff(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = ASSERT_PTR(userdata);
int r;
@ -1772,8 +1811,8 @@ static int method_kexec(sd_bus_message *message, void *userdata, sd_bus_error *e
static int method_switch_root(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_free_ char *ri = NULL, *rt = NULL;
const char *root, *init;
Manager *m = ASSERT_PTR(userdata);
const char *root, *init;
int r;
assert(message);
@ -3260,6 +3299,11 @@ const sd_bus_vtable bus_manager_vtable[] = {
NULL,
method_reboot,
SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)),
SD_BUS_METHOD_WITH_ARGS("SoftReboot",
SD_BUS_ARGS("s", new_root),
SD_BUS_NO_RESULT,
method_soft_reboot,
SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)),
SD_BUS_METHOD("PowerOff",
NULL,
NULL,

View file

@ -22,6 +22,8 @@ static const char* const emergency_action_table[_EMERGENCY_ACTION_MAX] = {
[EMERGENCY_ACTION_POWEROFF_IMMEDIATE] = "poweroff-immediate",
[EMERGENCY_ACTION_EXIT] = "exit",
[EMERGENCY_ACTION_EXIT_FORCE] = "exit-force",
[EMERGENCY_ACTION_SOFT_REBOOT] = "soft-reboot",
[EMERGENCY_ACTION_SOFT_REBOOT_FORCE] = "soft-reboot-force",
};
static void log_and_status(Manager *m, bool warn, const char *message, const char *reason) {
@ -47,7 +49,7 @@ void emergency_action(
assert(action < _EMERGENCY_ACTION_MAX);
/* Is the special shutdown target active or queued? If so, we are in shutdown state */
if (IN_SET(action, EMERGENCY_ACTION_REBOOT, EMERGENCY_ACTION_POWEROFF, EMERGENCY_ACTION_EXIT)) {
if (IN_SET(action, EMERGENCY_ACTION_REBOOT, EMERGENCY_ACTION_SOFT_REBOOT, EMERGENCY_ACTION_POWEROFF, EMERGENCY_ACTION_EXIT)) {
u = manager_get_unit(m, SPECIAL_SHUTDOWN_TARGET);
if (u && unit_active_or_pending(u)) {
log_notice("Shutdown is already active. Skipping emergency action request %s.",
@ -80,7 +82,6 @@ void emergency_action(
(void) update_reboot_parameter_and_warn(reboot_arg, true);
m->objective = MANAGER_REBOOT;
break;
case EMERGENCY_ACTION_REBOOT_IMMEDIATE:
@ -98,6 +99,18 @@ void emergency_action(
(void) reboot(RB_AUTOBOOT);
break;
case EMERGENCY_ACTION_SOFT_REBOOT:
log_and_status(m, warn, "Soft-rebooting", reason);
(void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_SOFT_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL);
break;
case EMERGENCY_ACTION_SOFT_REBOOT_FORCE:
log_and_status(m, warn, "Forcibly soft-rebooting", reason);
m->objective = MANAGER_SOFT_REBOOT;
break;
case EMERGENCY_ACTION_EXIT:
if (exit_status >= 0)

View file

@ -16,6 +16,8 @@ typedef enum EmergencyAction {
EMERGENCY_ACTION_EXIT,
_EMERGENCY_ACTION_FIRST_USER_ACTION = EMERGENCY_ACTION_EXIT,
EMERGENCY_ACTION_EXIT_FORCE,
EMERGENCY_ACTION_SOFT_REBOOT,
EMERGENCY_ACTION_SOFT_REBOOT_FORCE,
_EMERGENCY_ACTION_MAX,
_EMERGENCY_ACTION_INVALID = -EINVAL,
} EmergencyAction;

View file

@ -1788,7 +1788,7 @@ static int do_reexecute(
const char **args;
int r;
assert(IN_SET(objective, MANAGER_REEXECUTE, MANAGER_SWITCH_ROOT));
assert(IN_SET(objective, MANAGER_REEXECUTE, MANAGER_SWITCH_ROOT, MANAGER_SOFT_REBOOT));
assert(argc >= 0);
assert(saved_rlimit_nofile);
assert(saved_rlimit_memlock);
@ -1823,13 +1823,26 @@ static int do_reexecute(
if (saved_rlimit_memlock->rlim_cur != RLIM_INFINITY)
(void) setrlimit(RLIMIT_MEMLOCK, saved_rlimit_memlock);
if (switch_root_dir) {
/* Kill all remaining processes from the initrd, but don't wait for them, so that we can
* handle the SIGCHLD for them after deserializing. */
broadcast_signal(SIGTERM, false, true, arg_default_timeout_stop_usec);
/* Kill all remaining processes from the initrd, but don't wait for them, so that we can handle the
* SIGCHLD for them after deserializing. */
if (IN_SET(objective, MANAGER_SWITCH_ROOT, MANAGER_SOFT_REBOOT))
broadcast_signal(SIGTERM, /* wait_for_exit= */ false, /* send_sighup= */ true, arg_default_timeout_stop_usec);
if (!switch_root_dir && objective == MANAGER_SOFT_REBOOT) {
/* If no switch root dir is specified, then check if /run/nextroot/ qualifies and use that */
r = path_is_os_tree("/run/nextroot");
if (r < 0 && r != -ENOENT)
log_debug_errno(r, "Failed to determine if /run/nextroot/ is a valid OS tree, ignoring: %m");
else if (r > 0)
switch_root_dir = "/run/nextroot";
}
if (switch_root_dir) {
/* And switch root with MS_MOVE, because we remove the old directory afterwards and detach it. */
r = switch_root(switch_root_dir, /* old_root_after= */ NULL, MS_MOVE);
r = switch_root(/* new_root= */ switch_root_dir,
/* old_root_after= */ NULL,
MS_MOVE,
/* destroy_old_root= */ objective == MANAGER_SWITCH_ROOT);
if (r < 0)
log_error_errno(r, "Failed to switch root, trying to continue: %m");
}
@ -1851,7 +1864,7 @@ static int do_reexecute(
i = 1; /* Leave args[0] empty for now. */
filter_args(args, &i, argv, argc);
if (switch_root_dir)
if (IN_SET(objective, MANAGER_SWITCH_ROOT, MANAGER_SOFT_REBOOT))
args[i++] = "--switched-root";
args[i++] = runtime_scope_cmdline_option_to_string(arg_runtime_scope);
args[i++] = sfd;
@ -2036,6 +2049,24 @@ static int invoke_main_loop(
return objective;
case MANAGER_SOFT_REBOOT:
manager_send_reloading(m);
manager_set_switching_root(m, true);
r = prepare_reexecute(m, &arg_serialization, ret_fds, /* switching_root= */ true);
if (r < 0) {
*ret_error_message = "Failed to prepare for reexecution";
return r;
}
log_notice("Soft-rebooting.");
*ret_retval = EXIT_SUCCESS;
*ret_switch_root_dir = TAKE_PTR(m->switch_root);
*ret_switch_root_init = NULL;
return objective;
case MANAGER_EXIT:
if (MANAGER_IS_USER(m)) {
log_debug("Exit.");
@ -3093,6 +3124,7 @@ int main(int argc, char *argv[]) {
MANAGER_RELOAD,
MANAGER_REEXECUTE,
MANAGER_REBOOT,
MANAGER_SOFT_REBOOT,
MANAGER_POWEROFF,
MANAGER_HALT,
MANAGER_KEXEC,
@ -3109,7 +3141,7 @@ finish:
mac_selinux_finish();
if (IN_SET(r, MANAGER_REEXECUTE, MANAGER_SWITCH_ROOT))
if (IN_SET(r, MANAGER_REEXECUTE, MANAGER_SWITCH_ROOT, MANAGER_SOFT_REBOOT))
r = do_reexecute(r,
argc, argv,
&saved_rlimit_nofile,

View file

@ -561,6 +561,7 @@ static int manager_setup_signals(Manager *m) {
SIGRTMIN+4, /* systemd: start poweroff.target */
SIGRTMIN+5, /* systemd: start reboot.target */
SIGRTMIN+6, /* systemd: start kexec.target */
SIGRTMIN+7, /* systemd: start soft-reboot.target */
/* ... space for more special targets ... */
@ -568,9 +569,7 @@ static int manager_setup_signals(Manager *m) {
SIGRTMIN+14, /* systemd: Immediate poweroff */
SIGRTMIN+15, /* systemd: Immediate reboot */
SIGRTMIN+16, /* systemd: Immediate kexec */
/* ... space for one more immediate system state change ... */
SIGRTMIN+17, /* systemd: Immediate soft-reboot */
SIGRTMIN+18, /* systemd: control command */
/* ... space ... */
@ -1627,7 +1626,7 @@ Manager* manager_free(Manager *m) {
unit_vtable[c]->shutdown(m);
/* Keep the cgroup hierarchy in place except when we know we are going down for good */
manager_shutdown_cgroup(m, IN_SET(m->objective, MANAGER_EXIT, MANAGER_REBOOT, MANAGER_POWEROFF, MANAGER_HALT, MANAGER_KEXEC));
manager_shutdown_cgroup(m, /* delete= */ IN_SET(m->objective, MANAGER_EXIT, MANAGER_REBOOT, MANAGER_POWEROFF, MANAGER_HALT, MANAGER_KEXEC));
lookup_paths_flush_generator(&m->lookup_paths);
@ -2979,13 +2978,14 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
const char *target;
JobMode mode;
} target_table[] = {
[0] = { SPECIAL_DEFAULT_TARGET, JOB_ISOLATE },
[1] = { SPECIAL_RESCUE_TARGET, JOB_ISOLATE },
[2] = { SPECIAL_EMERGENCY_TARGET, JOB_ISOLATE },
[3] = { SPECIAL_HALT_TARGET, JOB_REPLACE_IRREVERSIBLY },
[4] = { SPECIAL_POWEROFF_TARGET, JOB_REPLACE_IRREVERSIBLY },
[5] = { SPECIAL_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY },
[6] = { SPECIAL_KEXEC_TARGET, JOB_REPLACE_IRREVERSIBLY },
[0] = { SPECIAL_DEFAULT_TARGET, JOB_ISOLATE },
[1] = { SPECIAL_RESCUE_TARGET, JOB_ISOLATE },
[2] = { SPECIAL_EMERGENCY_TARGET, JOB_ISOLATE },
[3] = { SPECIAL_HALT_TARGET, JOB_REPLACE_IRREVERSIBLY },
[4] = { SPECIAL_POWEROFF_TARGET, JOB_REPLACE_IRREVERSIBLY },
[5] = { SPECIAL_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY },
[6] = { SPECIAL_KEXEC_TARGET, JOB_REPLACE_IRREVERSIBLY },
[7] = { SPECIAL_SOFT_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY },
};
/* Starting SIGRTMIN+13, so that target halt and system halt are 10 apart */
@ -2994,6 +2994,7 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
[1] = MANAGER_POWEROFF,
[2] = MANAGER_REBOOT,
[3] = MANAGER_KEXEC,
[4] = MANAGER_SOFT_REBOOT,
};
if ((int) sfsi.ssi_signo >= SIGRTMIN+0 &&

View file

@ -44,6 +44,7 @@ typedef enum ManagerObjective {
MANAGER_RELOAD,
MANAGER_REEXECUTE,
MANAGER_REBOOT,
MANAGER_SOFT_REBOOT,
MANAGER_POWEROFF,
MANAGER_HALT,
MANAGER_KEXEC,

View file

@ -27,7 +27,8 @@
int switch_root(const char *new_root,
const char *old_root_after, /* path below the new root, where to place the old root after the transition; may be NULL to unmount it */
unsigned long mount_flags) { /* MS_MOVE or MS_BIND used for /proc/, /dev/, /run/, /sys/ */
unsigned long mount_flags, /* MS_MOVE or MS_BIND used for /proc/, /dev/, /run/, /sys/ */
bool destroy_old_root) {
_cleanup_close_ int old_root_fd = -EBADF, new_root_fd = -EBADF;
_cleanup_free_ char *resolved_old_root_after = NULL;
@ -144,7 +145,7 @@ int switch_root(const char *new_root,
return log_error_errno(errno, "Failed to change directory: %m");
}
if (istmp) {
if (istmp && destroy_old_root) {
struct stat rb;
if (fstat(old_root_fd, &rb) < 0)

View file

@ -3,4 +3,4 @@
#include <stdbool.h>
int switch_root(const char *new_root, const char *old_root_after, unsigned long mount_flags);
int switch_root(const char *new_root, const char *old_root_after, unsigned long mount_flags, bool destroy_old_root);

View file

@ -169,7 +169,11 @@ static int switch_root_initramfs(void) {
* /run/initramfs/shutdown will take care of these.
* Also do not detach the old root, because /run/initramfs/shutdown needs to access it.
*/
return switch_root("/run/initramfs", "/oldroot", MS_BIND);
return switch_root(
/* new_root= */ "/run/initramfs",
/* old_root_after= */ "/oldroot",
MS_BIND,
/* destroy_old_root= */ false);
}
/* Read the following fields from /proc/meminfo:

View file

@ -67,6 +67,7 @@ units = [
['proc-sys-fs-binfmt_misc.mount', 'ENABLE_BINFMT'],
['reboot.target', '',
'ctrl-alt-del.target' + (with_runlevels ? ' runlevel6.target' : '')],
['soft-reboot.target', ''],
['remote-cryptsetup.target', 'HAVE_LIBCRYPTSETUP',
'initrd-root-device.target.wants/'],
['remote-veritysetup.target', 'HAVE_LIBCRYPTSETUP',
@ -137,6 +138,7 @@ units = [
['systemd-networkd.socket', 'ENABLE_NETWORKD'],
['systemd-poweroff.service', ''],
['systemd-reboot.service', ''],
['systemd-soft-reboot.service', ''],
['systemd-rfkill.socket', 'ENABLE_RFKILL'],
['systemd-sysext.service', 'ENABLE_SYSEXT'],
['systemd-confext.service', 'ENABLE_SYSEXT'],

18
units/soft-reboot.target Normal file
View file

@ -0,0 +1,18 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Reboot System Userspace
Documentation=man:systemd.special(7)
DefaultDependencies=no
Requires=systemd-soft-reboot.service
After=systemd-soft-reboot.service
AllowIsolate=yes
JobTimeoutSec=30min
JobTimeoutAction=soft-reboot-force

View file

@ -0,0 +1,16 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Reboot System Userspace
Documentation=man:systemd-soft-reboot.service(8)
DefaultDependencies=no
Requires=shutdown.target umount.target final.target
After=shutdown.target umount.target final.target
SuccessAction=soft-reboot-force