#![cfg(target_os = "linux")]
use crate::{DiskInfo, DiskInfoProvider, ProviderError};
use serde::Deserialize;
use std::process::Command;
#[derive(Deserialize)]
struct LsblkDevice {
name: String,
model: Option<String>,
serial: Option<String>,
#[serde(rename = "type")]
device_type: String,
#[serde(rename = "rm")]
removable: bool,
hotplug: bool,
}
#[derive(Deserialize)]
struct LsblkOutput {
blockdevices: Vec<LsblkDevice>,
}
pub struct LinuxProvider;
impl DiskInfoProvider for LinuxProvider {
fn get_all_disks() -> Result<Vec<DiskInfo>, ProviderError> {
let output = Command::new("lsblk")
.args(["-o", "NAME,MODEL,SERIAL,TYPE,RM,HOTPLUG", "-J", "-b", "-d"]) .output()?;
if !output.status.success() {
return Err(ProviderError::CommandUnsuccessful(
String::from_utf8_lossy(&output.stderr).into(),
));
}
let json_str = String::from_utf8_lossy(&output.stdout);
let parsed: LsblkOutput = serde_json::from_str(&json_str)?;
let disks = parsed
.blockdevices
.into_iter()
.filter(|dev| dev.device_type == "disk") .map(|dev| DiskInfo {
name: dev.name.trim().to_string(),
model: dev.model.and_then(|m| {
if m.trim().is_empty() {
None
} else {
Some(m.trim().to_string())
}
}),
serial_number: dev.serial.and_then(|s| {
if s.trim().is_empty() {
None
} else {
Some(s.trim().to_string())
}
}),
removable: Some(dev.removable || dev.hotplug),
})
.collect();
Ok(disks)
}
}