]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
manager: fall back to direct reboot() if shutdown binary is missing
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Tue, 30 Jun 2026 17:22:33 +0000 (19:22 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 2 Jul 2026 15:19:18 +0000 (17:19 +0200)
When PID 1 reaches the shutdown phase, if systemd-shutdown was missing,
it'd complain loudly and freeze. Implement an internal fallback to
handle this case gracefully. The fallback is always in place, since it
makes things generally more robust and is only a little bit of code.

This shutdown is simplified, systemd issues the reboot() matching the
requested objective directly, mirroring what systemd-shutdown would do
as its final step. This skips all the cleanup systemd-shutdown normally
performs (unmounting file systems, detaching storage, killing remaining
processes, switching into the exitrd).

With SYSTEMD_LOG_LEVEL=debug:
...
[  OK  ] Reached target poweroff.target.
Notify message sent to '/run/host/notify': "X_SYSTEMD_UNIT_ACTIVE=poweroff.target"
Shutting down.
OSC sequence for shutdown successfully written: ...
Failed to execute shutdown binary: No such file or directory
Powering off.
Container container has been shut down.

src/core/main.c

index c0f68222ed14d48db276bb19ab46a5bd1f775c2f..5c02e30880068e054b5af591d1e67a6355571502 100644 (file)
@@ -95,6 +95,7 @@
 #include "proc-cmdline.h"
 #include "process-util.h"
 #include "random-util.h"
+#include "reboot-util.h"
 #include "rlimit-util.h"
 #include "rm-rf.h"
 #include "seccomp-util.h"
@@ -1861,6 +1862,52 @@ static int become_shutdown(int objective, int retval) {
         return -errno;
 }
 
+static int fallback_shutdown(ManagerObjective objective, int retval) {
+        int cmd;
+
+        /* Perform the requested shutdown operation ourselves, by issuing the matching reboot() directly.
+         * This skips the cleanup systemd-shutdown normally does (unmounting file systems, detaching storage,
+         * killing remaining processes, switching into the exitrd). */
+
+        switch (objective) {
+
+        case MANAGER_KEXEC:
+                log_info("Rebooting with kexec.");
+                (void) kexec();
+                /* If we are still here kexec didn't work, fall back to a plain reboot. */
+                _fallthrough_;
+
+        case MANAGER_REBOOT:
+                (void) reboot_with_parameter(REBOOT_LOG);
+                log_info("Rebooting.");
+                cmd = RB_AUTOBOOT;
+                break;
+
+        case MANAGER_EXIT:
+                if (detect_container() > 0) {
+                        log_info("Exiting container.");
+                        exit(retval);
+                }
+                _fallthrough_;
+
+        case MANAGER_POWEROFF:
+                log_info("Powering off.");
+                cmd = RB_POWER_OFF;
+                break;
+
+        case MANAGER_HALT:
+                log_info("Halting system.");
+                cmd = RB_HALT_SYSTEM;
+                break;
+
+        default:
+                assert_not_reached();
+        }
+
+        (void) reboot(cmd);
+        return log_error_errno(errno, "Failed to shut down: %m");
+}
+
 static void initialize_clock_timewarp(void) {
         int r;
 
@@ -3934,9 +3981,20 @@ finish:
          * If we failed above, we want to freeze after finishing cleanup. */
         if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM &&
             IN_SET(r, MANAGER_EXIT, MANAGER_REBOOT, MANAGER_POWEROFF, MANAGER_HALT, MANAGER_KEXEC)) {
-                r = become_shutdown(r, retval);
-                log_error_errno(r, "Failed to execute shutdown binary, %s: %m", getpid_cached() == 1 ? "freezing" : "quitting");
-                error_message = "Failed to execute shutdown binary";
+                ManagerObjective objective = r;
+
+                r = become_shutdown(objective, retval);
+                assert(r < 0);
+                log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
+                               "Failed to execute shutdown binary: %m");
+
+                /* As PID 1, carry out the requested operation ourselves rather than relying on the shutdown
+                 * binary. This only returns if that didn't work, in which case we freeze/exit/reboot
+                 * below. */
+                r = fallback_shutdown(objective, retval);
+                assert(r < 0);
+                log_error_errno(r, "Failed to perform shutdown: %m");
+                error_message = "Failed to perform shutdown";
         }
 
         /* This is primarily useful when running systemd in a VM, as it provides the user running the VM with