diff --git a/src/basic/special.h b/src/basic/special.h index ed3852a4504..98fcddf6318 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 c02ad09c078..f156b809cb5 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 5b9ec759227..0b458ebf28e 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 89a1dead237..2c61c9c7d61 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 3eb53577eba..dc7e4a97674 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 78d1a032e65..1df48d3cbc9 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 08dcee3ce76..a6c87dc852e 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 c7b562143dd..b1cbcc5e917 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 e3fabae3d9a..cba84e9b01b 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 cf0351cf782..dc713e529f2 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 fa76946ddb7..e4f064c1ab6 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 00000000000..6a6c772875e --- /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 00000000000..35ba3a9fc41 --- /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