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>
The precheck accepts a directory as new init: access(path, X_OK)
only checks search permission, which directories almost always have,
so the mistake is not caught until after teardown. The snprintf()
building init_path also never checks for truncation, so an overlong
newroot + newinit validates the wrong path.
Require a regular file, with EISDIR or ENOEXEC to match what is
printed, and fail with ENAMETOOLONG on a truncated init path.
Signed-off-by: Paweł Sobczak <github@fixeq.qzz.io>
initctl switch-root always exits 0, no matter what happens on the
finit side. do_switch_root_api() sends the ACK before switch_root()
has validated anything, so any failure after that point never reaches
the client -- the error only shows up in the log. The runlevel guard
in api_cb() has the same problem: it rejects the request but still
sends an ACK.
Split the validation out of switch_root() into switch_root_precheck()
and run it before the ACK. A failed check now sends a NACK with the
error message, which initctl prints before exiting 1:
initctl switch-root /mnt
switch-root: /mnt is not a mount point
The ACK is only sent once the precheck passes, since after that point
we are committed. On success finit execs the new init and the
connection dies with no reply at all, so initctl treats only an
explicit NACK as failure.
Signed-off-by: Paweł Sobczak <github@fixeq.qzz.io>