supermachine 0.4.4

Run any OCI/Docker image as a hardware-isolated microVM on macOS HVF (Linux KVM and Windows WHP in progress). Single library API, zero flags for the common case, sub-100 ms cold-restore from snapshot.
//! Run a process inside a baked guest via `Vm::exec`.
//!
//! ```sh
//! cargo supermachine build --release --example exec
//! target/release/examples/exec nginx:1.27-alpine -- /bin/sh -c 'echo from-guest && nginx -v 2>&1'
//! ```

use std::io::Read;

use supermachine::{Image, Vm, VmConfig};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut args = std::env::args().skip(1);
    let image_ref = args
        .next()
        .ok_or("usage: exec IMAGE [-- argv...]")?;
    // Skip a literal `--` separator if present.
    let mut argv: Vec<String> = args.collect();
    if argv.first().map(|s| s.as_str()) == Some("--") {
        argv.remove(0);
    }
    if argv.is_empty() {
        argv = vec!["/bin/sh".to_owned(), "-c".to_owned(), "echo hello && exit 0".to_owned()];
    }

    let image = Image::from_oci(&image_ref)?;
    let vm = Vm::start(&image, &VmConfig::new())?;

    let mut child = vm.exec_builder().argv(argv).spawn()?;

    // Drain stdout/stderr concurrently.
    let mut stdout = child.stdout().expect("stdout taken");
    let mut stderr = child.stderr().expect("stderr taken");

    let so = std::thread::spawn(move || {
        let mut buf = Vec::new();
        let _ = stdout.read_to_end(&mut buf);
        buf
    });
    let se = std::thread::spawn(move || {
        let mut buf = Vec::new();
        let _ = stderr.read_to_end(&mut buf);
        buf
    });

    let status = child.wait()?;
    let stdout_bytes = so.join().unwrap();
    let stderr_bytes = se.join().unwrap();

    print!("{}", String::from_utf8_lossy(&stdout_bytes));
    if !stderr_bytes.is_empty() {
        eprint!("{}", String::from_utf8_lossy(&stderr_bytes));
    }
    eprintln!("exit: {:?}", status.code());

    vm.stop()?;
    Ok(())
}