Add --mount flag for 9p host directory sharing
CI / build (pull_request) Successful in 13s

Add support for sharing host directories into the VM via QEMU 9p
(virtio-9p).  The --mount flag can be repeated; each host directory
appears at the same absolute path inside the VM.

Design:
- QEMU: each --mount gets a short 9p tag (slim0, slim1, …) via
  -virtfs local,path=…,mount_tag=slimN,security_model=mapped-xattr.
  Tags are kept short because 9p mount_tag has a ~31-byte limit.
- Kernel cmdline: the full destination path is passed as
  slim.mount=<tag>:<base64(path)> so the init script knows where to
  mount each tag.  Base64 avoids issues with spaces/special chars.
- slim-init.sh: after networking, parse slim.mount= entries, mkdir -p
  the destination, and mount -t 9p <tag> <dest> -o trans=virtio,version=9p2000.L

Tests verify: 9p share detection via sysfs mount_tag, 9p entry in
mount output, file content accessible at the expected path, and clean
VM exit.
This commit is contained in:
2026-09-10 00:18:38 +02:00
parent 5960cfe430
commit f931705971
3 changed files with 104 additions and 1 deletions
+29 -1
View File
@@ -8,7 +8,7 @@
use anyhow::{Context, Result, bail};
use base64::Engine;
use clap::Args;
use std::{fmt::Write as _, process::Command};
use std::{fmt::Write as _, path::PathBuf, process::Command};
use crate::{
forward::PortForward,
@@ -32,6 +32,11 @@ pub struct RunCmd {
/// cmdline as slim.cmd=<b64>). Overrides the CMD inferred at build time.
#[clap(long)]
cmd: Option<String>,
/// Share a host directory into the VM via 9p. The directory appears at
/// the same path inside the VM. Can be repeated for multiple mounts.
#[clap(long)]
mount: Vec<PathBuf>,
}
pub(crate) fn run(
@@ -40,6 +45,7 @@ pub(crate) fn run(
memory,
forward,
cmd,
mount,
}: RunCmd,
) -> Result<()> {
let reg_dir = registry_dir(&name)?;
@@ -81,6 +87,27 @@ pub(crate) fn run(
cmdline.push(format!("slim.cmd={encoded}"));
}
// Build 9p shares for each --mount. Tags are short (slim0, slim1, …)
// because 9p mount_tag has a ~31-byte limit. The full destination path
// is passed on the kernel cmdline as slim.mount=<tag>:<base64(path)>.
let mut virtfs_args: Vec<String> = Vec::new();
for (i, host_path) in mount.iter().enumerate() {
let canonical = host_path
.canonicalize()
.with_context(|| format!("Cannot resolve mount path '{}'", host_path.display()))?;
let path_str = canonical
.to_str()
.context("Mount path is not valid UTF-8")?;
let tag = format!("slim{i}");
let dest_b64 = base64::engine::general_purpose::STANDARD.encode(path_str.as_bytes());
virtfs_args.push("-virtfs".into());
virtfs_args.push(format!(
"local,path={path_str},mount_tag={tag},security_model=mapped-xattr"
));
cmdline.push(format!("slim.mount={tag}:{dest_b64}"));
}
println!("Booting {name} from registry: {}", reg_dir.display());
let cmdline = cmdline.join(" ");
@@ -101,6 +128,7 @@ pub(crate) fn run(
.arg("-kernel")
.arg(vmlinuz_path.as_os_str())
.args(&fs_args)
.args(&virtfs_args)
.args(["-snapshot"])
.args(["-no-reboot"])
.args(["-append", &cmdline])