fix: --kernel =PATH syntax, exit-code cleanup, bind warning in VM mode
- --kernel took an optional value greedily, swallowing the DISTRO positional: 'ecr --kernel alpine' failed with a missing-argument error and 'ecr --kernel alpine -- cmd' pulled a garbage OCI ref. require =PATH syntax for the kernel path instead. - setup_namespaces returns the child's exit code (or 128+signal) instead of calling process::exit, so the extracted rootfs tempdir is cleaned up before exiting instead of leaking into /tmp. - warn that --bind/--bind-rw are ignored when booting with --kernel.
This commit is contained in:
+11
-3
@@ -33,12 +33,20 @@ pub struct Args {
|
||||
#[arg(short = 'v', long)]
|
||||
pub verbose: bool,
|
||||
|
||||
/// Boot with QEMU system emulation (optionally specify kernel path, or omit to download default)
|
||||
/// Boot with QEMU system emulation (optionally specify kernel path with =PATH, or omit to download default)
|
||||
///
|
||||
/// Examples:
|
||||
/// --kernel Download and use the default Alpine linux-virt kernel
|
||||
/// --kernel ./vmlinuz Use a specific kernel file
|
||||
#[arg(long, value_name = "KERNEL_PATH", num_args = 0..=1)]
|
||||
/// --kernel=./vmlinuz Use a specific kernel file
|
||||
///
|
||||
/// The kernel path must use `=` syntax: with `--kernel ./vmlinuz` the
|
||||
/// path would be parsed as the DISTRO argument.
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "KERNEL_PATH",
|
||||
num_args = 0..=1,
|
||||
require_equals = true
|
||||
)]
|
||||
pub kernel: Option<Option<PathBuf>>,
|
||||
|
||||
/// Memory size for QEMU VM (only used with --kernel, e.g., 512M, 2G)
|
||||
|
||||
+21
-5
@@ -130,11 +130,20 @@ fn main() -> Result<()> {
|
||||
extract_tarball(&cache_path, &rootfs)?;
|
||||
|
||||
// Branch based on --kernel flag
|
||||
// Option<Option<PathBuf>>:
|
||||
// Option<Option<PathBuf>> (require_equals: the value must use =PATH syntax
|
||||
// so it can never swallow the DISTRO positional):
|
||||
// None -> --kernel not specified, use namespace mode
|
||||
// Some(None) -> --kernel without path, download default kernel
|
||||
// Some(Some(path)) -> --kernel /path/to/vmlinuz, use provided kernel
|
||||
// Some(Some(path)) -> --kernel=/path/to/vmlinuz, use provided kernel
|
||||
if let Some(kernel_opt) = &args.kernel {
|
||||
// VM mode boots an initramfs: host bind mounts are never applied
|
||||
if !args.bind.is_empty() || !args.bind_rw.is_empty() {
|
||||
eprintln!(
|
||||
"Warning: --bind/--bind-rw are ignored with --kernel \
|
||||
(the VM boots from an initramfs, no host directories are mounted)"
|
||||
);
|
||||
}
|
||||
|
||||
// QEMU system mode
|
||||
let kernel_path = match kernel_opt {
|
||||
Some(path) => {
|
||||
@@ -169,12 +178,19 @@ fn main() -> Result<()> {
|
||||
result
|
||||
} else {
|
||||
// Namespace/chroot mode
|
||||
namespace_mode(args, rootfs, config)
|
||||
let exit_code = namespace_mode(args, rootfs, config)?;
|
||||
// Propagate the command's exit code, cleaning up the extracted rootfs
|
||||
// first: process::exit does not run destructors.
|
||||
drop(temp_dir);
|
||||
if exit_code != 0 {
|
||||
std::process::exit(exit_code);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Run in namespace/chroot mode
|
||||
fn namespace_mode(args: Args, rootfs: std::path::PathBuf, config: Config) -> Result<()> {
|
||||
/// Run in namespace/chroot mode, returning the command's exit code
|
||||
fn namespace_mode(args: Args, rootfs: std::path::PathBuf, config: Config) -> Result<i32> {
|
||||
// Check user namespace availability
|
||||
namespace::check_user_namespace()?;
|
||||
|
||||
|
||||
+8
-8
@@ -58,8 +58,10 @@ pub fn check_user_namespace() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Setup namespaces and run the provided function inside them
|
||||
pub fn setup_namespaces<F>(f: F) -> Result<()>
|
||||
/// Setup namespaces and run the provided function inside them.
|
||||
/// Returns the child's exit code (0 on success, 128+signal when killed by a
|
||||
/// signal). Setup failures are returned as Err.
|
||||
pub fn setup_namespaces<F>(f: F) -> Result<i32>
|
||||
where
|
||||
F: FnOnce() -> Result<()> + Send + 'static,
|
||||
{
|
||||
@@ -222,20 +224,18 @@ where
|
||||
let status = nix::sys::wait::waitpid(pid, None)?;
|
||||
|
||||
match status {
|
||||
nix::sys::wait::WaitStatus::Exited(_, 0) => Ok(()),
|
||||
nix::sys::wait::WaitStatus::Exited(_, 0) => Ok(0),
|
||||
nix::sys::wait::WaitStatus::Exited(_, code) => {
|
||||
// If the child reported an error (e.g., setup failure), return it.
|
||||
// Otherwise, just forward the exit code without an error message.
|
||||
if let Some(msg) = child_error {
|
||||
Err(anyhow!("{}", msg))
|
||||
} else {
|
||||
std::process::exit(code);
|
||||
Ok(code)
|
||||
}
|
||||
}
|
||||
nix::sys::wait::WaitStatus::Signaled(_, sig, _) => {
|
||||
Err(anyhow!("Child process killed by signal {:?}", sig))
|
||||
}
|
||||
_ => Ok(()),
|
||||
nix::sys::wait::WaitStatus::Signaled(_, sig, _) => Ok(128 + sig as i32),
|
||||
_ => Ok(0),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user