]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core/main: preemptively check existence of init only if we're switching root
authorMike Yuan <me@yhndnzj.com>
Sat, 8 Mar 2025 19:09:58 +0000 (20:09 +0100)
committerMike Yuan <me@yhndnzj.com>
Sat, 8 Mar 2025 19:49:30 +0000 (20:49 +0100)
Follow-up for aaa27e2e21c04339914f26b7125789087eb51166

The commit described about system potentially becoming undebuggable
after switching into broken root or whatnot. But notably we can never
activate emergency.target after do_reexecute() failure, since the Manager
has been destructed. Plus, for a normal reexecution the fallback shell logic
triggered on non-existent /sbin/init is kinda useful. Let's hence guard
the extra check behind switch-root.

Also, move the check below /run/nextroot/ detection.

src/core/main.c

index 6b9b9eb691564bdbe14edc8032f6aac3278dd58d..4db7ad15d257a3f6f3b65430a515057a3bd650fe 100644 (file)
@@ -1977,37 +1977,10 @@ static int do_reexecute(
         assert(saved_rlimit_memlock);
         assert(ret_error_message);
 
-        if (switch_root_init) {
-                r = chase(switch_root_init, switch_root_dir, CHASE_PREFIX_ROOT, NULL, NULL);
-                if (r < 0)
-                        log_warning_errno(r, "Failed to chase configured init %s/%s: %m",
-                                          strempty(switch_root_dir), switch_root_init);
-        } else {
-                r = chase(SYSTEMD_BINARY_PATH, switch_root_dir, CHASE_PREFIX_ROOT, NULL, NULL);
-                if (r < 0)
-                        log_debug_errno(r, "Failed to chase our own binary %s/%s: %m",
-                                        strempty(switch_root_dir), SYSTEMD_BINARY_PATH);
-        }
-
-        if (r < 0) {
-                r = chase("/sbin/init", switch_root_dir, CHASE_PREFIX_ROOT, NULL, NULL);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to chase %s/sbin/init", strempty(switch_root_dir));
-        }
-
         /* Close and disarm the watchdog, so that the new instance can reinitialize it, but doesn't get
          * rebooted while we do that */
         watchdog_close(true);
 
-        /* Reset RLIMIT_NOFILE + RLIMIT_MEMLOCK back to the kernel defaults, so that the new systemd can pass
-         * the kernel default to its child processes */
-        if (saved_rlimit_nofile->rlim_cur != 0)
-                (void) setrlimit(RLIMIT_NOFILE, saved_rlimit_nofile);
-        if (saved_rlimit_memlock->rlim_cur != RLIM_INFINITY)
-                (void) setrlimit(RLIMIT_MEMLOCK, saved_rlimit_memlock);
-
-        finish_remaining_processes(objective);
-
         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");
@@ -2017,6 +1990,39 @@ static int do_reexecute(
                         switch_root_dir = "/run/nextroot";
         }
 
+        if (switch_root_dir) {
+                /* If we're supposed to switch root, preemptively check the existence of a usable init.
+                 * Otherwise the system might end up in a completely undebuggable state afterwards. */
+                if (switch_root_init) {
+                        r = chase_and_access(switch_root_init, switch_root_dir, CHASE_PREFIX_ROOT, X_OK, /* ret_path = */ NULL);
+                        if (r < 0)
+                                log_warning_errno(r, "Failed to chase configured init %s/%s: %m",
+                                                  switch_root_dir, switch_root_init);
+                } else {
+                        r = chase_and_access(SYSTEMD_BINARY_PATH, switch_root_dir, CHASE_PREFIX_ROOT, X_OK, /* ret_path = */ NULL);
+                        if (r < 0)
+                                log_debug_errno(r, "Failed to chase our own binary %s/%s: %m",
+                                                switch_root_dir, SYSTEMD_BINARY_PATH);
+                }
+
+                if (r < 0) {
+                        r = chase_and_access("/sbin/init", switch_root_dir, CHASE_PREFIX_ROOT, X_OK, /* ret_path = */ NULL);
+                        if (r < 0) {
+                                *ret_error_message = "Switch root target contains no usable init";
+                                return log_error_errno(r, "Failed to chase %s/sbin/init", switch_root_dir);
+                        }
+                }
+        }
+
+        /* Reset RLIMIT_NOFILE + RLIMIT_MEMLOCK back to the kernel defaults, so that the new systemd can pass
+         * the kernel default to its child processes */
+        if (saved_rlimit_nofile->rlim_cur != 0)
+                (void) setrlimit(RLIMIT_NOFILE, saved_rlimit_nofile);
+        if (saved_rlimit_memlock->rlim_cur != RLIM_INFINITY)
+                (void) setrlimit(RLIMIT_MEMLOCK, saved_rlimit_memlock);
+
+        finish_remaining_processes(objective);
+
         if (switch_root_dir) {
                 r = switch_root(/* new_root= */ switch_root_dir,
                                 /* old_root_after= */ NULL,
@@ -2123,16 +2129,17 @@ static int do_reexecute(
                               ANSI_HIGHLIGHT_RED "  !!  " ANSI_NORMAL,
                               "Failed to execute /sbin/init");
 
-        *ret_error_message = "Failed to execute fallback shell";
         if (r == -ENOENT) {
-                log_warning("No /sbin/init, trying fallback");
+                log_warning_errno(r, "No /sbin/init, trying fallback shell");
 
                 args[0] = "/bin/sh";
                 args[1] = NULL;
                 (void) execve(args[0], (char* const*) args, saved_env);
-                return log_error_errno(errno, "Failed to execute /bin/sh, giving up: %m");
-        } else
-                return log_error_errno(r, "Failed to execute /sbin/init, giving up: %m");
+                r = -errno;
+        }
+
+        *ret_error_message = "Failed to execute fallback shell";
+        return log_error_errno(r, "Failed to execute /bin/sh, giving up: %m");
 }
 
 static int invoke_main_loop(