]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
pid1: add "soft-reboot" reboot method
authorLennart Poettering <lennart@poettering.net>
Thu, 27 Apr 2023 15:23:18 +0000 (17:23 +0200)
committerLennart Poettering <lennart@poettering.net>
Fri, 2 Jun 2023 14:49:38 +0000 (16:49 +0200)
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)

13 files changed:
src/basic/special.h
src/core/dbus-manager.c
src/core/emergency-action.c
src/core/emergency-action.h
src/core/main.c
src/core/manager.c
src/core/manager.h
src/shared/switch-root.c
src/shared/switch-root.h
src/shutdown/shutdown.c
units/meson.build
units/soft-reboot.target [new file with mode: 0644]
units/systemd-soft-reboot.service [new file with mode: 0644]

index ed3852a4504fd23c5e80212222ca093baa427a34..98fcddf6318b3939b42618b5e7ffd40a049e9efa 100644 (file)
@@ -14,6 +14,7 @@
 #define SPECIAL_HALT_TARGET "halt.target"
 #define SPECIAL_POWEROFF_TARGET "poweroff.target"
 #define SPECIAL_REBOOT_TARGET "reboot.target"
+#define SPECIAL_SOFT_REBOOT_TARGET "soft-reboot.target"
 #define SPECIAL_KEXEC_TARGET "kexec.target"
 #define SPECIAL_EXIT_TARGET "exit.target"
 #define SPECIAL_SUSPEND_TARGET "suspend.target"
index c02ad09c0787f199f96c1ec0e3885e8516269390..f156b809cb5a41cddad854b8371731821182defe 100644 (file)
@@ -1713,6 +1713,45 @@ static int method_reboot(sd_bus_message *message, void *userdata, sd_bus_error *
         return sd_bus_reply_method_return(message, NULL);
 }
 
+static int method_soft_reboot(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        _cleanup_free_ char *rt = NULL;
+        Manager *m = ASSERT_PTR(userdata);
+        const char *root;
+        int r;
+
+        assert(message);
+
+        r = verify_run_space_permissive("soft reboot may fail", error);
+        if (r < 0)
+                return r;
+
+        r = mac_selinux_access_check(message, "reboot", error);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_read(message, "s", &root);
+        if (r < 0)
+                return r;
+
+        if (!isempty(root)) {
+                if (!path_is_valid(root))
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+                                                 "New root directory '%s' must be a valid path.", root);
+                if (!path_is_absolute(root))
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+                                                 "New root directory path '%s' is not absolute.", root);
+
+                rt = strdup(root);
+                if (!rt)
+                        return -ENOMEM;
+        }
+
+        free_and_replace(m->switch_root, rt);
+        m->objective = MANAGER_SOFT_REBOOT;
+
+        return sd_bus_reply_method_return(message, NULL);
+}
+
 static int method_poweroff(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         Manager *m = ASSERT_PTR(userdata);
         int r;
@@ -1772,8 +1811,8 @@ static int method_kexec(sd_bus_message *message, void *userdata, sd_bus_error *e
 
 static int method_switch_root(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         _cleanup_free_ char *ri = NULL, *rt = NULL;
-        const char *root, *init;
         Manager *m = ASSERT_PTR(userdata);
+        const char *root, *init;
         int r;
 
         assert(message);
@@ -3260,6 +3299,11 @@ const sd_bus_vtable bus_manager_vtable[] = {
                       NULL,
                       method_reboot,
                       SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)),
+        SD_BUS_METHOD_WITH_ARGS("SoftReboot",
+                                SD_BUS_ARGS("s", new_root),
+                                SD_BUS_NO_RESULT,
+                                method_soft_reboot,
+                                SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)),
         SD_BUS_METHOD("PowerOff",
                       NULL,
                       NULL,
index 5b9ec75922787bec1cd23f407ec8b282b7d0ab8e..0b458ebf28e8959d0712e40bd6126aaf769b1e76 100644 (file)
@@ -22,6 +22,8 @@ static const char* const emergency_action_table[_EMERGENCY_ACTION_MAX] = {
         [EMERGENCY_ACTION_POWEROFF_IMMEDIATE] = "poweroff-immediate",
         [EMERGENCY_ACTION_EXIT] =               "exit",
         [EMERGENCY_ACTION_EXIT_FORCE] =         "exit-force",
+        [EMERGENCY_ACTION_SOFT_REBOOT] =        "soft-reboot",
+        [EMERGENCY_ACTION_SOFT_REBOOT_FORCE] =  "soft-reboot-force",
 };
 
 static void log_and_status(Manager *m, bool warn, const char *message, const char *reason) {
@@ -47,7 +49,7 @@ void emergency_action(
         assert(action < _EMERGENCY_ACTION_MAX);
 
         /* Is the special shutdown target active or queued? If so, we are in shutdown state */
-        if (IN_SET(action, EMERGENCY_ACTION_REBOOT, EMERGENCY_ACTION_POWEROFF, EMERGENCY_ACTION_EXIT)) {
+        if (IN_SET(action, EMERGENCY_ACTION_REBOOT, EMERGENCY_ACTION_SOFT_REBOOT, EMERGENCY_ACTION_POWEROFF, EMERGENCY_ACTION_EXIT)) {
                 u = manager_get_unit(m, SPECIAL_SHUTDOWN_TARGET);
                 if (u && unit_active_or_pending(u)) {
                         log_notice("Shutdown is already active. Skipping emergency action request %s.",
@@ -80,7 +82,6 @@ void emergency_action(
 
                 (void) update_reboot_parameter_and_warn(reboot_arg, true);
                 m->objective = MANAGER_REBOOT;
-
                 break;
 
         case EMERGENCY_ACTION_REBOOT_IMMEDIATE:
@@ -98,6 +99,18 @@ void emergency_action(
                 (void) reboot(RB_AUTOBOOT);
                 break;
 
+        case EMERGENCY_ACTION_SOFT_REBOOT:
+                log_and_status(m, warn, "Soft-rebooting", reason);
+
+                (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_SOFT_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL);
+                break;
+
+        case EMERGENCY_ACTION_SOFT_REBOOT_FORCE:
+                log_and_status(m, warn, "Forcibly soft-rebooting", reason);
+
+                m->objective = MANAGER_SOFT_REBOOT;
+                break;
+
         case EMERGENCY_ACTION_EXIT:
 
                 if (exit_status >= 0)
index 89a1dead23785a601229cd567e7d2281e9924117..2c61c9c7d6124599f22542e76cfeb3ca8a40b3f5 100644 (file)
@@ -16,6 +16,8 @@ typedef enum EmergencyAction {
         EMERGENCY_ACTION_EXIT,
         _EMERGENCY_ACTION_FIRST_USER_ACTION = EMERGENCY_ACTION_EXIT,
         EMERGENCY_ACTION_EXIT_FORCE,
+        EMERGENCY_ACTION_SOFT_REBOOT,
+        EMERGENCY_ACTION_SOFT_REBOOT_FORCE,
         _EMERGENCY_ACTION_MAX,
         _EMERGENCY_ACTION_INVALID = -EINVAL,
 } EmergencyAction;
index 3eb53577eba104cbf182f69ec2305456e0190b7c..dc7e4a97674039df31b9bd448898c45fa8bac0c7 100644 (file)
@@ -1788,7 +1788,7 @@ static int do_reexecute(
         const char **args;
         int r;
 
-        assert(IN_SET(objective, MANAGER_REEXECUTE, MANAGER_SWITCH_ROOT));
+        assert(IN_SET(objective, MANAGER_REEXECUTE, MANAGER_SWITCH_ROOT, MANAGER_SOFT_REBOOT));
         assert(argc >= 0);
         assert(saved_rlimit_nofile);
         assert(saved_rlimit_memlock);
@@ -1823,13 +1823,26 @@ static int do_reexecute(
         if (saved_rlimit_memlock->rlim_cur != RLIM_INFINITY)
                 (void) setrlimit(RLIMIT_MEMLOCK, saved_rlimit_memlock);
 
-        if (switch_root_dir) {
-                /* Kill all remaining processes from the initrd, but don't wait for them, so that we can
-                 * handle the SIGCHLD for them after deserializing. */
-                broadcast_signal(SIGTERM, false, true, arg_default_timeout_stop_usec);
+        /* Kill all remaining processes from the initrd, but don't wait for them, so that we can handle the
+         * SIGCHLD for them after deserializing. */
+        if (IN_SET(objective, MANAGER_SWITCH_ROOT, MANAGER_SOFT_REBOOT))
+                broadcast_signal(SIGTERM, /* wait_for_exit= */ false, /* send_sighup= */ true, arg_default_timeout_stop_usec);
+
+        if (!switch_root_dir && objective == MANAGER_SOFT_REBOOT) {
+                /* If no switch root dir is specified, then check if /run/nextroot/ qualifies and use that */
+                r = path_is_os_tree("/run/nextroot");
+                if (r < 0 && r != -ENOENT)
+                        log_debug_errno(r, "Failed to determine if /run/nextroot/ is a valid OS tree, ignoring: %m");
+                else if (r > 0)
+                        switch_root_dir = "/run/nextroot";
+        }
 
+        if (switch_root_dir) {
                 /* And switch root with MS_MOVE, because we remove the old directory afterwards and detach it. */
-                r = switch_root(switch_root_dir, /* old_root_after= */ NULL, MS_MOVE);
+                r = switch_root(/* new_root= */ switch_root_dir,
+                                /* old_root_after= */ NULL,
+                                MS_MOVE,
+                                /* destroy_old_root= */ objective == MANAGER_SWITCH_ROOT);
                 if (r < 0)
                         log_error_errno(r, "Failed to switch root, trying to continue: %m");
         }
@@ -1851,7 +1864,7 @@ static int do_reexecute(
                 i = 1;         /* Leave args[0] empty for now. */
                 filter_args(args, &i, argv, argc);
 
-                if (switch_root_dir)
+                if (IN_SET(objective, MANAGER_SWITCH_ROOT, MANAGER_SOFT_REBOOT))
                         args[i++] = "--switched-root";
                 args[i++] = runtime_scope_cmdline_option_to_string(arg_runtime_scope);
                 args[i++] = sfd;
@@ -2036,6 +2049,24 @@ static int invoke_main_loop(
 
                         return objective;
 
+                case MANAGER_SOFT_REBOOT:
+                        manager_send_reloading(m);
+                        manager_set_switching_root(m, true);
+
+                        r = prepare_reexecute(m, &arg_serialization, ret_fds, /* switching_root= */ true);
+                        if (r < 0) {
+                                *ret_error_message = "Failed to prepare for reexecution";
+                                return r;
+                        }
+
+                        log_notice("Soft-rebooting.");
+
+                        *ret_retval = EXIT_SUCCESS;
+                        *ret_switch_root_dir = TAKE_PTR(m->switch_root);
+                        *ret_switch_root_init = NULL;
+
+                        return objective;
+
                 case MANAGER_EXIT:
                         if (MANAGER_IS_USER(m)) {
                                 log_debug("Exit.");
@@ -3093,6 +3124,7 @@ int main(int argc, char *argv[]) {
                                   MANAGER_RELOAD,
                                   MANAGER_REEXECUTE,
                                   MANAGER_REBOOT,
+                                  MANAGER_SOFT_REBOOT,
                                   MANAGER_POWEROFF,
                                   MANAGER_HALT,
                                   MANAGER_KEXEC,
@@ -3109,7 +3141,7 @@ finish:
 
         mac_selinux_finish();
 
-        if (IN_SET(r, MANAGER_REEXECUTE, MANAGER_SWITCH_ROOT))
+        if (IN_SET(r, MANAGER_REEXECUTE, MANAGER_SWITCH_ROOT, MANAGER_SOFT_REBOOT))
                 r = do_reexecute(r,
                                  argc, argv,
                                  &saved_rlimit_nofile,
index 78d1a032e6588c956276b31fd32e844a9f2ab444..1df48d3cbc968d33dea1ff2e25c0d9b1d9542026 100644 (file)
@@ -561,6 +561,7 @@ static int manager_setup_signals(Manager *m) {
                         SIGRTMIN+4,  /* systemd: start poweroff.target */
                         SIGRTMIN+5,  /* systemd: start reboot.target */
                         SIGRTMIN+6,  /* systemd: start kexec.target */
+                        SIGRTMIN+7,  /* systemd: start soft-reboot.target */
 
                         /* ... space for more special targets ... */
 
@@ -568,9 +569,7 @@ static int manager_setup_signals(Manager *m) {
                         SIGRTMIN+14, /* systemd: Immediate poweroff */
                         SIGRTMIN+15, /* systemd: Immediate reboot */
                         SIGRTMIN+16, /* systemd: Immediate kexec */
-
-                        /* ... space for one more immediate system state change ... */
-
+                        SIGRTMIN+17, /* systemd: Immediate soft-reboot */
                         SIGRTMIN+18, /* systemd: control command */
 
                         /* ... space ... */
@@ -1627,7 +1626,7 @@ Manager* manager_free(Manager *m) {
                         unit_vtable[c]->shutdown(m);
 
         /* Keep the cgroup hierarchy in place except when we know we are going down for good */
-        manager_shutdown_cgroup(m, IN_SET(m->objective, MANAGER_EXIT, MANAGER_REBOOT, MANAGER_POWEROFF, MANAGER_HALT, MANAGER_KEXEC));
+        manager_shutdown_cgroup(m, /* delete= */ IN_SET(m->objective, MANAGER_EXIT, MANAGER_REBOOT, MANAGER_POWEROFF, MANAGER_HALT, MANAGER_KEXEC));
 
         lookup_paths_flush_generator(&m->lookup_paths);
 
@@ -2979,13 +2978,14 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
                         const char *target;
                         JobMode mode;
                 } target_table[] = {
-                        [0] = { SPECIAL_DEFAULT_TARGET,   JOB_ISOLATE },
-                        [1] = { SPECIAL_RESCUE_TARGET,    JOB_ISOLATE },
-                        [2] = { SPECIAL_EMERGENCY_TARGET, JOB_ISOLATE },
-                        [3] = { SPECIAL_HALT_TARGET,      JOB_REPLACE_IRREVERSIBLY },
-                        [4] = { SPECIAL_POWEROFF_TARGET,  JOB_REPLACE_IRREVERSIBLY },
-                        [5] = { SPECIAL_REBOOT_TARGET,    JOB_REPLACE_IRREVERSIBLY },
-                        [6] = { SPECIAL_KEXEC_TARGET,     JOB_REPLACE_IRREVERSIBLY },
+                        [0] = { SPECIAL_DEFAULT_TARGET,     JOB_ISOLATE },
+                        [1] = { SPECIAL_RESCUE_TARGET,      JOB_ISOLATE },
+                        [2] = { SPECIAL_EMERGENCY_TARGET,   JOB_ISOLATE },
+                        [3] = { SPECIAL_HALT_TARGET,        JOB_REPLACE_IRREVERSIBLY },
+                        [4] = { SPECIAL_POWEROFF_TARGET,    JOB_REPLACE_IRREVERSIBLY },
+                        [5] = { SPECIAL_REBOOT_TARGET,      JOB_REPLACE_IRREVERSIBLY },
+                        [6] = { SPECIAL_KEXEC_TARGET,       JOB_REPLACE_IRREVERSIBLY },
+                        [7] = { SPECIAL_SOFT_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY },
                 };
 
                 /* Starting SIGRTMIN+13, so that target halt and system halt are 10 apart */
@@ -2994,6 +2994,7 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
                         [1] = MANAGER_POWEROFF,
                         [2] = MANAGER_REBOOT,
                         [3] = MANAGER_KEXEC,
+                        [4] = MANAGER_SOFT_REBOOT,
                 };
 
                 if ((int) sfsi.ssi_signo >= SIGRTMIN+0 &&
index 08dcee3ce761c630f4e2fb443c86d2f52df2976c..a6c87dc852ef875dc64b3c3675691b9fdb4ede0e 100644 (file)
@@ -44,6 +44,7 @@ typedef enum ManagerObjective {
         MANAGER_RELOAD,
         MANAGER_REEXECUTE,
         MANAGER_REBOOT,
+        MANAGER_SOFT_REBOOT,
         MANAGER_POWEROFF,
         MANAGER_HALT,
         MANAGER_KEXEC,
index c7b562143dd9817a4baa13e7d5b2df90023754dc..b1cbcc5e91715faf1dcde325ed73abef69daf273 100644 (file)
@@ -27,7 +27,8 @@
 
 int switch_root(const char *new_root,
                 const char *old_root_after,   /* path below the new root, where to place the old root after the transition; may be NULL to unmount it */
-                unsigned long mount_flags) {  /* MS_MOVE or MS_BIND used for /proc/, /dev/, /run/, /sys/ */
+                unsigned long mount_flags,    /* MS_MOVE or MS_BIND used for /proc/, /dev/, /run/, /sys/ */
+                bool destroy_old_root) {
 
         _cleanup_close_ int old_root_fd = -EBADF, new_root_fd = -EBADF;
         _cleanup_free_ char *resolved_old_root_after = NULL;
@@ -144,7 +145,7 @@ int switch_root(const char *new_root,
                         return log_error_errno(errno, "Failed to change directory: %m");
         }
 
-        if (istmp) {
+        if (istmp && destroy_old_root) {
                 struct stat rb;
 
                 if (fstat(old_root_fd, &rb) < 0)
index e3fabae3d9a6253b92f7941204ced800817574ab..cba84e9b01b632c5b685638a9e7325f19fbc55b4 100644 (file)
@@ -3,4 +3,4 @@
 
 #include <stdbool.h>
 
-int switch_root(const char *new_root, const char *old_root_after, unsigned long mount_flags);
+int switch_root(const char *new_root, const char *old_root_after, unsigned long mount_flags, bool destroy_old_root);
index cf0351cf78298161cc4bdc3568482abc07b1143e..dc713e529f2ed935c3d8f0a1e1e0ad75e196903b 100644 (file)
@@ -169,7 +169,11 @@ static int switch_root_initramfs(void) {
          * /run/initramfs/shutdown will take care of these.
          * Also do not detach the old root, because /run/initramfs/shutdown needs to access it.
          */
-        return switch_root("/run/initramfs", "/oldroot", MS_BIND);
+        return switch_root(
+                        /* new_root= */ "/run/initramfs",
+                        /* old_root_after= */ "/oldroot",
+                        MS_BIND,
+                        /* destroy_old_root= */ false);
 }
 
 /* Read the following fields from /proc/meminfo:
index fa76946ddb7e2270edb3d07850f83cdd8506f660..e4f064c1ab65dd9d55b82c3cfdc2c210f32bed73 100644 (file)
@@ -67,6 +67,7 @@ units = [
         ['proc-sys-fs-binfmt_misc.mount',       'ENABLE_BINFMT'],
         ['reboot.target',                       '',
          'ctrl-alt-del.target' + (with_runlevels ? ' runlevel6.target' : '')],
+        ['soft-reboot.target',                  ''],
         ['remote-cryptsetup.target',            'HAVE_LIBCRYPTSETUP',
          'initrd-root-device.target.wants/'],
         ['remote-veritysetup.target',           'HAVE_LIBCRYPTSETUP',
@@ -137,6 +138,7 @@ units = [
         ['systemd-networkd.socket',             'ENABLE_NETWORKD'],
         ['systemd-poweroff.service',            ''],
         ['systemd-reboot.service',              ''],
+        ['systemd-soft-reboot.service',         ''],
         ['systemd-rfkill.socket',               'ENABLE_RFKILL'],
         ['systemd-sysext.service',              'ENABLE_SYSEXT'],
         ['systemd-confext.service',             'ENABLE_SYSEXT'],
diff --git a/units/soft-reboot.target b/units/soft-reboot.target
new file mode 100644 (file)
index 0000000..6a6c772
--- /dev/null
@@ -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 (file)
index 0000000..35ba3a9
--- /dev/null
@@ -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