fix(guest): diagnose and fail on mount errors in the init

A virtiofs mount that failed (bad fsconfig, missing share) was
swallowed: the payload ran without its data and the only symptom
was a confusing failure downstream. The init now names the share
tag and target on the console and exits 126 - the same contract as
an unreachable workdir - instead of booting a run that cannot do
its job.
This commit is contained in:
vhaudiquet committed 2026-10-30 00:03:04 +01:00
1 parent f3cbf16180
commit 6bece1098b
1 file changed
+18 -4
+18 -4
View File
@@ -65,7 +65,9 @@ pub fn sh_quote(s: &str) -> String {
/// Render the guest init. The payload runs as a child (never `exec`): /// Render the guest init. The payload runs as a child (never `exec`):
/// the init must outlive it to write the exit code and power the /// the init must outlive it to write the exit code and power the
/// machine off. /// machine off. A share that fails to mount is named on the console
/// and ends the init with exit 126: a run without its shares is not
/// the run that was asked for.
pub fn init_script( pub fn init_script(
shares: &[GuestShare], shares: &[GuestShare],
payload: &[String], payload: &[String],
@@ -81,11 +83,11 @@ pub fn init_script(
set -u\n\n\ set -u\n\n\
mount_share() {\n\ mount_share() {\n\
\x20 tag=\"$1\"; target=\"$2\"; ro=\"$3\"\n\ \x20 tag=\"$1\"; target=\"$2\"; ro=\"$3\"\n\
\x20 mkdir -p \"$target\"\n\ \x20 mkdir -p \"$target\" || { echo \"ecrou: cannot create share target '$target'\" >&2; exit 126; }\n\
\x20 if [ \"$ro\" = \"ro\" ]; then\n\ \x20 if [ \"$ro\" = \"ro\" ]; then\n\
\x20 mount -t virtiofs -o ro \"$tag\" \"$target\"\n\ \x20 mount -t virtiofs -o ro \"$tag\" \"$target\" || { echo \"ecrou: failed to mount share '$tag' at '$target' (read-only)\" >&2; exit 126; }\n\
\x20 else\n\ \x20 else\n\
\x20 mount -t virtiofs \"$tag\" \"$target\"\n\ \x20 mount -t virtiofs \"$tag\" \"$target\" || { echo \"ecrou: failed to mount share '$tag' at '$target'\" >&2; exit 126; }\n\
\x20 fi\n\ \x20 fi\n\
}\n\n", }\n\n",
); );
@@ -177,6 +179,18 @@ mod tests {
assert_eq!(sh_quote("new\nline"), "'new\nline'"); assert_eq!(sh_quote("new\nline"), "'new\nline'");
} }
#[test]
fn a_failed_share_mount_is_diagnosed_and_fatal() {
let script = init_script(&[], &["true".into()], "/", "m", GuestStdio::Console, None);
assert!(
script.contains("failed to mount share '$tag' at '$target'"),
"{script}"
);
assert!(script.contains("cannot create share target"), "{script}");
// The diagnosis ends the init: nothing runs without its shares.
assert!(script.contains(">&2; exit 126; }"), "{script}");
}
#[test] #[test]
fn mounts_each_share_with_its_ro_flag() { fn mounts_each_share_with_its_ro_flag() {
let shares = vec![ let shares = vec![