supermachine 0.7.108

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.
Documentation
//! Validate the fix for supermachine-vm#14 (SMSNAP04b): a `with_volume` disk
//! whose guest path was ALREADY baked into the snapshot can have its backing
//! file swapped per fork at restore time, even though the device itself can't
//! be hot-attached. Bake a VM with a placeholder volume at `/data`, snapshot
//! it, then restore TWO independent forks from the SAME snapshot, each with
//! `VmConfig::with_volume` pointing `/data` at its own fresh backing file.
//! Confirms each fork sees only its own content (not the bake-time content,
//! not the sibling fork's) — the "dirty files packed into a tiny image,
//! attached as a second virtio-blk disk" transport from the company's
//! platform-architecture doc §5.

#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn main() {
    use std::time::Duration;
    use supermachine::vmm::resources::VolumeSpec;
    use supermachine::{Image, VmConfig};

    let dir = "/tmp/supermachine-vol-fork";
    let bake_backing = "/tmp/supermachine-vol-fork-bake.img";
    let fork_a_backing = "/tmp/supermachine-vol-fork-a.img";
    let fork_b_backing = "/tmp/supermachine-vol-fork-b.img";
    for p in [bake_backing, fork_a_backing, fork_b_backing] {
        let _ = std::fs::remove_file(p);
    }
    let _ = std::fs::remove_dir_all(dir);
    const SIZE: u64 = 64 * 1024 * 1024;

    eprintln!("=== BAKE: boot with a placeholder volume at /data, live-snapshot ===");
    let bake_cfg =
        VmConfig::new().with_volume(VolumeSpec::new(bake_backing, "/data").with_size_bytes(SIZE));
    let image = Image::from_oci("alpine").expect("from_oci");
    let vm = image.start(&bake_cfg).expect("bake start");
    std::thread::sleep(Duration::from_millis(5000));
    let baked = vm
        .exec_builder()
        .argv(["/bin/sh", "-c", "mount | grep -c ' /data '"])
        .output()
        .expect("bake exec");
    eprintln!(
        "bake: mounted={:?}",
        String::from_utf8_lossy(&baked.stdout).trim_end()
    );
    let snap = vm.snapshot_live(dir).expect("snapshot_live");
    vm.stop().expect("bake stop");

    // Two independent forks of the SAME snapshot, each pointing the already-baked
    // `/data` device at its own fresh (not-yet-existing) backing file — restore
    // formats it ext4, so each fork starts from a BLANK volume, isolated from the
    // other fork and from whatever the bake wrote (nothing, here).
    let run_fork = |label: &str, backing: &str, marker: &str| -> (bool, String) {
        eprintln!("=== FORK {label}: restore with VmConfig::with_volume -> {backing} ===");
        let cfg =
            VmConfig::new().with_volume(VolumeSpec::new(backing, "/data").with_size_bytes(SIZE));
        let vm = snap.start(&cfg).expect("fork start");
        std::thread::sleep(Duration::from_millis(3000));
        let w = vm
            .exec_builder()
            .argv([
                "/bin/sh",
                "-c",
                &format!(
                    "M=$(mount | grep -c ' /data '); \
                     echo {marker} > /data/who; sync; \
                     P=$(cat /data/who 2>/dev/null); echo mounted=$M who=$P"
                ),
            ])
            .output()
            .expect("fork exec");
        let got = String::from_utf8_lossy(&w.stdout).trim_end().to_string();
        eprintln!("fork {label}: success={} {got:?}", w.success());
        vm.stop().ok();
        (
            w.success() && got.contains("mounted=1") && got.contains(&format!("who={marker}")),
            got,
        )
    };

    // Each fork's guest wrote (and read back) its OWN marker through its OWN
    // `/dev/vdb`, backed by a DISTINCT host file passed via VmConfig — this is
    // exactly the bug from #14: before the fix, `mounted=1` would never be
    // true here (restore ignored config.volumes entirely, so the fork saw
    // only vda). Restored volumes are intentionally mapped MAP_PRIVATE (guest
    // writes are ephemeral CoW, see `VirtioBlk::open_rw_private`), so the
    // host backing FILE never carries the guest's write — proof lives in the
    // guest-side read-back, not the host file's bytes.
    let (a_ok, _a_got) = run_fork("A", fork_a_backing, "fork-a");
    let (b_ok, _b_got) = run_fork("B", fork_b_backing, "fork-b");

    let pass = a_ok && b_ok;
    eprintln!(
        "=== VOLUME-FORK-SWAP: {} (fork_a={a_ok} fork_b={b_ok}) ===",
        if pass { "PASS" } else { "FAIL" }
    );
    assert!(pass, "per-fork volume backing swap on restore failed");
}

#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
fn main() {
    eprintln!("kvm_volume_fork is Linux/x86_64 only");
}