mirror of
https://github.com/troglobit/finit.git
synced 2026-10-27 12:20:54 +00:00
initramfs: drop to sulogin when switch_root fails past the ACK
A failed move of /dev, /proc, /sys or /run is logged at dbg() level and ignored, so the new init boots without its virtual filesystems and falls over much later in some unrelated way. The steps after, chdir/mount/chroot and the final execl(), do return -1 on failure, but by then all services are dead and the API socket is gone, so there is nobody left to report to: the system hangs with a live but useless PID 1. Make a failed move fatal, and try all four moves even if one fails, so a bad /dev does not also skip /proc, /sys and /run. A plain directory is not an error though: /run stays a plain directory on a tmpfs rootfs, so only a path on a different device than / is treated as a mount point and moved. Any failure past the point of no return now drops to sulogin(1) for a maintenance shell that reboots on exit, same as a fatal fsck() at boot. Signals are unblocked before the moves so the shell does not inherit finit's blocked signal mask. Signed-off-by: Paweł Sobczak <github@fixeq.qzz.io>
This commit is contained in:
1 parent
7263641953
commit
ad02b343f2
3 files changed
+70
-32
No files matched your search
+1
-1
@@ -122,7 +122,7 @@ static void banner(void)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sulogin(int do_reboot)
|
int sulogin(int do_reboot)
|
||||||
{
|
{
|
||||||
int rc = EX_OSFILE;
|
int rc = EX_OSFILE;
|
||||||
char *cmd[] = {
|
char *cmd[] = {
|
||||||
|
|||||||
+68
-31
@@ -106,7 +106,7 @@ static int is_initramfs(void)
|
|||||||
/*
|
/*
|
||||||
* Move a mount point from oldpath to newpath under newroot
|
* Move a mount point from oldpath to newpath under newroot
|
||||||
*/
|
*/
|
||||||
static int do_move_mount(const char *oldpath, const char *newroot)
|
static int do_move_mount(const char *oldpath, const char *newroot, dev_t rootdev)
|
||||||
{
|
{
|
||||||
char newpath[PATH_MAX];
|
char newpath[PATH_MAX];
|
||||||
struct stat st;
|
struct stat st;
|
||||||
@@ -114,13 +114,22 @@ static int do_move_mount(const char *oldpath, const char *newroot)
|
|||||||
if (stat(oldpath, &st))
|
if (stat(oldpath, &st))
|
||||||
return 0; /* Not mounted, skip */
|
return 0; /* Not mounted, skip */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* stat() succeeds on plain directories too, only a device
|
||||||
|
* differing from / marks a mount point. Cannot use fismnt()
|
||||||
|
* here since /proc may already have moved.
|
||||||
|
*/
|
||||||
|
if (st.st_dev == rootdev)
|
||||||
|
return 0; /* Not a mount point, skip */
|
||||||
|
|
||||||
snprintf(newpath, sizeof(newpath), "%s%s", newroot, oldpath);
|
snprintf(newpath, sizeof(newpath), "%s%s", newroot, oldpath);
|
||||||
|
|
||||||
/* Create target directory if needed */
|
/* Create target directory if needed */
|
||||||
makedir(newpath, 0755);
|
makedir(newpath, 0755);
|
||||||
|
|
||||||
if (mount(oldpath, newpath, NULL, MS_MOVE, NULL)) {
|
if (mount(oldpath, newpath, NULL, MS_MOVE, NULL)) {
|
||||||
dbg("Failed to move %s to %s: %s", oldpath, newpath, strerror(errno));
|
logit(LOG_ERR, "switch_root: failed to move %s to %s: %s",
|
||||||
|
oldpath, newpath, strerror(errno));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,6 +251,28 @@ int switch_root_precheck(const char *newroot, const char *newinit,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Past the point of no return: services are dead and the API socket
|
||||||
|
* is gone, so failures cannot be reported back to anyone. Same deal
|
||||||
|
* as a fatal fsck() at boot: drop to a maintenance shell, sulogin(1)
|
||||||
|
* reboots when it exits.
|
||||||
|
*/
|
||||||
|
static int __attribute__ ((format (printf, 1, 2)))
|
||||||
|
switch_root_rescue(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
char msg[128];
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
logit(LOG_CONSOLE | LOG_ALERT, "switch_root: %s, attempting sulogin ...", msg);
|
||||||
|
sulogin(1);
|
||||||
|
|
||||||
|
return -1; /* not reached, sulogin(1) reboots */
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Perform switch_root to a new root filesystem
|
* Perform switch_root to a new root filesystem
|
||||||
*
|
*
|
||||||
@@ -251,6 +282,7 @@ int switch_root_precheck(const char *newroot, const char *newinit,
|
|||||||
int switch_root(const char *newroot, const char *newinit)
|
int switch_root(const char *newroot, const char *newinit)
|
||||||
{
|
{
|
||||||
struct stat oldroot_st;
|
struct stat oldroot_st;
|
||||||
|
int failed = 0;
|
||||||
int console_fd;
|
int console_fd;
|
||||||
dev_t rootdev;
|
dev_t rootdev;
|
||||||
int signo;
|
int signo;
|
||||||
@@ -263,10 +295,11 @@ int switch_root(const char *newroot, const char *newinit)
|
|||||||
if (!newinit || !newinit[0])
|
if (!newinit || !newinit[0])
|
||||||
newinit = "/sbin/init";
|
newinit = "/sbin/init";
|
||||||
|
|
||||||
/* Needed below for the initramfs cleanup */
|
/* Needed below for the moves and the initramfs cleanup */
|
||||||
if (stat("/", &oldroot_st))
|
if (stat("/", &oldroot_st))
|
||||||
return switch_root_fail(NULL, 0, errno,
|
return switch_root_fail(NULL, 0, errno,
|
||||||
"cannot stat /: %s", strerror(errno));
|
"cannot stat /: %s", strerror(errno));
|
||||||
|
rootdev = oldroot_st.st_dev;
|
||||||
|
|
||||||
logit(LOG_NOTICE, "Performing switch_root to %s, init %s", newroot, newinit);
|
logit(LOG_NOTICE, "Performing switch_root to %s, init %s", newroot, newinit);
|
||||||
|
|
||||||
@@ -298,42 +331,50 @@ int switch_root(const char *newroot, const char *newinit)
|
|||||||
plugin_exit();
|
plugin_exit();
|
||||||
cond_exit();
|
cond_exit();
|
||||||
|
|
||||||
/* Move virtual filesystems to new root */
|
/*
|
||||||
|
* Unblock signals already here, before the rescue paths in
|
||||||
|
* switch_root_rescue() can trigger, so a maintenance shell
|
||||||
|
* does not inherit our blocked signal mask.
|
||||||
|
*/
|
||||||
|
sig_unblock();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Move virtual filesystems to new root. Try all four even if
|
||||||
|
* one fails, so a bad /dev doesn't also skip /proc, /sys and
|
||||||
|
* /run. Each failure is logged by do_move_mount() itself.
|
||||||
|
*/
|
||||||
dbg("Moving virtual filesystems...");
|
dbg("Moving virtual filesystems...");
|
||||||
do_move_mount("/dev", newroot);
|
failed |= do_move_mount("/dev", newroot, rootdev);
|
||||||
do_move_mount("/proc", newroot);
|
failed |= do_move_mount("/proc", newroot, rootdev);
|
||||||
do_move_mount("/sys", newroot);
|
failed |= do_move_mount("/sys", newroot, rootdev);
|
||||||
do_move_mount("/run", newroot);
|
failed |= do_move_mount("/run", newroot, rootdev);
|
||||||
|
if (failed)
|
||||||
|
return switch_root_rescue("failed to move virtual filesystems");
|
||||||
|
|
||||||
/* Change to new root directory */
|
/* Change to new root directory */
|
||||||
if (chdir(newroot)) {
|
if (chdir(newroot))
|
||||||
err(1, "Failed to chdir to %s", newroot);
|
return switch_root_rescue("failed to chdir to %s: %s",
|
||||||
return -1;
|
newroot, strerror(errno));
|
||||||
}
|
|
||||||
|
|
||||||
/* Delete contents of old root if we're on initramfs */
|
/* Delete contents of old root if we're on initramfs */
|
||||||
rootdev = oldroot_st.st_dev;
|
|
||||||
if (is_initramfs()) {
|
if (is_initramfs()) {
|
||||||
dbg("Deleting initramfs contents...");
|
dbg("Deleting initramfs contents...");
|
||||||
delete_initramfs_contents(rootdev, newroot);
|
delete_initramfs_contents(rootdev, newroot);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mount --move newroot to / */
|
/* Mount --move newroot to / */
|
||||||
if (mount(newroot, "/", NULL, MS_MOVE, NULL)) {
|
if (mount(newroot, "/", NULL, MS_MOVE, NULL))
|
||||||
err(1, "Failed to move %s to /", newroot);
|
return switch_root_rescue("failed to move %s to /: %s",
|
||||||
return -1;
|
newroot, strerror(errno));
|
||||||
}
|
|
||||||
|
|
||||||
/* chroot to new root */
|
/* chroot to new root */
|
||||||
if (chroot(".")) {
|
if (chroot("."))
|
||||||
err(1, "Failed to chroot to new root");
|
return switch_root_rescue("failed to chroot to new root: %s",
|
||||||
return -1;
|
strerror(errno));
|
||||||
}
|
|
||||||
|
|
||||||
if (chdir("/")) {
|
if (chdir("/"))
|
||||||
err(1, "Failed to chdir to /");
|
return switch_root_rescue("failed to chdir to /: %s",
|
||||||
return -1;
|
strerror(errno));
|
||||||
}
|
|
||||||
|
|
||||||
/* Reopen console in new root. dup2() closes the old fds itself,
|
/* Reopen console in new root. dup2() closes the old fds itself,
|
||||||
* so open() returns a fd > STDERR_FILENO that we can always close. */
|
* so open() returns a fd > STDERR_FILENO that we can always close. */
|
||||||
@@ -345,16 +386,12 @@ int switch_root(const char *newroot, const char *newinit)
|
|||||||
close(console_fd);
|
close(console_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reset signals to default */
|
|
||||||
sig_unblock();
|
|
||||||
|
|
||||||
/* Exec the new init - this does not return on success */
|
/* Exec the new init - this does not return on success */
|
||||||
dbg("Executing %s...", newinit);
|
dbg("Executing %s...", newinit);
|
||||||
execl(newinit, newinit, NULL);
|
execl(newinit, newinit, NULL);
|
||||||
|
|
||||||
/* If we get here, exec failed */
|
return switch_root_rescue("failed to exec %s: %s",
|
||||||
err(1, "Failed to exec %s", newinit);
|
newinit, strerror(errno));
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ void iterate_proc (int (*cb)(int, void *), void *data);
|
|||||||
int switch_root_precheck(const char *newroot, const char *newinit,
|
int switch_root_precheck(const char *newroot, const char *newinit,
|
||||||
char *errbuf, size_t errbuflen);
|
char *errbuf, size_t errbuflen);
|
||||||
int switch_root (const char *newroot, const char *newinit);
|
int switch_root (const char *newroot, const char *newinit);
|
||||||
|
int sulogin (int do_reboot);
|
||||||
|
|
||||||
#endif /* FINIT_PRIVATE_H_ */
|
#endif /* FINIT_PRIVATE_H_ */
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user