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}