Skip to main content

io_smoke/
io_smoke.rs

1//! Smoke test for the v0.7 I/O command surface: write -> read -> compare
2//! -> verify -> write_zeroes -> dsm(deallocate) -> flush.
3//!
4//! **Refuses to run unless the controller's model string equals
5//! `QEMU NVMe Ctrl`.** Same safety latch as `format_smoke` — these commands
6//! mutate user data, so they must never touch a real disk.
7//!
8//! ```sh
9//! bash tests/qemu/run.sh
10//! # inside the guest:
11//! cd /mnt/host && CARGO_TARGET_DIR=/tmp/target sudo -E cargo run \
12//!     --example io_smoke -p libnvme
13//! ```
14
15use libnvme::{DsmAttr, DsmRange, Root};
16
17const QEMU_MODEL: &str = "QEMU NVMe Ctrl";
18
19fn main() -> Result<(), Box<dyn std::error::Error>> {
20    let root = Root::scan()?;
21    let mut exercised = 0;
22
23    for host in root.hosts() {
24        for subsys in host.subsystems() {
25            for ctrl in subsys.controllers() {
26                let model = ctrl.model().unwrap_or("?").trim();
27                if model != QEMU_MODEL {
28                    println!(
29                        "skipping {}: model {model:?} != {QEMU_MODEL:?} (refusing on real hardware)",
30                        ctrl.name()?
31                    );
32                    continue;
33                }
34
35                for ns in ctrl.namespaces() {
36                    let lba_size = ns.lba_size();
37                    println!(
38                        "exercising {} (NSID {}, {} B LBAs)",
39                        ns.name()?,
40                        ns.nsid(),
41                        lba_size
42                    );
43
44                    // ---- Write one LBA with a recognizable pattern -----
45                    let mut pattern = vec![0u8; lba_size as usize];
46                    for (i, b) in pattern.iter_mut().enumerate() {
47                        *b = (i & 0xFF) as u8;
48                    }
49                    ns.write(0, 1, &pattern).fua().execute()?;
50                    println!("  write ok");
51
52                    // ---- Read it back ---------------------------------
53                    let got = ns.read_to_vec(0, 1)?;
54                    assert_eq!(got, pattern, "read did not return the bytes we wrote");
55                    println!("  read ok (round-trip matches)");
56
57                    // ---- Compare (should succeed) ---------------------
58                    ns.compare(0, 1, &pattern).execute()?;
59                    println!("  compare ok");
60
61                    // ---- Verify (controller-side integrity check) -----
62                    ns.verify(0, 1).execute()?;
63                    println!("  verify ok");
64
65                    // ---- Write zeroes over the LBA --------------------
66                    ns.write_zeroes(0, 1).execute()?;
67                    let zeroed = ns.read_to_vec(0, 1)?;
68                    assert!(zeroed.iter().all(|&b| b == 0), "expected all-zero LBA");
69                    println!("  write_zeroes ok");
70
71                    // ---- DSM deallocate (TRIM) ------------------------
72                    ns.dsm(DsmAttr::DEALLOCATE)
73                        .ranges(&[DsmRange::new(0, 1)])
74                        .execute()?;
75                    println!("  dsm(deallocate) ok");
76
77                    // ---- Flush ----------------------------------------
78                    ns.flush()?;
79                    println!("  flush ok");
80
81                    exercised += 1;
82                }
83            }
84        }
85    }
86
87    if exercised == 0 {
88        println!("(no QEMU virtual NVMe controllers found)");
89    } else {
90        println!("\nall I/O commands exercised on {exercised} namespace(s)");
91    }
92    Ok(())
93}