Gunnar

mikronova/draupnir

examples/two_shapes.rs

//! **One medium, two machines** — the proof that a guest shape is a real choice
//! and that a run whose only evidence channel is the framebuffer still yields
//! evidence.
//!
//! Boots the SAME ISO twice through draupnir's KVM backend:
//!
//! 1. [`GuestShape::UefiSerial`] — UEFI + `-serial mon:stdio`. Evidence: the
//!    captured serial console (`KvmBoot::serial_log`).
//! 2. [`GuestShape::BiosFramebuffer`] — SeaBIOS + `-serial none` + `-vga std`.
//!    Evidence: `KvmBoot::screendump` over the HMP socket. The serial log is
//!    EMPTY by construction, and the boot status is `Unobservable` rather than a
//!    timeout dressed up as a failure.
//!
//! ```text
//! cargo run --example two_shapes --features backend-tunnr -- <iso> <outdir>
//! ```
//!
//! Nothing here asserts a PASS: it prints what each channel carried, and an empty
//! channel prints as empty. That is the whole point — a proof that ran on the
//! wrong machine and said PASS is worse than no proof.

#[cfg(feature = "backend-tunnr")]
fn main() {
    use draupnir::kvm::KvmBoot;
    use draupnir::{Boot, BootSpec, GuestShape, Lifecycle};
    use std::path::PathBuf;
    use std::time::Duration;

    let mut args = std::env::args().skip(1);
    let iso = args.next().expect("usage: two_shapes <iso> <outdir>");
    let out = PathBuf::from(args.next().expect("usage: two_shapes <iso> <outdir>"));
    std::fs::create_dir_all(&out).unwrap();
    let dwell: u64 = std::env::var("TWO_SHAPES_DWELL_SECS")
        .ok()
        .and_then(|s| s.parse().ok())
        .unwrap_or(40);

    // ── 1. UEFI + serial console ────────────────────────────────────────────
    let backend = KvmBoot::new();
    let spec = BootSpec::iso_boot("shape-uefi-serial", &iso)
        .with_guest_shape(GuestShape::UefiSerial)
        .with_success_marker("INSTALLER:")
        .with_boot_timeout(Duration::from_secs(dwell + 30));
    let m = backend.boot(&spec).expect("uefi+serial boot");
    std::thread::sleep(Duration::from_secs(dwell));
    let serial = backend.serial_log(&m).unwrap_or_default();
    println!("== UefiSerial: serial bytes = {}", serial.len());
    for line in serial.lines().take(60) {
        println!("   serial| {line}");
    }
    println!("== UefiSerial: booted_ok = {}", backend.booted_ok(&m));
    println!(
        "== UefiSerial: screendump = {:?}",
        backend.screendump(&m, &out.join("uefi-serial.ppm"))
    );
    let _ = backend.power_off(&m);

    // ── 2. SeaBIOS + no serial + framebuffer ────────────────────────────────
    let backend = KvmBoot::new();
    let spec = BootSpec::iso_boot("shape-bios-fb", &iso)
        .with_guest_shape(GuestShape::BiosFramebuffer)
        .with_boot_timeout(Duration::from_secs(dwell + 30));
    let m = backend.boot(&spec).expect("seabios+framebuffer boot");
    std::thread::sleep(Duration::from_secs(dwell));
    let serial = backend.serial_log(&m).unwrap_or_default();
    println!("== BiosFramebuffer: serial bytes = {}", serial.len());
    println!(
        "== BiosFramebuffer: booted_ok = {} (no wire can ever carry a marker)",
        backend.booted_ok(&m)
    );
    println!(
        "== BiosFramebuffer: exit_reason = {:?}",
        backend.exit_reason(&m)
    );
    for n in 0..3 {
        let p = out.join(format!("bios-fb-{n}.ppm"));
        println!("== BiosFramebuffer: screendump {n} = {:?}", backend.screendump(&m, &p));
        std::thread::sleep(Duration::from_secs(4));
    }
    let _ = backend.power_off(&m);

    // ── 3. The refusals, by name ────────────────────────────────────────────
    let refused = BootSpec::iso_boot("shape-refused-marker", &iso)
        .with_guest_shape(GuestShape::BiosFramebuffer)
        .with_success_marker("BOOT-OK");
    println!(
        "== refusal(marker on a console-less machine) = {:?}",
        KvmBoot::new().boot(&refused).err()
    );
    let refused = BootSpec::iso_boot("shape-refused-fw", &iso)
        .with_guest_shape(GuestShape::BiosFramebuffer)
        .with_firmware(draupnir::Firmware::SecureBoot);
    println!(
        "== refusal(secure boot on a BIOS machine) = {:?}",
        KvmBoot::new().boot(&refused).err()
    );
}

#[cfg(not(feature = "backend-tunnr"))]
fn main() {
    eprintln!("this example needs --features backend-tunnr (the live tunnr KVM wire)");
}