//! **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)");
}