diff --git a/src/main.rs b/src/main.rs index aca50d5..e9fed03 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ -use anyhow::{Context, Result}; +use anyhow::{Context, Result, bail}; use clap::{Parser, Subcommand}; +use flate2::write::GzEncoder; use std::fs::{self, File}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; #[derive(Parser, Debug)] -#[command(name = "slim")] #[command(about = "Build bootable initrd VMs from container images")] struct Cli { #[command(subcommand)] @@ -42,10 +42,15 @@ fn main() -> Result<()> { Ok(()) } -fn registry_dir(image: &str) -> PathBuf { +fn registry_dir(image: &str) -> Result { let base = xdg::BaseDirectories::with_prefix("slim-rs"); - let initrd_path = base.place_data_file(format!("registry/{}/initrd", image)); - initrd_path.unwrap().parent().unwrap().to_path_buf() + let initrd_path = base + .place_data_file(format!("registry/{}/initrd", image)) + .context("Failed to write to XDG_DATA_HOME")?; + Ok(initrd_path + .parent() + .expect("data file has a parent") + .to_owned()) } fn build(image: &str) -> Result<()> { @@ -75,7 +80,7 @@ fn mount_image(image: &str) -> Result { .context("Failed to run podman image mount")?; let mount_path = String::from_utf8_lossy(&output.stdout).trim().to_string(); if !output.status.success() || mount_path.is_empty() { - anyhow::bail!( + bail!( "podman image mount failed: {}", String::from_utf8_lossy(&output.stderr).trim() ); @@ -95,12 +100,10 @@ fn build_artifacts(image: &str, mount_path: &Path) -> Result<()> { .status() .context("Failed to check for /vmlinuz inside the image mount")?; if !has_kernel.success() { - anyhow::bail!( - "Image missing required /vmlinuz. Place kernel at /vmlinuz in Containerfile." - ); + bail!("Image missing required /vmlinuz. Place kernel at /vmlinuz in Containerfile."); } - let reg_dir = registry_dir(image); + let reg_dir = registry_dir(image)?; fs::create_dir_all(®_dir)?; println!("Registry: {}", reg_dir.display()); @@ -116,7 +119,7 @@ fn build_artifacts(image: &str, mount_path: &Path) -> Result<()> { .status() .context("Failed to copy vmlinuz out of the image mount")?; if !cp_status.success() { - anyhow::bail!("Failed to copy vmlinuz to registry"); + bail!("Failed to copy vmlinuz to registry"); } println!("Copied vmlinuz -> {}", vmlinuz_dst.display()); @@ -132,7 +135,7 @@ fn build_artifacts(image: &str, mount_path: &Path) -> Result<()> { .context("Failed to spawn the cpio pipeline")?; let initrd_file = File::create(&initrd_path).context("Failed to create initrd file")?; - let mut encoder = flate2::write::GzEncoder::new(initrd_file, flate2::Compression::default()); + let mut encoder = GzEncoder::new(initrd_file, flate2::Compression::default()); let cpio_stdout = cpio.stdout.take().context("cpio stdout was not piped")?; let copied = std::io::copy(&mut std::io::BufReader::new(cpio_stdout), &mut encoder); let stream_result = copied @@ -148,7 +151,7 @@ fn build_artifacts(image: &str, mount_path: &Path) -> Result<()> { } if !cpio_status.success() { let _ = fs::remove_file(&initrd_path); - anyhow::bail!("find/cpio pipeline failed with status {cpio_status}"); + bail!("find/cpio pipeline failed with status {cpio_status}"); } println!("Created initrd -> {}", initrd_path.display()); @@ -156,11 +159,11 @@ fn build_artifacts(image: &str, mount_path: &Path) -> Result<()> { } fn run(name: &str) -> Result<()> { - let reg_dir = registry_dir(name); + let reg_dir = registry_dir(name)?; let vmlinuz_path = reg_dir.join("vmlinuz"); let initrd_path = reg_dir.join("initrd"); if !vmlinuz_path.exists() || !initrd_path.exists() { - anyhow::bail!( + bail!( "Registry missing vmlinuz/initrd for '{}'. Run `slim build` first.", name ); @@ -171,34 +174,36 @@ fn run(name: &str) -> Result<()> { vmlinuz_path.display(), initrd_path.display() ); + + let cmdline = [ + "console=ttyS0,115200", + "root=/dev/ram0", + "rw", + "earlyprintk=serial", + "nokaslr", + "raid=noautodetect", + ] + .join(" "); + // Launch with a 5-second timeout for quick smoke verification - let status = Command::new("timeout") - .args([ - "5", - "qemu-system-x86_64", - "-m", - "256", - "-nographic", - "-kernel", - vmlinuz_path.to_str().unwrap(), - "-initrd", - initrd_path.to_str().unwrap(), - "-append", - "console=ttyS0", - ]) + let status = Command::new("qemu-system-x86_64") + .args(["-m", "1024M"]) + .arg("-kernel") + .arg(vmlinuz_path.as_os_str()) + .arg("-initrd") + .arg(initrd_path.as_os_str()) + .args(["-append", &cmdline]) + .args(["-nographic"]) .status() .context("Failed to launch qemu-system-x86_64")?; - // `timeout` exits 124 when it kills QEMU after the 5s smoke-test - // window; any other non-zero status is a real failure. match status.code() { Some(0) => println!("QEMU exited cleanly."), - Some(124) => println!("QEMU timed out after 5s (expected during smoke boot)."), Some(126) | Some(127) => { - anyhow::bail!("Failed to start qemu-system-x86_64 (is it installed and in PATH?)") + bail!("Failed to start qemu-system-x86_64 (is it installed and in PATH?)") } - Some(code) => anyhow::bail!("QEMU exited with code {code}"), - None => anyhow::bail!("QEMU terminated by a signal"), + Some(code) => bail!("QEMU exited with code {code}"), + None => bail!("QEMU terminated by a signal"), } Ok(()) }