core/main: preemptively check existence of init only if we're switching root

Follow-up for aaa27e2e21

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.
This commit is contained in:
Mike Yuan
2025-03-08 20:49:30 +01:00
parent 550f4718bc
commit 93e19483dc
+39 -32
View 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(