1pub fn detect_zpool() -> Vec<String> {
15 let output = std::process::Command::new("zpool")
16 .args(["list", "-H", "-p", "-o", "name,size,alloc,health"])
17 .output();
18
19 let Ok(output) = output else {
20 return Vec::new();
21 };
22 if !output.status.success() {
23 return Vec::new();
24 }
25
26 parse_zpool_list(&String::from_utf8_lossy(&output.stdout))
27}
28
29pub fn parse_zpool_list(text: &str) -> Vec<String> {
31 let mut pools = Vec::new();
32
33 for line in text.lines() {
34 let fields: Vec<&str> = line.split('\t').collect();
35 if fields.len() < 4 {
36 continue;
37 }
38 let name = fields[0];
39 let Ok(size) = fields[1].parse::<u64>() else {
40 continue;
41 };
42 let Ok(alloc) = fields[2].parse::<u64>() else {
43 continue;
44 };
45 let health = fields[3];
46
47 let size_gb = size as f64 / 1_073_741_824.0;
48 let alloc_gb = alloc as f64 / 1_073_741_824.0;
49
50 pools.push(format!(
51 "{}: {:.1} / {:.1} GB ({})",
52 name, alloc_gb, size_gb, health
53 ));
54 }
55
56 pools
57}
58
59#[cfg(test)]
60mod tests {
61 use super::parse_zpool_list;
62
63 #[test]
64 fn test_parse_zpool_list_single() {
65 let text = "tank\t1073741824000\t536870912000\tONLINE\n";
66 let pools = parse_zpool_list(text);
67 assert_eq!(pools, vec!["tank: 500.0 / 1000.0 GB (ONLINE)"]);
68 }
69
70 #[test]
71 fn test_parse_zpool_list_multi() {
72 let text = "tank\t1073741824000\t536870912000\tONLINE\nbackup\t2147483648000\t107374182400\tDEGRADED\n";
73 let pools = parse_zpool_list(text);
74 assert_eq!(pools.len(), 2);
75 assert_eq!(pools[0], "tank: 500.0 / 1000.0 GB (ONLINE)");
76 assert_eq!(pools[1], "backup: 100.0 / 2000.0 GB (DEGRADED)");
77 }
78
79 #[test]
80 fn test_parse_zpool_list_empty() {
81 assert!(parse_zpool_list("").is_empty());
82 }
83
84 #[test]
85 fn test_parse_zpool_list_malformed_line_skipped() {
86 let text = "tank\tnot-a-number\t536870912000\tONLINE\n";
87 assert!(parse_zpool_list(text).is_empty());
88 }
89}