#![cfg(target_os = "macos")]
use crate::{DiskInfo, DiskInfoProvider, ProviderError};
use serde::Deserialize;
use std::collections::HashSet;
use std::process::Command;
#[allow(dead_code)]
#[derive(Deserialize, Debug)]
struct PhysicalDriveInfo {
#[serde(rename = "device_name")]
model: String,
}
#[allow(dead_code)]
#[derive(Deserialize, Debug)]
struct MacVolume {
bsd_name: String,
physical_drive: PhysicalDriveInfo,
}
#[allow(dead_code)]
#[derive(Deserialize, Debug)]
struct MacStorageOutput {
#[serde(rename = "SPStorageDataType")]
volumes: Vec<MacVolume>,
}
pub struct MacosProvider;
impl DiskInfoProvider for MacosProvider {
fn get_all_disks() -> Result<Vec<DiskInfo>, ProviderError> {
let profiler_output = Command::new("system_profiler")
.args(["SPStorageDataType", "-json"])
.output()?;
if !profiler_output.status.success() {
return Err(ProviderError::CommandUnsuccessful(
String::from_utf8_lossy(&profiler_output.stderr).into(),
));
}
let json_str = String::from_utf8_lossy(&profiler_output.stdout);
let parsed: MacStorageOutput = serde_json::from_str(&json_str)?;
let mut physical_disk_ids = HashSet::new();
for volume in parsed.volumes {
if let Some(rest) = volume.bsd_name.strip_prefix("disk") {
let number_part_len = rest.chars().take_while(|c| c.is_ascii_digit()).count();
let disk_id = format!("disk{}", &rest[..number_part_len]);
physical_disk_ids.insert(disk_id);
}
}
let mut disks_info = Vec::new();
for disk_id in physical_disk_ids {
let diskutil_output = Command::new("diskutil")
.arg("info")
.arg(&disk_id)
.output()?;
if !diskutil_output.status.success() {
eprintln!(
"Warning: Unable to get detailed information for '{}'.",
disk_id
);
continue;
}
let output_str = String::from_utf8_lossy(&diskutil_output.stdout);
let mut model: Option<String> = None;
let mut serial_number: Option<String> = None;
for line in output_str.lines() {
if let Some((key, value)) = line.split_once(':') {
let key = key.trim();
let value = value.trim();
if value.is_empty() {
continue;
}
match key {
"Device / Media Name" => model = Some(value.to_string()),
"Serial Number" => serial_number = Some(value.to_string()),
"Disk / Partition UUID" => serial_number = Some(value.to_string()),
"Volume UUID" => serial_number = Some(value.to_string()),
_ => {}
}
}
}
if let Some(m) = model {
disks_info.push(DiskInfo {
name: disk_id, model: Some(m),
serial_number,
});
}
}
Ok(disks_info)
}
}