mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
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:
parent
8f9a307fec
commit
13ffc60749
@ -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"
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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 &&
|
||||
|
@ -44,6 +44,7 @@ typedef enum ManagerObjective {
|
||||
MANAGER_RELOAD,
|
||||
MANAGER_REEXECUTE,
|
||||
MANAGER_REBOOT,
|
||||
MANAGER_SOFT_REBOOT,
|
||||
MANAGER_POWEROFF,
|
||||
MANAGER_HALT,
|
||||
MANAGER_KEXEC,
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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
18
units/soft-reboot.target
Normal 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
|
16
units/systemd-soft-reboot.service
Normal file
16
units/systemd-soft-reboot.service
Normal 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
|
Loading…
Reference in New Issue
Block a user