use std::process::Command;
use crate::device::{DeviceDescriptor, MountPoint};
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct Devices {
blockdevices: Vec<Device>,
}
#[derive(Deserialize, Debug)]
struct Device {
size: Option<u64>,
#[serde(default = "Device::name_default")]
kname: String,
#[serde(default = "Device::name_default")]
name: String,
tran: Option<String>,
subsystems: String,
ro: bool,
#[serde(rename = "phy-sec")]
phy_sec: u32,
#[serde(rename = "log-sec")]
log_sec: u32,
rm: bool,
ptype: Option<String>,
#[serde(default)]
children: Vec<Child>,
label: Option<String>,
vendor: Option<String>,
model: Option<String>,
hotplug: bool,
}
impl Device {
fn name_default() -> String {
"NO_NAME".to_string()
}
fn is_scsi(&self) -> bool {
self.subsystems.contains("sata")
|| self.subsystems.contains("scsi")
|| self.subsystems.contains("ata")
|| self.subsystems.contains("ide")
|| self.subsystems.contains("pci")
}
fn description(&self) -> String {
[
self.label.as_deref().unwrap_or_default(),
self.vendor.as_deref().unwrap_or_default(),
self.model.as_deref().unwrap_or_default(),
]
.into_iter()
.filter(|x| !x.is_empty())
.fold(String::new(), |mut acc, x| {
acc.push_str(x);
acc
})
}
fn is_virtual(&self) -> bool {
!self.subsystems.contains("block")
}
fn is_removable(&self) -> bool {
self.rm || self.hotplug || self.is_virtual()
}
fn is_system(&self) -> bool {
!(self.is_removable() || self.is_virtual())
}
}
impl From<Device> for DeviceDescriptor {
fn from(value: Device) -> Self {
let is_scsi = value.is_scsi();
let description = value.description();
let is_virtual = value.is_virtual();
let is_removable = value.is_removable();
let is_system = value.is_system();
Self {
enumerator: "lsblk:json".to_string(),
bus_type: Some(value.tran.as_deref().unwrap_or("UNKNOWN").to_uppercase()),
device: value.name,
raw: value.kname,
is_virtual,
is_scsi,
is_usb: value.subsystems.contains("usb"),
is_readonly: value.ro,
description,
size: value.size,
block_size: value.phy_sec,
logical_block_size: value.log_sec,
is_removable,
is_system,
partition_table_type: value.ptype,
mountpoints: value.children.into_iter().map(Into::into).collect(),
..Default::default()
}
}
}
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum FsSize {
String(String),
U64(u64),
}
impl From<FsSize> for u64 {
fn from(value: FsSize) -> Self {
match value {
FsSize::String(x) => x.parse().unwrap(),
FsSize::U64(x) => x,
}
}
}
#[derive(Deserialize, Debug)]
struct Child {
mountpoint: Option<String>,
fssize: Option<FsSize>,
fsavail: Option<FsSize>,
label: Option<String>,
partlabel: Option<String>,
}
impl From<Child> for MountPoint {
fn from(value: Child) -> Self {
Self {
path: value.mountpoint.unwrap_or_default(),
label: if value.label.is_some() {
value.label
} else {
value.partlabel
},
total_bytes: value.fssize.map(Into::into),
available_bytes: value.fsavail.map(Into::into),
}
}
}
pub(crate) fn lsblk() -> crate::Result<Vec<DeviceDescriptor>> {
let output = Command::new("lsblk")
.args(["--bytes", "--all", "--json", "--paths", "--output-all"])
.output()
.map_err(|e| crate::Error::LsblkExecuteError { source: Some(e) })?;
if !output.status.success() {
return Err(crate::Error::LsblkExecuteError { source: None });
}
let res: Devices = serde_json::from_slice(&output.stdout).unwrap();
Ok(res.blockdevices.into_iter().map(Into::into).collect())
}
#[cfg(test)]
mod tests {
use crate::DeviceDescriptor;
#[test]
fn loop_dev() {
let data = r#"
{
"blockdevices": [
{
"name":"/dev/loop23",
"kname":"/dev/loop23",
"path":"/dev/loop23",
"maj:min":"7:23",
"fsavail":null,
"fssize":null,
"fstype":null,
"fsused":null,
"fsuse%":null,
"mountpoint":null,
"label":null,
"uuid":null,
"ptuuid":null,
"pttype":null,
"parttype":null,
"partlabel":null,
"partuuid":null,
"partflags":null,
"ra":128,
"ro":false,
"rm":false,
"hotplug":false,
"model":null,
"serial":null,
"size":null,
"state":null,
"owner":"root",
"group":"disk",
"mode":"brw-rw----",
"alignment":0,
"min-io":512,
"opt-io":0,
"phy-sec":512,
"log-sec":512,
"rota":false,
"sched":"none",
"rq-size":128,
"type":"loop",
"disc-aln":0,
"disc-gran":4096,
"disc-max":4294966784,
"disc-zero":false,
"wsame":0,
"wwn":null,
"rand":false,
"pkname":null,
"hctl":null,
"tran":null,
"subsystems":"block",
"rev":null,
"vendor":null,
"zoned":"none"
}
]
}"#;
let res: super::Devices = serde_json::from_str(data).unwrap();
let _: Vec<DeviceDescriptor> = res.blockdevices.into_iter().map(Into::into).collect();
}
}