use serde::Deserialize; #[derive(Deserialize)] struct SmartJson { smart_status: SmartStatus, temperature: Option, ata_smart_attributes: Option, nvme_smart_health_information_log: Option, } #[derive(Deserialize)] struct SmartStatus { passed: bool } #[derive(Deserialize)] struct SmartTemp { current: i64 } #[derive(Deserialize)] struct SmartAttrs { table: Vec } #[derive(Deserialize)] struct SmartAttr { id: u8, raw: SmartAttrRaw, value: Option, } #[derive(Deserialize)] struct SmartAttrRaw { value: i64 } #[derive(Deserialize)] struct NvmeHealth { percentage_used: Option, temperature: Option, } pub fn is_available() -> bool { std::process::Command::new("which") .arg("smartctl") .output() .map(|o| o.status.success()) .unwrap_or(false) } pub fn parse_json(json: &str) -> Result { let s: SmartJson = serde_json::from_str(json)?; let temperature = s.temperature.as_ref().map(|t| t.current) .or_else(|| s.nvme_smart_health_information_log.as_ref()?.temperature); let mut reallocated = None; let mut power_hours = None; let mut wear = None; if let Some(attrs) = &s.ata_smart_attributes { for attr in &attrs.table { match attr.id { 5 => reallocated = Some(attr.raw.value), 9 => power_hours = Some(attr.raw.value), 177 => wear = attr.value, _ => {} } } } if wear.is_none() { if let Some(nvme) = &s.nvme_smart_health_information_log { if let Some(pct) = nvme.percentage_used { wear = Some(100 - pct); } } } Ok(crate::payload::SmartMetrics { device: String::new(), passed: s.smart_status.passed, temperature, reallocated_sectors: reallocated, power_on_hours: power_hours, wear_level: wear, }) } pub fn collect() -> Option> { if !is_available() { return None; } let mut devs: Vec = std::fs::read_dir("/sys/block") .into_iter() .flatten() .flatten() .map(|e| e.file_name().into_string().unwrap_or_default()) .filter_map(|n| { if n.starts_with("sd") { Some(format!("/dev/{}", n)) } else if n.starts_with("nvme") && !n.contains('n') { // /sys/block contient nvme0n1 (namespace) — on utilise le contrôleur nvme0 Some(format!("/dev/{}", n)) } else if n.starts_with("nvme") && n.contains('n') { // Déduit le contrôleur depuis nvme0n1 → nvme0 let ctrl = n.split('n').next()?; Some(format!("/dev/{}", ctrl)) } else { None } }) .collect::>() .into_iter() .collect(); devs.sort(); let mut results = Vec::new(); for dev in &devs { let Ok(output) = std::process::Command::new("smartctl") .args(["-a", "-j", dev]) .output() else { continue }; let json = String::from_utf8_lossy(&output.stdout); if let Ok(metrics) = parse_json(&json) { results.push(crate::payload::SmartMetrics { device: dev.trim_start_matches("/dev/").to_string(), ..metrics }); } } if results.is_empty() { None } else { Some(results) } }