1use libnvme::{Controller, Namespace, Root};
4
5fn main() -> Result<(), Box<dyn std::error::Error>> {
6 let root = Root::scan()?;
7
8 let mut rows: Vec<Row> = Vec::new();
9 for host in root.hosts() {
10 for subsys in host.subsystems() {
11 for ctrl in subsys.controllers() {
12 let mut had_namespace = false;
13 for ns in ctrl.namespaces() {
14 rows.push(Row::from(&ctrl, Some(&ns)));
15 had_namespace = true;
16 }
17 if !had_namespace {
18 rows.push(Row::from(&ctrl, None));
19 }
20 }
21 }
22 }
23
24 if rows.is_empty() {
25 println!("(no NVMe devices found)");
26 return Ok(());
27 }
28
29 print_table(&rows);
30 Ok(())
31}
32
33struct Row {
34 node: String,
35 model: String,
36 serial: String,
37 firmware: String,
38 transport: String,
39 address: String,
40 nsid: String,
41 size: String,
42}
43
44impl Row {
45 fn from(ctrl: &Controller<'_>, ns: Option<&Namespace<'_>>) -> Self {
46 Row {
47 node: ns
48 .and_then(|n| n.name().ok().map(|s| format!("/dev/{s}")))
49 .unwrap_or_else(|| {
50 ctrl.name()
51 .ok()
52 .map(|s| format!("/dev/{s}"))
53 .unwrap_or_default()
54 }),
55 model: ctrl.model().unwrap_or("").to_string(),
56 serial: ctrl.serial().unwrap_or("").to_string(),
57 firmware: ctrl.firmware().unwrap_or("").to_string(),
58 transport: ctrl.transport().unwrap_or("").to_string(),
59 address: ctrl.address().unwrap_or("").to_string(),
60 nsid: ns.map(|n| n.nsid().to_string()).unwrap_or_default(),
61 size: ns.map(|n| format_bytes(n.size_bytes())).unwrap_or_default(),
62 }
63 }
64}
65
66fn format_bytes(bytes: u64) -> String {
67 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB", "PB"];
68 let mut value = bytes as f64;
69 let mut unit_idx = 0;
70 while value >= 1000.0 && unit_idx + 1 < UNITS.len() {
71 value /= 1000.0;
72 unit_idx += 1;
73 }
74 if unit_idx == 0 {
75 format!("{bytes} {}", UNITS[0])
76 } else {
77 format!("{value:.2} {}", UNITS[unit_idx])
78 }
79}
80
81fn print_table(rows: &[Row]) {
82 let headers = [
83 "Node",
84 "NSID",
85 "Model",
86 "Serial",
87 "FW",
88 "Size",
89 "Transport",
90 "Address",
91 ];
92 let widths: Vec<usize> = (0..headers.len())
93 .map(|i| {
94 let max_data = rows.iter().map(|r| field(r, i).len()).max().unwrap_or(0);
95 std::cmp::max(headers[i].len(), max_data)
96 })
97 .collect();
98
99 for (i, header) in headers.iter().enumerate() {
100 print!("{:<width$} ", header, width = widths[i]);
101 }
102 println!();
103 for w in &widths {
104 print!("{:-<width$} ", "", width = *w);
105 }
106 println!();
107
108 for row in rows {
109 for (i, w) in widths.iter().enumerate() {
110 print!("{:<width$} ", field(row, i), width = *w);
111 }
112 println!();
113 }
114}
115
116fn field(row: &Row, idx: usize) -> &str {
117 match idx {
118 0 => &row.node,
119 1 => &row.nsid,
120 2 => &row.model,
121 3 => &row.serial,
122 4 => &row.firmware,
123 5 => &row.size,
124 6 => &row.transport,
125 7 => &row.address,
126 _ => "",
127 }
128}