From 13ffc60749df0ca7c76cfcac317b41a05679b364 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 27 Apr 2023 17:23:18 +0200 Subject: [PATCH 01/14] 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) --- src/basic/special.h | 1 + src/core/dbus-manager.c | 46 ++++++++++++++++++++++++++++- src/core/emergency-action.c | 17 +++++++++-- src/core/emergency-action.h | 2 ++ src/core/main.c | 48 +++++++++++++++++++++++++------ src/core/manager.c | 23 ++++++++------- src/core/manager.h | 1 + src/shared/switch-root.c | 5 ++-- src/shared/switch-root.h | 2 +- src/shutdown/shutdown.c | 6 +++- units/meson.build | 2 ++ units/soft-reboot.target | 18 ++++++++++++ units/systemd-soft-reboot.service | 16 +++++++++++ 13 files changed, 161 insertions(+), 26 deletions(-) create mode 100644 units/soft-reboot.target create mode 100644 units/systemd-soft-reboot.service diff --git a/src/basic/special.h b/src/basic/special.h index ed3852a450..98fcddf631 100644 --- a/src/basic/special.h +++ b/src/basic/special.h @@ -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" diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index c02ad09c07..f156b809cb 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -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, diff --git a/src/core/emergency-action.c b/src/core/emergency-action.c index 5b9ec75922..0b458ebf28 100644 --- a/src/core/emergency-action.c +++ b/src/core/emergency-action.c @@ -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) diff --git a/src/core/emergency-action.h b/src/core/emergency-action.h index 89a1dead23..2c61c9c7d6 100644 --- a/src/core/emergency-action.h +++ b/src/core/emergency-action.h @@ -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; diff --git a/src/core/main.c b/src/core/main.c index 3eb53577eb..dc7e4a9767 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -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, diff --git a/src/core/manager.c b/src/core/manager.c index 78d1a032e6..1df48d3cbc 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -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 && diff --git a/src/core/manager.h b/src/core/manager.h index 08dcee3ce7..a6c87dc852 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -44,6 +44,7 @@ typedef enum ManagerObjective { MANAGER_RELOAD, MANAGER_REEXECUTE, MANAGER_REBOOT, + MANAGER_SOFT_REBOOT, MANAGER_POWEROFF, MANAGER_HALT, MANAGER_KEXEC, diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index c7b562143d..b1cbcc5e91 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -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) diff --git a/src/shared/switch-root.h b/src/shared/switch-root.h index e3fabae3d9..cba84e9b01 100644 --- a/src/shared/switch-root.h +++ b/src/shared/switch-root.h @@ -3,4 +3,4 @@ #include -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); diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index cf0351cf78..dc713e529f 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -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: diff --git a/units/meson.build b/units/meson.build index fa76946ddb..e4f064c1ab 100644 --- a/units/meson.build +++ b/units/meson.build @@ -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'], diff --git a/units/soft-reboot.target b/units/soft-reboot.target new file mode 100644 index 0000000000..6a6c772875 --- /dev/null +++ b/units/soft-reboot.target @@ -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 diff --git a/units/systemd-soft-reboot.service b/units/systemd-soft-reboot.service new file mode 100644 index 0000000000..35ba3a9fc4 --- /dev/null +++ b/units/systemd-soft-reboot.service @@ -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 From 9edf5af57bdfd2de1ef32f2f3d3609b917d5cedf Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 27 Apr 2023 21:56:14 +0200 Subject: [PATCH 02/14] logind: add support for 'soft-reboot' reboots --- src/basic/login-util.h | 3 ++- src/login/logind-action.c | 15 +++++++++++++++ src/login/logind-action.h | 1 + src/login/logind-dbus.c | 18 +++++++++++++++--- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/basic/login-util.h b/src/basic/login-util.h index 841fd324f1..613fb0f86f 100644 --- a/src/basic/login-util.h +++ b/src/basic/login-util.h @@ -6,11 +6,12 @@ #define SD_LOGIND_ROOT_CHECK_INHIBITORS (UINT64_C(1) << 0) #define SD_LOGIND_REBOOT_VIA_KEXEC (UINT64_C(1) << 1) +#define SD_LOGIND_SOFT_REBOOT (UINT64_C(1) << 2) /* For internal use only */ #define SD_LOGIND_INTERACTIVE (UINT64_C(1) << 63) -#define SD_LOGIND_SHUTDOWN_AND_SLEEP_FLAGS_PUBLIC (SD_LOGIND_ROOT_CHECK_INHIBITORS|SD_LOGIND_REBOOT_VIA_KEXEC) +#define SD_LOGIND_SHUTDOWN_AND_SLEEP_FLAGS_PUBLIC (SD_LOGIND_ROOT_CHECK_INHIBITORS|SD_LOGIND_REBOOT_VIA_KEXEC|SD_LOGIND_SOFT_REBOOT) #define SD_LOGIND_SHUTDOWN_AND_SLEEP_FLAGS_ALL (SD_LOGIND_SHUTDOWN_AND_SLEEP_FLAGS_PUBLIC|SD_LOGIND_INTERACTIVE) bool session_id_valid(const char *id); diff --git a/src/login/logind-action.c b/src/login/logind-action.c index c6a36a1556..42dc36b670 100644 --- a/src/login/logind-action.c +++ b/src/login/logind-action.c @@ -67,6 +67,18 @@ static const HandleActionData handle_action_data_table[_HANDLE_ACTION_MAX] = { .message = "System is rebooting with kexec", .log_verb = "kexec", }, + [HANDLE_SOFT_REBOOT] = { + .handle = HANDLE_SOFT_REBOOT, + .target = SPECIAL_SOFT_REBOOT_TARGET, + .inhibit_what = INHIBIT_SHUTDOWN, + .polkit_action = "org.freedesktop.login1.reboot", + .polkit_action_multiple_sessions = "org.freedesktop.login1.reboot-multiple-sessions", + .polkit_action_ignore_inhibit = "org.freedesktop.login1.reboot-ignore-inhibit", + .sleep_operation = _SLEEP_OPERATION_INVALID, + .message_id = SD_MESSAGE_SHUTDOWN_STR, + .message = "System userspace is rebooting", + .log_verb = "soft-reboot", + }, [HANDLE_SUSPEND] = { .handle = HANDLE_SUSPEND, .target = SPECIAL_SUSPEND_TARGET, @@ -133,6 +145,7 @@ int manager_handle_action( [HANDLE_REBOOT] = "Rebooting...", [HANDLE_HALT] = "Halting...", [HANDLE_KEXEC] = "Rebooting via kexec...", + [HANDLE_SOFT_REBOOT] = "Rebooting userspace...", [HANDLE_SUSPEND] = "Suspending...", [HANDLE_HIBERNATE] = "Hibernating...", [HANDLE_HYBRID_SLEEP] = "Hibernating and suspending...", @@ -257,6 +270,7 @@ static const char* const handle_action_verb_table[_HANDLE_ACTION_MAX] = { [HANDLE_REBOOT] = "reboot", [HANDLE_HALT] = "halt", [HANDLE_KEXEC] = "kexec", + [HANDLE_SOFT_REBOOT] = "soft-reboot", [HANDLE_SUSPEND] = "suspend", [HANDLE_HIBERNATE] = "hibernate", [HANDLE_HYBRID_SLEEP] = "enter hybrid sleep", @@ -273,6 +287,7 @@ static const char* const handle_action_table[_HANDLE_ACTION_MAX] = { [HANDLE_REBOOT] = "reboot", [HANDLE_HALT] = "halt", [HANDLE_KEXEC] = "kexec", + [HANDLE_SOFT_REBOOT] = "soft-reboot", [HANDLE_SUSPEND] = "suspend", [HANDLE_HIBERNATE] = "hibernate", [HANDLE_HYBRID_SLEEP] = "hybrid-sleep", diff --git a/src/login/logind-action.h b/src/login/logind-action.h index 9a01af1690..dfef6400e9 100644 --- a/src/login/logind-action.h +++ b/src/login/logind-action.h @@ -9,6 +9,7 @@ typedef enum HandleAction { HANDLE_REBOOT, HANDLE_HALT, HANDLE_KEXEC, + HANDLE_SOFT_REBOOT, HANDLE_SUSPEND, HANDLE_HIBERNATE, HANDLE_HYBRID_SLEEP, diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index c5e0fda5e5..e1e9b723c8 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -1860,9 +1860,19 @@ static int method_do_shutdown_or_sleep( if ((flags & ~SD_LOGIND_SHUTDOWN_AND_SLEEP_FLAGS_PUBLIC) != 0) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); - if (a->handle != HANDLE_REBOOT && (flags & SD_LOGIND_REBOOT_VIA_KEXEC)) + + if (FLAGS_SET(flags, (SD_LOGIND_REBOOT_VIA_KEXEC|SD_LOGIND_SOFT_REBOOT))) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, - "Reboot via kexec is only applicable with reboot operations"); + "Both reboot via kexec and soft reboot selected, which is not supported"); + + if (a->handle != HANDLE_REBOOT) { + if (flags & SD_LOGIND_REBOOT_VIA_KEXEC) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, + "Reboot via kexec option is only applicable with reboot operations"); + if (flags & SD_LOGIND_SOFT_REBOOT) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, + "Soft reboot option is only applicable with reboot operations"); + } } else { /* Old style method: no flags parameter, but interactive bool passed as boolean in * payload. Let's convert this argument to the new-style flags parameter for our internal @@ -1878,6 +1888,8 @@ static int method_do_shutdown_or_sleep( if ((flags & SD_LOGIND_REBOOT_VIA_KEXEC) && kexec_loaded()) a = handle_action_lookup(HANDLE_KEXEC); + else if ((flags & SD_LOGIND_SOFT_REBOOT)) + a = handle_action_lookup(HANDLE_SOFT_REBOOT); /* Don't allow multiple jobs being executed at the same time */ if (m->delayed_action) @@ -2221,7 +2233,7 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ } handle = handle_action_from_string(type); - if (!IN_SET(handle, HANDLE_POWEROFF, HANDLE_REBOOT, HANDLE_HALT, HANDLE_KEXEC)) + if (!IN_SET(handle, HANDLE_POWEROFF, HANDLE_REBOOT, HANDLE_SOFT_REBOOT, HANDLE_HALT, HANDLE_KEXEC)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Unsupported shutdown type"); a = handle_action_lookup(handle); From 34f21ff610a3a77bf6e5064f1d0eb567f9a90fb2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 27 Apr 2023 21:56:20 +0200 Subject: [PATCH 03/14] systemctl: add "systemctl soft-reboot" command --- src/systemctl/systemctl-logind.c | 2 ++ src/systemctl/systemctl-start-special.c | 1 + src/systemctl/systemctl-start-unit.c | 1 + src/systemctl/systemctl-trivial-method.c | 1 + src/systemctl/systemctl.c | 7 +++++-- src/systemctl/systemctl.h | 1 + 6 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/systemctl/systemctl-logind.c b/src/systemctl/systemctl-logind.c index fd8ca09de8..a4486dcce9 100644 --- a/src/systemctl/systemctl-logind.c +++ b/src/systemctl/systemctl-logind.c @@ -45,6 +45,7 @@ int logind_reboot(enum action a) { [ACTION_POWEROFF] = "PowerOff", [ACTION_REBOOT] = "Reboot", [ACTION_KEXEC] = "Reboot", + [ACTION_SOFT_REBOOT] = "Reboot", [ACTION_HALT] = "Halt", [ACTION_SUSPEND] = "Suspend", [ACTION_HIBERNATE] = "Hibernate", @@ -80,6 +81,7 @@ int logind_reboot(enum action a) { SET_FLAG(flags, SD_LOGIND_ROOT_CHECK_INHIBITORS, arg_check_inhibitors > 0); SET_FLAG(flags, SD_LOGIND_REBOOT_VIA_KEXEC, a == ACTION_KEXEC); + SET_FLAG(flags, SD_LOGIND_SOFT_REBOOT, a == ACTION_SOFT_REBOOT); r = bus_call_method(bus, bus_login_mgr, method_with_flags, &error, NULL, "t", flags); if (r >= 0) diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c index c6084f373e..6e2238f2a5 100644 --- a/src/systemctl/systemctl-start-special.c +++ b/src/systemctl/systemctl-start-special.c @@ -199,6 +199,7 @@ int verb_start_special(int argc, char *argv[], void *userdata) { case ACTION_REBOOT: case ACTION_KEXEC: case ACTION_HALT: + case ACTION_SOFT_REBOOT: if (arg_when == 0) r = logind_reboot(a); else if (arg_when != USEC_INFINITY) diff --git a/src/systemctl/systemctl-start-unit.c b/src/systemctl/systemctl-start-unit.c index 3dac7da460..8268217967 100644 --- a/src/systemctl/systemctl-start-unit.c +++ b/src/systemctl/systemctl-start-unit.c @@ -222,6 +222,7 @@ const struct action_metadata action_table[_ACTION_MAX] = { [ACTION_POWEROFF] = { SPECIAL_POWEROFF_TARGET, "poweroff", "replace-irreversibly" }, [ACTION_REBOOT] = { SPECIAL_REBOOT_TARGET, "reboot", "replace-irreversibly" }, [ACTION_KEXEC] = { SPECIAL_KEXEC_TARGET, "kexec", "replace-irreversibly" }, + [ACTION_SOFT_REBOOT] = { SPECIAL_SOFT_REBOOT_TARGET, "soft-reboot", "replace-irreversibly" }, [ACTION_RUNLEVEL2] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, [ACTION_RUNLEVEL3] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, [ACTION_RUNLEVEL4] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, diff --git a/src/systemctl/systemctl-trivial-method.c b/src/systemctl/systemctl-trivial-method.c index 5e530f3a14..02a29123e7 100644 --- a/src/systemctl/systemctl-trivial-method.c +++ b/src/systemctl/systemctl-trivial-method.c @@ -30,6 +30,7 @@ int verb_trivial_method(int argc, char *argv[], void *userdata) { streq(argv[0], "halt") ? "Halt" : streq(argv[0], "reboot") ? "Reboot" : streq(argv[0], "kexec") ? "KExec" : + streq(argv[0], "soft-reboot") ? "SoftReboot" : streq(argv[0], "exit") ? "Exit" : /* poweroff */ "PowerOff"; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index b31a59785b..143c8b41b2 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -242,6 +242,7 @@ static int systemctl_help(void) { " poweroff Shut down and power-off the system\n" " reboot Shut down and reboot the system\n" " kexec Shut down and reboot the system with kexec\n" + " soft-reboot Shut down and reboot userspace\n" " exit [EXIT_CODE] Request user instance or container exit\n" " switch-root [ROOT [INIT]] Change to a different root file system\n" " suspend Suspend the system\n" @@ -285,8 +286,9 @@ static int systemctl_help(void) { " --now Start or stop unit after enabling or disabling it\n" " --dry-run Only print what would be done\n" " Currently supported by verbs: halt, poweroff, reboot,\n" - " kexec, suspend, hibernate, suspend-then-hibernate,\n" - " hybrid-sleep, default, rescue, emergency, and exit.\n" + " kexec, soft-reboot, suspend, hibernate, \n" + " suspend-then-hibernate, hybrid-sleep, default,\n" + " rescue, emergency, and exit.\n" " -q --quiet Suppress output\n" " --no-warn Suppress several warnings shown by default\n" " --wait For (re)start, wait until service stopped again\n" @@ -1180,6 +1182,7 @@ static int systemctl_main(int argc, char *argv[]) { { "poweroff", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, { "reboot", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, { "kexec", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, + { "soft-reboot", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, { "suspend", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, { "hibernate", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, { "hybrid-sleep", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h index 03645624ee..e8ba8f76a0 100644 --- a/src/systemctl/systemctl.h +++ b/src/systemctl/systemctl.h @@ -16,6 +16,7 @@ enum action { ACTION_POWEROFF, ACTION_REBOOT, ACTION_KEXEC, + ACTION_SOFT_REBOOT, ACTION_EXIT, ACTION_SUSPEND, ACTION_HIBERNATE, From 7c764d45997721705e43ac66fbb5a56747d00d40 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 16 May 2023 14:57:31 +0200 Subject: [PATCH 04/14] switch-root: always use MS_BIND to move api vfs over MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We previously would use MS_MOVE to move the old procfs, sysfs, /dev/ and /run to the new place in some places, and MS_BIND in others. The logic when to use MS_MOVE and when to use MS_BIND was pretty arbitrary so far: we'd use MS_MOVE during the initrd → host transition and MS_BIND when transitioning from host into the exitrd during shutdown. Traditionally, using MS_MOVE was preferable, because we didn't bother with unmounting the old mount hierarchy before the switch root, and thus using MS_MOVE did some clean-up as side-effect (because the old mounts went away this way). But since we nowadays properly umount all remaining mount points (since 268d1244e87a35ff8dff56c92ef375ebf69d462e) when transitioning it's pointless. Let's just use MS_BIND always. Let's tweak it though: let's use MS_BIND|MS_REC for the kernel API VFS, and MS_BIND without MS_REC for /run/. The latter reflects the fact that the submounts /run/ has usually are not so much about just accessing kernel APIs but about auxiliary user resources. Hence let's only move the main mount over for that. While we are at it, also set up the base filesystem *before* we move the mounts from the old to the new root, since the base filesystem setup logic creates various needed inodes for us, which we really should make use of instead of creating on our own. --- src/core/main.c | 2 -- src/shared/switch-root.c | 61 +++++++++++++++++++++++++--------------- src/shared/switch-root.h | 2 +- src/shutdown/shutdown.c | 6 +--- 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/core/main.c b/src/core/main.c index dc7e4a9767..28cfca25b3 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1838,10 +1838,8 @@ static int do_reexecute( } if (switch_root_dir) { - /* And switch root with MS_MOVE, because we remove the old directory afterwards and detach it. */ 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"); diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index b1cbcc5e91..2ea65eafaf 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -10,6 +10,7 @@ #include "base-filesystem.h" #include "chase.h" +#include "creds-util.h" #include "fd-util.h" #include "initrd-util.h" #include "log.h" @@ -27,15 +28,26 @@ 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/ */ bool destroy_old_root) { + struct { + const char *path; + unsigned long mount_flags; + } transfer_table[] = { + { "/dev", MS_BIND|MS_REC }, /* Recursive, because we want to save the original /dev/shm + /dev/pts and similar */ + { "/sys", MS_BIND|MS_REC }, /* Similar, we want to retain various API VFS, or the cgroupv1 /sys/fs/cgroup/ tree */ + { "/proc", MS_BIND|MS_REC }, /* Similar */ + { "/run", MS_BIND }, /* Stuff mounted below this we don't save, as it might have lost its relevance, i.e. credentials, removable media and such, we rather want that the new boot mounts this fresh */ + { SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND }, /* Credentials passed into the system should survive */ + { ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND }, /* Similar */ + { "/run/host", MS_BIND|MS_REC }, /* Host supplied hierarchy should also survive */ + }; + _cleanup_close_ int old_root_fd = -EBADF, new_root_fd = -EBADF; _cleanup_free_ char *resolved_old_root_after = NULL; int r, istmp; assert(new_root); - assert(IN_SET(mount_flags, MS_MOVE, MS_BIND)); /* Check if we shall remove the contents of the old root */ old_root_fd = open("/", O_DIRECTORY|O_CLOEXEC); @@ -83,32 +95,35 @@ int switch_root(const char *new_root, if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0) return log_error_errno(errno, "Failed to set \"/\" mount propagation to private: %m"); - FOREACH_STRING(path, "/sys", "/dev", "/run", "/proc") { - _cleanup_free_ char *chased = NULL; - - r = chase(path, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &chased, NULL); - if (r < 0) - return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, path); - if (r > 0) { - /* Already exists. Let's see if it is a mount point already. */ - r = path_is_mount_point(chased, NULL, 0); - if (r < 0) - return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", chased); - if (r > 0) /* If it is already mounted, then do nothing */ - continue; - } else - /* Doesn't exist yet? */ - (void) mkdir_p_label(chased, 0755); - - if (mount(path, chased, NULL, mount_flags, NULL) < 0) - return log_error_errno(errno, "Failed to mount %s to %s: %m", path, chased); - } - /* Do not fail if base_filesystem_create() fails. Not all switch roots are like base_filesystem_create() wants * them to look like. They might even boot, if they are RO and don't have the FS layout. Just ignore the error * and switch_root() nevertheless. */ (void) base_filesystem_create_fd(new_root_fd, new_root, UID_INVALID, GID_INVALID); + FOREACH_ARRAY(transfer, transfer_table, ELEMENTSOF(transfer_table)) { + _cleanup_free_ char *chased = NULL; + + if (access(transfer->path, F_OK) < 0) { + log_debug_errno(errno, "Path '%s' to move to target root directory, not found, ignoring: %m", transfer->path); + continue; + } + + r = chase(transfer->path, new_root, CHASE_PREFIX_ROOT, &chased, NULL); + if (r < 0) + return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, transfer->path); + + /* Let's see if it is a mount point already. */ + r = path_is_mount_point(chased, NULL, 0); + if (r < 0) + return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", chased); + if (r > 0) /* If it is already mounted, then do nothing */ + continue; + + r = mount_nofollow_verbose(LOG_ERR, transfer->path, chased, NULL, transfer->mount_flags, NULL); + if (r < 0) + return r; + } + if (fchdir(new_root_fd) < 0) return log_error_errno(errno, "Failed to change directory to %s: %m", new_root); diff --git a/src/shared/switch-root.h b/src/shared/switch-root.h index cba84e9b01..9882d2ef91 100644 --- a/src/shared/switch-root.h +++ b/src/shared/switch-root.h @@ -3,4 +3,4 @@ #include -int switch_root(const char *new_root, const char *old_root_after, unsigned long mount_flags, bool destroy_old_root); +int switch_root(const char *new_root, const char *old_root_after, bool destroy_old_root); diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index dc713e529f..bd6ded5529 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -165,14 +165,10 @@ static int switch_root_initramfs(void) { if (mount(NULL, "/run/initramfs", NULL, MS_PRIVATE, NULL) < 0) return log_error_errno(errno, "Failed to make /run/initramfs private mount: %m"); - /* switch_root with MS_BIND, because there might still be processes lurking around, which have open file descriptors. - * /run/initramfs/shutdown will take care of these. - * Also do not detach the old root, because /run/initramfs/shutdown needs to access it. - */ + /* Do not detach the old root, because /run/initramfs/shutdown needs to access it. */ return switch_root( /* new_root= */ "/run/initramfs", /* old_root_after= */ "/oldroot", - MS_BIND, /* destroy_old_root= */ false); } From 2932161bf6d9a8c127da00c9032e7e449222c9ea Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 19 May 2023 15:48:12 +0200 Subject: [PATCH 05/14] switch-root: introduce SwitchRootFlags flags parameter to switch_root() Let's replace the current boolean param with a proper flags param. With a single flag this doesn't appear to make much sense, though it does already make things more readable I think. However, once we add a second flag, it starts to make more sense. Also, while we are at it, condition the "istmp" determinaton with this flag too, since we only need it when the flag is set. --- src/core/main.c | 2 +- src/shared/switch-root.c | 17 ++++++++++------- src/shared/switch-root.h | 6 +++++- src/shutdown/shutdown.c | 2 +- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/core/main.c b/src/core/main.c index 28cfca25b3..f067b13fff 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1840,7 +1840,7 @@ static int do_reexecute( if (switch_root_dir) { r = switch_root(/* new_root= */ switch_root_dir, /* old_root_after= */ NULL, - /* destroy_old_root= */ objective == MANAGER_SWITCH_ROOT); + /* flags= */ objective == MANAGER_SWITCH_ROOT ? SWITCH_ROOT_DESTROY_OLD_ROOT : 0); if (r < 0) log_error_errno(r, "Failed to switch root, trying to continue: %m"); } diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index 2ea65eafaf..333118f75f 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -28,7 +28,7 @@ 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 */ - bool destroy_old_root) { + SwitchRootFlags flags) { struct { const char *path; @@ -66,11 +66,14 @@ int switch_root(const char *new_root, return 0; } - istmp = fd_is_temporary_fs(old_root_fd); - if (istmp < 0) - return log_error_errno(istmp, "Failed to stat root directory: %m"); - if (istmp > 0) - log_debug("Root directory is on tmpfs, will do cleanup later."); + if (FLAGS_SET(flags, SWITCH_ROOT_DESTROY_OLD_ROOT)) { + istmp = fd_is_temporary_fs(old_root_fd); + if (istmp < 0) + return log_error_errno(istmp, "Failed to stat root directory: %m"); + if (istmp > 0) + log_debug("Root directory is on tmpfs, will do cleanup later."); + } else + istmp = -1; /* don't know */ if (old_root_after) { /* Determine where we shall place the old root after the transition */ @@ -160,7 +163,7 @@ int switch_root(const char *new_root, return log_error_errno(errno, "Failed to change directory: %m"); } - if (istmp && destroy_old_root) { + if (istmp > 0) { struct stat rb; if (fstat(old_root_fd, &rb) < 0) diff --git a/src/shared/switch-root.h b/src/shared/switch-root.h index 9882d2ef91..059b4603ea 100644 --- a/src/shared/switch-root.h +++ b/src/shared/switch-root.h @@ -3,4 +3,8 @@ #include -int switch_root(const char *new_root, const char *old_root_after, bool destroy_old_root); +typedef enum SwitchRootFlags { + SWITCH_ROOT_DESTROY_OLD_ROOT = 1 << 0, /* rm -rf old root when switching – under the condition that it is backed by non-persistent tmpfs/ramfs/… */ +} SwitchRootFlags; + +int switch_root(const char *new_root, const char *old_root_after, SwitchRootFlags flags); diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index bd6ded5529..be4adb79d5 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -169,7 +169,7 @@ static int switch_root_initramfs(void) { return switch_root( /* new_root= */ "/run/initramfs", /* old_root_after= */ "/oldroot", - /* destroy_old_root= */ false); + /* flags= */ 0); } /* Read the following fields from /proc/meminfo: From 1795252caa681b6b16e575ae2e5bd24a48f4e376 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 19 May 2023 15:52:43 +0200 Subject: [PATCH 06/14] switch-root: disable sync() again when we switch root during shutdown Our shutdown binary that takes over as PID 1 when shutting down puts great efforts into a sync() that comes with a time-out once sync'ing process stops. If we'd add another dumb sync() here, we kinda defeat all it is good for. Hence, let's keep the sync() in for most codepats, but let's disable it for the final shutdown logic when we transition back into the exitrd. After all we sync()ed more than enough here, no need to sync() even more. --- src/shared/switch-root.c | 3 ++- src/shared/switch-root.h | 1 + src/shutdown/shutdown.c | 8 ++++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index 333118f75f..17078c0490 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -89,7 +89,8 @@ int switch_root(const char *new_root, * all while making them invisible/inaccessible in the file system tree for later code. That makes * sync'ing them then difficult. Let's hence issue a manual sync() here, so that we at least can * guarantee all file systems are an a good state before entering this state. */ - sync(); + if (!FLAGS_SET(flags, SWITCH_ROOT_DONT_SYNC)) + sync(); /* Work-around for kernel design: the kernel refuses MS_MOVE if any file systems are mounted * MS_SHARED. Hence remount them MS_PRIVATE here as a work-around. diff --git a/src/shared/switch-root.h b/src/shared/switch-root.h index 059b4603ea..79e47c67f4 100644 --- a/src/shared/switch-root.h +++ b/src/shared/switch-root.h @@ -5,6 +5,7 @@ typedef enum SwitchRootFlags { SWITCH_ROOT_DESTROY_OLD_ROOT = 1 << 0, /* rm -rf old root when switching – under the condition that it is backed by non-persistent tmpfs/ramfs/… */ + SWITCH_ROOT_DONT_SYNC = 1 << 1, /* don't call sync() immediately before switching root */ } SwitchRootFlags; int switch_root(const char *new_root, const char *old_root_after, SwitchRootFlags flags); diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index be4adb79d5..cdf828616e 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -165,11 +165,15 @@ static int switch_root_initramfs(void) { if (mount(NULL, "/run/initramfs", NULL, MS_PRIVATE, NULL) < 0) return log_error_errno(errno, "Failed to make /run/initramfs private mount: %m"); - /* Do not detach the old root, because /run/initramfs/shutdown needs to access it. */ + /* Do not detach the old root, because /run/initramfs/shutdown needs to access it. + * + * Disable sync() during switch-root, we after all sync'ed here plenty, and a dumb sync (as opposed + * to the "smart" sync() we did here that looks at progress parameters) would defeat much of our + * efforts here. */ return switch_root( /* new_root= */ "/run/initramfs", /* old_root_after= */ "/oldroot", - /* flags= */ 0); + /* flags= */ SWITCH_ROOT_DONT_SYNC); } /* Read the following fields from /proc/meminfo: From f9ad896ee2a7c14b6c51bcc26547b82761ec0ab3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 2 Jun 2023 18:23:44 +0200 Subject: [PATCH 07/14] mount-util: add fd_make_mount_point() helper --- src/shared/mount-util.c | 18 ++++++++++++++ src/shared/mount-util.h | 1 + src/test/test-mount-util.c | 49 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 81b681afa7..366f59ee82 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -1099,6 +1099,24 @@ int make_mount_point(const char *path) { return 1; } +int fd_make_mount_point(int fd) { + int r; + + assert(fd >= 0); + + r = fd_is_mount_point(fd, NULL, 0); + if (r < 0) + return log_debug_errno(r, "Failed to determine whether file descriptor is a mount point: %m"); + if (r > 0) + return 0; + + r = mount_follow_verbose(LOG_DEBUG, FORMAT_PROC_FD_PATH(fd), FORMAT_PROC_FD_PATH(fd), NULL, MS_BIND|MS_REC, NULL); + if (r < 0) + return r; + + return 1; +} + int make_userns(uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping) { _cleanup_close_ int userns_fd = -EBADF; _cleanup_free_ char *line = NULL; diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h index b33e739b42..7214cf1e52 100644 --- a/src/shared/mount-util.h +++ b/src/shared/mount-util.h @@ -100,6 +100,7 @@ int bind_mount_in_namespace(pid_t target, const char *propagate_path, const char int mount_image_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory, const MountOptions *options, const ImagePolicy *image_policy); int make_mount_point(const char *path); +int fd_make_mount_point(int fd); typedef enum RemountIdmapping { REMOUNT_IDMAPPING_NONE, diff --git a/src/test/test-mount-util.c b/src/test/test-mount-util.c index 2529b2f0ee..021eff1548 100644 --- a/src/test/test-mount-util.c +++ b/src/test/test-mount-util.c @@ -519,6 +519,55 @@ TEST(umount_recursive) { } } +TEST(fd_make_mount_point) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_free_ char *s = NULL; + int r; + + if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) { + (void) log_tests_skipped("not running privileged"); + return; + } + + assert_se(mkdtemp_malloc(NULL, &t) >= 0); + + assert_se(asprintf(&s, "%s/somerandomname%" PRIu64, t, random_u64()) >= 0); + assert_se(s); + assert_se(mkdir(s, 0700) >= 0); + + r = safe_fork("(make_mount-point)", + FORK_RESET_SIGNALS | + FORK_CLOSE_ALL_FDS | + FORK_DEATHSIG | + FORK_WAIT | + FORK_REOPEN_LOG | + FORK_LOG | + FORK_NEW_MOUNTNS | + FORK_MOUNTNS_SLAVE, + NULL); + assert_se(r >= 0); + + if (r == 0) { + _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; + + fd = open(s, O_PATH|O_CLOEXEC); + assert_se(fd >= 0); + + assert_se(fd_is_mount_point(fd, NULL, AT_SYMLINK_FOLLOW) == 0); + + assert_se(fd_make_mount_point(fd) > 0); + + /* Reopen the inode so that we end up on the new mount */ + fd2 = open(s, O_PATH|O_CLOEXEC); + + assert_se(fd_is_mount_point(fd2, NULL, AT_SYMLINK_FOLLOW) > 0); + + assert_se(fd_make_mount_point(fd2) == 0); + + _exit(EXIT_SUCCESS); + } +} + static int intro(void) { /* Create a dummy network interface for testing remount_sysfs(). */ (void) system("ip link add dummy-test-mnt type dummy"); From f717d7a40a696b351415976f22a4f498c401de41 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 2 Jun 2023 18:24:27 +0200 Subject: [PATCH 08/14] switch-root: automatically make target switch root dir a mount point MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let's make sure implicitly that the target directory is a mount point, instead of doing so manually beforehand. This allows us to drop this step from the transition into the /run/initramfs/ dir at shutdown. During the initrd→host transition the switch root operations so far where towards pre-existing mount points, but there are cetrainly usecases where it might make sense to siwtch into arbitrary subdirectories, too. --- src/shared/switch-root.c | 5 +++++ src/shutdown/shutdown.c | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index 17078c0490..070d5bd908 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -66,6 +66,11 @@ int switch_root(const char *new_root, return 0; } + /* Make the new root directory a mount point if it isn't */ + r = fd_make_mount_point(new_root_fd); + if (r < 0) + return log_error_errno(r, "Failed to make new root directory a mount point: %m"); + if (FLAGS_SET(flags, SWITCH_ROOT_DESTROY_OLD_ROOT)) { istmp = fd_is_temporary_fs(old_root_fd); if (istmp < 0) diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index cdf828616e..f8a9b0243a 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -159,12 +159,6 @@ static int parse_argv(int argc, char *argv[]) { } static int switch_root_initramfs(void) { - if (mount("/run/initramfs", "/run/initramfs", NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Failed to mount bind /run/initramfs on /run/initramfs: %m"); - - if (mount(NULL, "/run/initramfs", NULL, MS_PRIVATE, NULL) < 0) - return log_error_errno(errno, "Failed to make /run/initramfs private mount: %m"); - /* Do not detach the old root, because /run/initramfs/shutdown needs to access it. * * Disable sync() during switch-root, we after all sync'ed here plenty, and a dumb sync (as opposed From d58506562407a6b224326cae52cdb369f8bf756c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 2 Jun 2023 17:50:50 +0200 Subject: [PATCH 09/14] mount-setup: exclude /run/nextroot/ from relabelling Just like /run/initramfs/ the data in /run/nextroot/ should be a self-contained OS tree, and not require labelling, hence don't. --- src/shared/mount-setup.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c index fd14cd8598..ede4e8483a 100644 --- a/src/shared/mount-setup.c +++ b/src/shared/mount-setup.c @@ -376,8 +376,9 @@ static int relabel_cb( return RECURSE_DIR_CONTINUE; case RECURSE_DIR_ENTER: - /* /run/initramfs is static data and big, no need to dynamically relabel its contents at boot... */ - if (path_equal(path, "/run/initramfs")) + /* /run/initramfs/ + /run/nextroot/ are static data and big, no need to dynamically relabel + * its contents at boot... */ + if (PATH_STARTSWITH_SET(path, "/run/initramfs", "/run/nextroot")) return RECURSE_DIR_SKIP_ENTRY; _fallthrough_; From ba1af88c3c5755eec5ba5a37077575893bd526a8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 2 Jun 2023 17:51:37 +0200 Subject: [PATCH 10/14] fstab-util: consider /run/nextroot/ among extrinsic mounts This way we'll not add deps for the mount point that unmount it during shutdown. This is similar as for /run/initramfs/ which we want to transition into during shutdown. This way we don't have to add "-o x-initrd.mount" to all bind mounts for /run/nextroot anymore to make it survive the reboot, it will be implied. --- src/shared/fstab-util.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shared/fstab-util.c b/src/shared/fstab-util.c index d22cff2d82..b0e274afcc 100644 --- a/src/shared/fstab-util.c +++ b/src/shared/fstab-util.c @@ -47,6 +47,7 @@ bool fstab_is_extrinsic(const char *mount, const char *opts) { if (PATH_STARTSWITH_SET(mount, "/run/initramfs", /* This should stay around from before we boot until after we shutdown */ + "/run/nextroot", /* Similar (though might be updated from the host) */ "/proc", /* All of this is API VFS */ "/sys", /* … dito … */ "/dev")) /* … dito … */ From 4de665812aa83fffcdf7395eccc7e0b1d74ef4c2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 3 May 2023 10:33:01 +0200 Subject: [PATCH 11/14] man: document the soft reboot operation --- man/org.freedesktop.login1.xml | 31 +++--- man/org.freedesktop.systemd1.xml | 21 ++-- man/rules/meson.build | 1 + man/systemctl.xml | 18 ++++ man/systemd-poweroff.service.xml | 5 + man/systemd-soft-reboot.service.xml | 158 ++++++++++++++++++++++++++++ man/systemd.mount.xml | 8 +- man/systemd.special.xml | 56 +++++++--- man/systemd.unit.xml | 47 +++++---- man/systemd.xml | 15 +++ 10 files changed, 300 insertions(+), 60 deletions(-) create mode 100644 man/systemd-soft-reboot.service.xml diff --git a/man/org.freedesktop.login1.xml b/man/org.freedesktop.login1.xml index 034fd1078f..867662e057 100644 --- a/man/org.freedesktop.login1.xml +++ b/man/org.freedesktop.login1.xml @@ -576,16 +576,15 @@ node /org/freedesktop/login1 { PowerOff(), Reboot(), Halt(), Suspend(), and Hibernate() result in the system being powered - off, rebooted, halted (shut down without turning off power), suspended (the system state is - saved to RAM and the CPU is turned off), or hibernated (the system state is saved to disk and - the machine is powered down). HybridSleep() results in the system entering a - hybrid-sleep mode, i.e. the system is both hibernated and suspended. - SuspendThenHibernate() results in the system being suspended, then later woken - using an RTC timer and hibernated. The only argument is the polkit interactivity boolean - interactive (see below). The main purpose of these calls is that they enforce - polkit policy and hence allow powering off/rebooting/suspending/hibernating even by unprivileged - users. They also enforce inhibition locks for non-privileged users. UIs should expose these calls - as the primary mechanism to poweroff/reboot/suspend/hibernate the machine. Methods + off, rebooted, halted (shut down without turning off power), suspended (the system state is saved to + RAM and the CPU is turned off), or hibernated (the system state is saved to disk and the machine is + powered down). HybridSleep() results in the system entering a hybrid-sleep mode, + i.e. the system is both hibernated and suspended. SuspendThenHibernate() results + in the system being suspended, then later woken using an RTC timer and hibernated. The only argument is + the polkit interactivity boolean interactive (see below). The main purpose of these + calls is that they enforce polkit policy and hence allow powering off/rebooting/suspending/hibernating + even by unprivileged users. They also enforce inhibition locks for non-privileged users. UIs should + expose these calls as the primary mechanism to poweroff/reboot/suspend/hibernate the machine. Methods PowerOffWithFlags(), RebootWithFlags(), HaltWithFlags(), SuspendWithFlags(), HibernateWithFlags(), HybridSleepWithFlags() and @@ -594,12 +593,14 @@ node /org/freedesktop/login1 { #define SD_LOGIND_ROOT_CHECK_INHIBITORS (UINT64_C(1) << 0) #define SD_LOGIND_KEXEC_REBOOT (UINT64_C(1) << 1) +#define SD_LOGIND_SOFT_REBOOT (UINT64_C(1) << 2) - When the flags is 0 then these methods behave just like the versions - without flags. When SD_LOGIND_ROOT_CHECK_INHIBITORS (0x01) is set, active - inhibitors are honoured for privileged users too. When SD_LOGIND_KEXEC_REBOOT - (0x02) is set, then RebootWithFlags() perform kexec reboot if kexec - kernel is loaded. + When the flags is 0 then these methods behave just like the versions without + flags. When SD_LOGIND_ROOT_CHECK_INHIBITORS (0x01) is set, active inhibitors are + honoured for privileged users too. When SD_LOGIND_KEXEC_REBOOT (0x02) is set, then + RebootWithFlags() performs a kexec reboot if kexec kernel is loaded. When + SD_LOGIND_SOFT_REBOOT (0x04) is set, then RebootWithFlags() + performs a userspace reboot only. SetRebootParameter() sets a parameter for a subsequent reboot operation. See the description of reboot in diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 0835481f37..a08108dd88 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -183,6 +183,8 @@ node /org/freedesktop/systemd1 { @org.freedesktop.systemd1.Privileged("true") Reboot(); @org.freedesktop.systemd1.Privileged("true") + SoftReboot(in s new_root); + @org.freedesktop.systemd1.Privileged("true") PowerOff(); @org.freedesktop.systemd1.Privileged("true") Halt(); @@ -912,6 +914,8 @@ node /org/freedesktop/systemd1 { + + @@ -1415,15 +1419,18 @@ node /org/freedesktop/systemd1 { Exit() may be invoked to ask the manager to exit. This is not available for the system manager and is useful only for user session managers. - Reboot(), PowerOff(), Halt(), or - KExec() may be used to ask for immediate reboot, powering down, halt or kexec - based reboot of the system. Note that this does not shut down any services and immediately transitions - into the reboot process. These functions are normally only called as the last step of shutdown and should - not be called directly. To shut down the machine, it is generally a better idea to invoke - Reboot() or PowerOff() on the + Reboot(), PowerOff(), Halt(), + KExec() and SoftReboot() may be used to ask for immediate + reboot, powering down, halt, kexec based reboot, or soft reboot of the system. Note that this does not + shut down any services and immediately transitions into the later shutdown operation. These functions + are normally only called as the last step of shutdown and should not be called directly. To shut down + the machine, it is generally a better idea to invoke Reboot(), + RebootWithFlags() or PowerOff() on the systemd-logind manager object; see org.freedesktop.login15 - for more information. + for more information. SoftReboot() accepts an argument indicating the path for the + root file system to activate for the next boot cycle. If an empty string is specified the + /run/nextroot/ path is used if it exists. SwitchRoot() may be used to transition to a new root directory. This is intended to be used in the initrd, and also to transition from the host system into a shutdown initrd. diff --git a/man/rules/meson.build b/man/rules/meson.build index 4658ef99f0..166d8a5e22 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1036,6 +1036,7 @@ manpages = [ ['systemd-sleep.conf', '5', ['sleep.conf.d'], ''], ['systemd-socket-activate', '1', [], ''], ['systemd-socket-proxyd', '8', [], ''], + ['systemd-soft-reboot.service', '8', [], ''], ['systemd-stdio-bridge', '1', [], ''], ['systemd-stub', '7', diff --git a/man/systemctl.xml b/man/systemctl.xml index e54dd22aae..29e5fc65c2 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -1559,6 +1559,24 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err + + soft-reboot + + + Shut down and reboot userspace. This is equivalent to systemctl start + soft-reboot.target --job-mode=replace-irreversibly --no-block. This command is + asynchronous; it will return after the reboot operation is enqueued, without waiting for it to + complete. + + This command honors and in a similar way + as halt. + + This operation only reboots userspace, leaving the kernel running. See + systemd-soft-reboot.service8 + for details. + + + exit EXIT_CODE diff --git a/man/systemd-poweroff.service.xml b/man/systemd-poweroff.service.xml index 98c20471da..b430170bec 100644 --- a/man/systemd-poweroff.service.xml +++ b/man/systemd-poweroff.service.xml @@ -68,6 +68,10 @@ Note that systemd-poweroff.service (and the related units) should never be executed directly. Instead, trigger system shutdown with a command such as systemctl poweroff. + + Another form of shutdown is provided by the + systemd-soft-reboot.service8 + functionality. It reboots only the OS userspace, leaving the kernel, firmware, and hardware as it is. @@ -78,6 +82,7 @@ systemd.special7, reboot2, systemd-suspend.service8, + systemd-soft-reboot.service8, bootup7 diff --git a/man/systemd-soft-reboot.service.xml b/man/systemd-soft-reboot.service.xml new file mode 100644 index 0000000000..0e95cba3be --- /dev/null +++ b/man/systemd-soft-reboot.service.xml @@ -0,0 +1,158 @@ + + + + + + + + systemd-soft-reboot.service + systemd + + + + systemd-soft-reboot.service + 8 + + + + systemd-soft-reboot.service + Userspace reboot operation + + + + systemd-soft-reboot.service + + + + Description + + systemd-soft-reboot.service is a system service that is pulled in by + soft-reboot.target and is responsible for performing a userspace-only reboot + operation. When invoked, it will send the SIGTERM signal to any processes left + running (but does not follow up with SIGKILL, and does not wait for the processes to + exit). If the /run/nextroot/ directory exists (which may be a regular directory, a + directory mount point or a symlink to either) then it will switch the file system root to it. It then + reexecutes the service manager off the (possibly now new) root file system, which will enqueue a new boot + transaction as in a normal reboot. + + Such a userspace-only reboot operation permits updating or resetting the entirety of userspace with + minimal downtime, as the reboot operation does not transition through: + + + The second phase of regular shutdown, as implemented by + systemd-shutdown8. + + The third phase of regular shutdown, i.e. the return to the initrd + context + + The hardware reboot operation + + The firmware initialization + + The boot loader initialization + + The kernel initialization + + The initrd initialization + + + However this form of reboot comes with drawbacks as well: + + + The OS update remains incomplete, as the kernel is not reset and continues + running. + + Kernel settings (such as /proc/sys/ settings, a.k.a. "sysctl", or + /sys/ settings) are not reset. + + + These limitations may be addressed by various means, which are outside of the scope of this + documentation, such as kernel live-patching and sufficiently comprehensive + /etc/sysctl.d/ files. + + + + Resource Pass-Through + + Various runtime OS resources can passed from a system runtime to the next, through the userspace + reboot operation. Specificially: + + + File descriptors placed in the file descriptor store of services that remain active + until the very end are passed to the next boot, where they are placed in the file descriptor store of + the same unit. For this to work, units must declare DefaultDependencies=no (and + avoid a manual Conflicts=shutdown.target or similar) to ensure they are not + terminated as usual during the system shutdown operation. Alternatively, use + FileDescriptorStorePreserve= to allow the file descriptor store to remain pinned + even when the unit is down. See + systemd.service5 for + details about the file descriptor store. + + Similar to this, file descriptors associated with .socket units + remain open (and connectible) if the units are not stopped during the transition. (Achieved by + DefaultDependencies=no.) + + The /run/ file system remains mounted and populated and may be + used to pass state information between such userspace reboot cycles. + + Service processes may continue to run over the transition, if they are placed in + services that remain active until the very end of shutdown (which again is achieved via + DefaultDependencies=no). They must also be set up to avoid being killed by the + aforementioned SIGTERM spree (as per systemd and Storage Daemons for the Root File + System). + + File system mounts may remain mounted during the transition, and complex storage + attached, if configured to remain until the very end of the shutdown process. (Also achieved via + DefaultDependencies=no, and by avoiding + Conflicts=umount.target) + + + Even though passing resources from one soft reboot cycle to the next is possible this way, we + strongly suggest to use this functionality sparingly only, as it creates a more fragile system as + resources from different versions of the OS and applications might be mixed with unforeseen + consequences. In particular it's recommended to avoid allowing processes to survive + the soft reboot operation, as this means code updates will necessarily be incomplete, and processes + typically pin various other resources (such as the file system they are backed by), thus increasing + memory usage (as two versions of the OS/application/file system might be kept in memory). Leaving + processes running during a soft-reboot operation requires disconnecting the service comprehensively from + the rest of the OS, i.e. minimizing IPC and reducing sharing of resources with the rest of the OS. A + possible mechanism to achieve this is the concept of Portable Services. + + If units shall be left running until the very end of shutdown during a soft reboot operation, but + shall be terminated regularly during other forms of shutdown, it's recommended to set + DefaultDependencies=no and then place + Conflicts=/Before= onto reboot.target, + kexec.target, poweroff.target and + halt.target (but not onto + soft-reboot.target). + + + + Notes + + Note that because + systemd-shutdown8 is + not executed, the executables in /usr/lib/systemd/system-shutdown/ are not executed + either. + + Note that systemd-soft-reboot.service (and related units) should never be + executed directly. Instead, trigger system shutdown with a command such as systemctl + soft-reboot. + + + + See Also + + systemd1, + systemctl1, + systemd.special7, + systemd-poweroff.service8, + systemd-suspend.service8, + bootup7 + + + + diff --git a/man/systemd.mount.xml b/man/systemd.mount.xml index 550f006601..0d14d702a8 100644 --- a/man/systemd.mount.xml +++ b/man/systemd.mount.xml @@ -437,8 +437,12 @@ An additional filesystem to be mounted in the initrd. See initrd-fs.target description in - systemd.special7. - + systemd.special7. This + is both an indicator to the initrd to mount this partition early and an indicator to the host to + leave the partition mounted until final shutdown. Or in other words, if this flag is set it is + assumed the mount shall be active during the entire regular runtime of the system, i.e. established + before the initrd transitions into the host all the way until the host transitions to the final + shutdown phase. diff --git a/man/systemd.special.xml b/man/systemd.special.xml index 1620895511..c90e7156ed 100644 --- a/man/systemd.special.xml +++ b/man/systemd.special.xml @@ -81,6 +81,7 @@ slices.target, smartcard.target, sockets.target, + soft-reboot.target, sound.target, suspend.target, swap.target, @@ -442,15 +443,18 @@ kexec.target - A special target unit for shutting down and rebooting - the system via kexec. + A special target unit for shutting down and rebooting the system via kexec. - Applications wanting to reboot the system should not start this unit - directly, but should instead execute systemctl kexec - (possibly with the option) or call - systemd1's - org.freedesktop.systemd1.Manager.KExec D-Bus method + Applications wanting to reboot the system should not start this unit directly, but should + instead execute systemctl kexec (possibly with the + option) or call + systemd-logind8's + org.freedesktop.login1.Manager.RebootWithFlags() D-Bus method directly. + + See + systemd-kexec.service8 + for further details of the operation this target pulls in. @@ -559,18 +563,20 @@ reboot.target - A special target unit for shutting down and rebooting - the system. + A special target unit for shutting down and rebooting the system. - Applications wanting to reboot the system should not start this unit - directly, but should instead execute systemctl reboot - (possibly with the option) or call + Applications wanting to reboot the system should not start this unit directly, but should + instead execute systemctl reboot (possibly with the + option) or call systemd-logind8's - org.freedesktop.login1.Manager.Reboot D-Bus method - directly. + org.freedesktop.login1.Manager.Reboot() D-Bus method directly. - runlevel6.target is an alias for - this target unit, for compatibility with SysV. + See + systemd-reboot.service8 + for further details of the operation this target pulls in. + + runlevel6.target is an alias for this target unit, for compatibility + with SysV. @@ -703,6 +709,24 @@ section. + + soft-reboot.target + + A special target unit for shutting down and rebooting the userspace of the system (leaving + the kernel running). + + Applications wanting to reboot the system should not start this unit directly, but should + instead execute systemctl soft-reboot (possibly with the + option) or call + systemd-logind8's + org.freedesktop.login1.Manager.RebootWithFlags() D-Bus method + directly. + + See + systemd-soft-reboot.service8 + for further details of the operation this target pulls in. + + suspend.target diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index fcd1f914a8..77c3cd24f8 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -999,27 +999,34 @@ FailureAction= SuccessAction= - Configure the action to take when the unit stops and enters a failed state or inactive state. - Takes one of , , , - , , , - , , and . In system mode, - all options are allowed. In user mode, only , , and - are allowed. Both options default to . + Configure the action to take when the unit stops and enters a failed state or + inactive state. Takes one of , , + , , , + , , , + , and . In + system mode, all options are allowed. In user mode, only , + , , and + are allowed. Both options default to . - If is set, no action will be triggered. causes a reboot - following the normal shutdown procedure (i.e. equivalent to systemctl reboot). - causes a forced reboot which will terminate all processes forcibly but should - cause no dirty file systems on reboot (i.e. equivalent to systemctl reboot -f) and - causes immediate execution of the - reboot2 system call, which - might result in data loss (i.e. equivalent to systemctl reboot -ff). Similarly, - , , have the effect - of powering down the system with similar semantics. causes the manager to exit following - the normal shutdown procedure, and causes it terminate without shutting down - services. When or is used by default the exit status of the - main process of the unit (if this applies) is returned from the service manager. However, this may be overridden - with FailureActionExitStatus=/SuccessActionExitStatus=, see - below. + If is set, no action will be triggered. causes a + reboot following the normal shutdown procedure (i.e. equivalent to systemctl + reboot). causes a forced reboot which will terminate all + processes forcibly but should cause no dirty file systems on reboot (i.e. equivalent to + systemctl reboot -f) and causes immediate + execution of the + reboot2 system + call, which might result in data loss (i.e. equivalent to systemctl reboot + -ff). Similarly, , , + have the effect of powering down the system with similar + semantics. causes the manager to exit following the normal shutdown procedure, + and causes it terminate without shutting down services. When + or is used by default the exit status of the main + process of the unit (if this applies) is returned from the service manager. However, this may be + overridden with + FailureActionExitStatus=/SuccessActionExitStatus=, see + below. will trigger a userspace reboot + operation. does that too, but does not go through the shutdown + transaction beforehand. diff --git a/man/systemd.xml b/man/systemd.xml index ca9e4e9988..95dc1fef83 100644 --- a/man/systemd.xml +++ b/man/systemd.xml @@ -519,6 +519,15 @@ + + SIGRTMIN+7 + + Reboots userspace, starts the soft-reboot.target unit. This is + mostly equivalent to systemctl start soft-reboot.target + --job-mode=replace-irreversibly. + + + SIGRTMIN+13 @@ -543,6 +552,12 @@ Immediately reboots the machine with kexec. + + SIGRTMIN+17 + + Immediately reboots the userspace. + + SIGRTMIN+20 From 8af66251a13a6b4f585624acda50b0d564cd6ed0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 3 May 2023 09:37:55 +0200 Subject: [PATCH 12/14] test: disable SoftReboot() in dfuzzer test for now As requested: https://github.com/systemd/systemd/pull/27435#issuecomment-1527810336 --- test/units/testsuite-21.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/test/units/testsuite-21.sh b/test/units/testsuite-21.sh index 36f647ca5f..87a18fe540 100755 --- a/test/units/testsuite-21.sh +++ b/test/units/testsuite-21.sh @@ -28,6 +28,7 @@ systemctl log-level info # FIXME: systemd-run doesn't play well with daemon-reexec # See: https://github.com/systemd/systemd/issues/27204 sed -i '/\[org.freedesktop.systemd1\]/aorg.freedesktop.systemd1.Manager:Reexecute FIXME' /etc/dfuzzer.conf +sed -i '/\[org.freedesktop.systemd1\]/aorg.freedesktop.systemd1.Manager:SoftReboot destructive' /etc/dfuzzer.conf # TODO # * check for possibly newly introduced buses? From 093d5456588004f5b710ae03c74c695e9f2b527d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 3 May 2023 15:05:12 +0200 Subject: [PATCH 13/14] test: add integration test for soft reboots incl. fdstore passing --- test/TEST-82-SOFTREBOOT/Makefile | 1 + test/TEST-82-SOFTREBOOT/test.sh | 21 +++++ test/units/testsuite-82.service | 11 +++ test/units/testsuite-82.sh | 147 +++++++++++++++++++++++++++++++ 4 files changed, 180 insertions(+) create mode 120000 test/TEST-82-SOFTREBOOT/Makefile create mode 100755 test/TEST-82-SOFTREBOOT/test.sh create mode 100644 test/units/testsuite-82.service create mode 100755 test/units/testsuite-82.sh diff --git a/test/TEST-82-SOFTREBOOT/Makefile b/test/TEST-82-SOFTREBOOT/Makefile new file mode 120000 index 0000000000..e9f93b1104 --- /dev/null +++ b/test/TEST-82-SOFTREBOOT/Makefile @@ -0,0 +1 @@ +../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-82-SOFTREBOOT/test.sh b/test/TEST-82-SOFTREBOOT/test.sh new file mode 100755 index 0000000000..9d88c94fb7 --- /dev/null +++ b/test/TEST-82-SOFTREBOOT/test.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -e + +TEST_DESCRIPTION="Test Soft-Rebooting" + +# shellcheck source=test/test-functions +. "$TEST_BASE_DIR/test-functions" + +test_append_files() { + local workspace="${1:?}" + # prevent shutdown in test suite, the expect script does that manually. + mkdir -p "${workspace:?}/etc/systemd/system/end.service.d" + cat >"$workspace/etc/systemd/system/end.service.d/99-override.conf" <"$T" + systemd-notify --fd=3 --pid=parent 3<"$T" + rm "$T" + + # Check that the surviving service is still around + test "$(systemctl show -P ActiveState testsuite-82-survive.service)" = "active" + test "$(systemctl show -P ActiveState testsuite-82-nosurvive.service)" != "active" + + # Test that we really are in the new overlayfs root fs + read -r x "$T" + systemd-notify --fd=3 --pid=parent 3<"$T" + rm "$T" + + # Check that the surviving service is still around + test "$(systemctl show -P ActiveState testsuite-82-survive.service)" = "active" + test "$(systemctl show -P ActiveState testsuite-82-nosurvive.service)" != "active" + + # This time we test the /run/nextroot/ root switching logic. (We synthesize a new rootfs from the old via overlayfs) + mkdir -p /run/nextroot /tmp/nextroot-lower /original-root + mount -t tmpfs tmpfs /tmp/nextroot-lower + echo miep >/tmp/nextroot-lower/lower + mount -t overlay nextroot /run/nextroot -o lowerdir=/:/tmp/nextroot-lower,ro + + # Bind our current root into the target so that we later can return to it + mount --bind / /run/nextroot/original-root + + # Now issue the soft reboot. We should be right back soon. + touch /run/testsuite82.touch2 + systemctl --no-block soft-reboot + + # Now block until the soft-boot killing spree kills us + exec sleep infinity +else + # This is the first boot + systemd-notify --status="First Boot" + + # Let's upload an fd to the fdstore, so that we can verify fdstore passing works correcly + T="/dev/shm/fdstore.$RANDOM" + echo "wuffwuff" >"$T" + systemd-notify --fd=3 --pid=parent 3<"$T" + rm "$T" + + # Create a script that can survive the soft reboot by ignoring SIGTERM (we + # do this instead of the argv[0][0] = '@' thing because that's so hard to + # do from a shell + T="/dev/shm/survive-$RANDOM.sh" + cat >$T </testok + +systemctl --no-block poweroff + +exit 0 From 5596fd8ff7b0e85fa9bb95a1797e2397a1af78ab Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 2 May 2023 12:39:59 +0200 Subject: [PATCH 14/14] update TODO --- TODO | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/TODO b/TODO index 79643583dd..4c32a4a4ca 100644 --- a/TODO +++ b/TODO @@ -153,6 +153,10 @@ Features: dont), of sd-stub and data supplied by user. Then measure sbat too in sd-stub, explicitly. +* figure out what to do about credentials sealed to PCRs in kexec + soft-reboot + scenarios. Maybe insist sealing is done additionally against some keypair in + the TPM to which access is updated on each boot, for the next, or so? + * open up creds for uses in generators, and document clearly that encrypted creds are only supported if strictly tpm bound, but not when using the host secret (as that is only avilable if /var/ is around. @@ -162,6 +166,11 @@ Features: idea, and specifically works around the fact the autofs ignores busy by mount namespaces) +* refuse using the switch-root operation without /etc/initrd-release. Now + that we have a concept of userspace reboot, we can clearly say: switch-root + is for transitioning from initrd to host (or initrd to next initrd), while + userspace reboot is for switching host to next version of the host. + * mount most file systems with a restrictive uidmap. e.g. mount /usr/ with a uidmap that blocks out anything outside 0…1000 (i.e. system users) and similar.