Support host:guest mount syntax with WORKINGDIR resolution
--mount now accepts <host-path>:<guest-path> (Docker-style). When the guest path is omitted, the host path is used. Non-absolute guest paths are resolved against the image WORKINGDIR, which is written to /slim/workdir at build time and read by slim-init.sh at boot. Changes: - qemu.rs: Parse host:guest spec, pass guest path (not host path) on the kernel cmdline as slim.mount=<tag>:<base64(guest_path)> - inject.rs: Accept working_dir param, write /slim/workdir into rootfs - build.rs: Pass config.working_dir to inject() - slim-init.sh: Read /slim/workdir, resolve relative guest paths against it before mounting - test.sh: Test host:guest absolute paths (multi-mount) and relative guest path resolved against WORKINGDIR Addresses PR #9 review comment from @hulthe.
This commit was merged in pull request #9.
This commit is contained in:
+6
-1
@@ -78,7 +78,12 @@ fn build_inner(
|
|||||||
let command = cmd.unwrap_or_else(|| inject::infer_command(&config));
|
let command = cmd.unwrap_or_else(|| inject::infer_command(&config));
|
||||||
let exec_script =
|
let exec_script =
|
||||||
inject::build_exec_script(&command, &config.env, config.working_dir.as_deref());
|
inject::build_exec_script(&command, &config.env, config.working_dir.as_deref());
|
||||||
inject::inject(mount_path, &exec_script, config.user.as_deref())?;
|
inject::inject(
|
||||||
|
mount_path,
|
||||||
|
&exec_script,
|
||||||
|
config.user.as_deref(),
|
||||||
|
config.working_dir.as_deref(),
|
||||||
|
)?;
|
||||||
println!("Injected /slim/ (init + exec)");
|
println!("Injected /slim/ (init + exec)");
|
||||||
|
|
||||||
match kind {
|
match kind {
|
||||||
|
|||||||
+12
-3
@@ -100,9 +100,14 @@ fn install_into_rootfs(mount: &str, name: &str, mode: &str, content: &str) -> Re
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inject /slim/init, /slim/exec, and optionally /slim/user into a mounted
|
/// Inject /slim/init, /slim/exec, and optionally /slim/user and /slim/workdir
|
||||||
/// container image rootfs.
|
/// into a mounted container image rootfs.
|
||||||
pub fn inject(mount_path: &Path, exec_script: &str, user: Option<&str>) -> Result<()> {
|
pub fn inject(
|
||||||
|
mount_path: &Path,
|
||||||
|
exec_script: &str,
|
||||||
|
user: Option<&str>,
|
||||||
|
working_dir: Option<&str>,
|
||||||
|
) -> Result<()> {
|
||||||
let mount_str = mount_path.to_str().context("mount path is not UTF-8")?;
|
let mount_str = mount_path.to_str().context("mount path is not UTF-8")?;
|
||||||
|
|
||||||
install_into_rootfs(mount_str, "init", "755", SLIM_INIT)?;
|
install_into_rootfs(mount_str, "init", "755", SLIM_INIT)?;
|
||||||
@@ -112,5 +117,9 @@ pub fn inject(mount_path: &Path, exec_script: &str, user: Option<&str>) -> Resul
|
|||||||
install_into_rootfs(mount_str, "user", "644", user)?;
|
install_into_rootfs(mount_str, "user", "644", user)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(dir) = working_dir.filter(|d| !d.is_empty()) {
|
||||||
|
install_into_rootfs(mount_str, "workdir", "644", dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-13
@@ -8,7 +8,7 @@
|
|||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use std::{fmt::Write as _, path::PathBuf, process::Command};
|
use std::{fmt::Write as _, process::Command};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
forward::PortForward,
|
forward::PortForward,
|
||||||
@@ -33,10 +33,12 @@ pub struct RunCmd {
|
|||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
cmd: Option<String>,
|
cmd: Option<String>,
|
||||||
|
|
||||||
/// Share a host directory into the VM via 9p. The directory appears at
|
/// Share a host directory into the VM via 9p. Format:
|
||||||
/// the same path inside the VM. Can be repeated for multiple mounts.
|
/// `<host-path>` or `<host-path>:<guest-path>`. When the guest path is
|
||||||
|
/// omitted, the host path is used. Non-absolute guest paths are
|
||||||
|
/// relative to the image's WORKINGDIR. Can be repeated.
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
mount: Vec<PathBuf>,
|
mount: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn run(
|
pub(crate) fn run(
|
||||||
@@ -88,25 +90,30 @@ pub(crate) fn run(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build 9p shares for each --mount. Tags are short (slim0, slim1, …)
|
// Build 9p shares for each --mount. Tags are short (slim0, slim1, …)
|
||||||
// because 9p mount_tag has a ~31-byte limit. The full destination path
|
// because 9p mount_tag has a ~31-byte limit. The guest destination path
|
||||||
// is passed on the kernel cmdline as slim.mount=<tag>:<base64(path)>.
|
// is passed on the kernel cmdline as slim.mount=<tag>:<base64(guest_path)>.
|
||||||
|
// If no guest path is specified, the host path is used as the guest path.
|
||||||
let mut virtfs_args: Vec<String> = Vec::new();
|
let mut virtfs_args: Vec<String> = Vec::new();
|
||||||
for (i, host_path) in mount.iter().enumerate() {
|
for (i, spec) in mount.iter().enumerate() {
|
||||||
let canonical = host_path
|
let (host_path, guest_path) = match spec.split_once(':') {
|
||||||
|
Some((h, g)) => (h, g),
|
||||||
|
None => (spec.as_str(), spec.as_str()),
|
||||||
|
};
|
||||||
|
let canonical = std::path::Path::new(host_path)
|
||||||
.canonicalize()
|
.canonicalize()
|
||||||
.with_context(|| format!("Cannot resolve mount path '{}'", host_path.display()))?;
|
.with_context(|| format!("Cannot resolve mount path '{host_path}'"))?;
|
||||||
let path_str = canonical
|
let host_str = canonical
|
||||||
.to_str()
|
.to_str()
|
||||||
.context("Mount path is not valid UTF-8")?;
|
.context("Mount path is not valid UTF-8")?;
|
||||||
let tag = format!("slim{i}");
|
let tag = format!("slim{i}");
|
||||||
let dest_b64 = base64::engine::general_purpose::STANDARD.encode(path_str.as_bytes());
|
let dest_b64 = base64::engine::general_purpose::STANDARD.encode(guest_path.as_bytes());
|
||||||
|
|
||||||
// QEMU's QemuOpts splits on commas — escape literal commas in the
|
// QEMU's QemuOpts splits on commas — escape literal commas in the
|
||||||
// path as ",," per QEMU convention.
|
// path as ",," per QEMU convention.
|
||||||
let path_escaped = path_str.replace(',', ",,");
|
let host_escaped = host_str.replace(',', ",,");
|
||||||
virtfs_args.push("-virtfs".into());
|
virtfs_args.push("-virtfs".into());
|
||||||
virtfs_args.push(format!(
|
virtfs_args.push(format!(
|
||||||
"local,path={path_escaped},mount_tag={tag},security_model=mapped-xattr"
|
"local,path={host_escaped},mount_tag={tag},security_model=mapped-xattr"
|
||||||
));
|
));
|
||||||
cmdline.push(format!("slim.mount={tag}:{dest_b64}"));
|
cmdline.push(format!("slim.mount={tag}:{dest_b64}"));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user