use fs_wrap::build_path;
use mounts::Mounts;
mod error;
mod fs_wrap;
mod gpt;
mod mounts;
mod size;
pub use error::DrivesError;
pub use mounts::Mount;
pub use size::{Size, Unit};
pub use gpt::GptUUID;
use std::fs::DirEntry;
#[derive(Debug)]
pub struct Device {
pub name: String,
pub partitions: Vec<Partition>,
pub is_removable: bool,
pub model: Option<String>,
pub serial: Option<String>,
pub size: Size,
pub uuid: GptUUID,
}
#[derive(Debug)]
pub struct Partition {
pub name: String,
pub size: Size,
pub number: u32,
pub mountpoint: Option<Mount>,
pub part_uuid: GptUUID,
}
struct Drives {
base_path: String,
mounts: Mounts,
}
impl Drives {
fn find_partitions(&self, dir_entry: &DirEntry) -> Result<Vec<Partition>, DrivesError> {
let mount_points = self.mounts.read_mountpoints()?;
let mut partitions = vec![];
let base_dir_name = fs_wrap::name_from_direntry(dir_entry)?;
let dir_entry_path = if let Some(dir_entry_path) = dir_entry.path().to_str() {
dir_entry_path.to_owned()
} else {
return Err(DrivesError::NameFromDirEntryFailed);
};
for entry in fs_wrap::read_dir(&dir_entry_path)? {
let entry = entry.map_err(|_err| DrivesError::DiraccessError {
directory: dir_entry_path.to_string(),
})?;
if let Ok(file_type) = entry.file_type() {
if file_type.is_dir() {
let dir_name = fs_wrap::name_from_direntry(&entry)?;
if dir_name.starts_with(&base_dir_name) {
let size = fs_wrap::read_file_to_u64(&build_path(&entry, "/size")?)?;
let mount = self.find_mountpoint_for_partition(&mount_points, &dir_name)?;
let number = fs_wrap::read_file_to_u32(&build_path(&entry, "/partition")?)?;
partitions.push(Partition {
name: dir_name,
size: Size::new(size),
number,
mountpoint: mount,
part_uuid: GptUUID::NotAvailable,
});
}
}
} else {
return Err(DrivesError::FileTypeError {
filename: fs_wrap::path_to_string(entry.path().as_path()),
});
}
}
Ok(partitions)
}
fn find_mountpoint_for_partition(
&self,
mounts: &[Mount],
partition_name: &str,
) -> Result<Option<Mount>, DrivesError> {
let found_mount = mounts
.iter()
.find(|mount| mount.device.contains(partition_name));
if let Some(mount) = found_mount {
return Ok(Some(mount.clone()));
}
Ok(None)
}
fn read_model_and_serial_if_available(
&self,
dir_entry: &DirEntry,
) -> (Option<String>, Option<String>) {
let device_subdir_path = dir_entry.path().join("device");
if !device_subdir_path.exists() {
return (None, None);
}
let model_file_path = device_subdir_path.join("model");
let serial_file_path = device_subdir_path.join("serial");
let model = fs_wrap::read_file_to_string(model_file_path.as_path()).ok();
let serial = fs_wrap::read_file_to_string(serial_file_path.as_path()).ok();
(model, serial)
}
fn get_devices(&self) -> Result<Vec<Device>, DrivesError> {
let mut devices = vec![];
for entry in fs_wrap::read_dir(&self.base_path)? {
let entry = entry.map_err(|_err| DrivesError::DiraccessError {
directory: self.base_path.to_string(),
})?;
let device_name = fs_wrap::name_from_direntry(&entry)?;
let removable_path = fs_wrap::build_path(&entry, "/removable")?;
let removable = fs_wrap::read_bool_file(&removable_path)?;
let partitions = self.find_partitions(&entry)?;
let model_and_serial = self.read_model_and_serial_if_available(&entry);
let size = fs_wrap::read_file_to_u64(&build_path(&entry, "/size")?)?;
let mut device = Device {
name: device_name.clone(),
partitions,
is_removable: removable,
model: model_and_serial.0,
serial: model_and_serial.1,
size: Size::new(size),
uuid: GptUUID::NotAvailable,
};
device = gpt::enrich_with_gpt_uuid(device);
devices.push(device);
}
Ok(devices)
}
fn new() -> Drives {
Drives {
base_path: "/sys/block".to_owned(),
mounts: Mounts::new(),
}
}
}
pub fn get_devices() -> Result<Vec<Device>, DrivesError> {
let drives = Drives::new();
drives.get_devices()
}
#[cfg(test)]
mod tests {
use tempfile::tempdir;
use super::*;
use std::{fs, io::Write};
#[test]
fn test_drives() {
let temp_dir = tempdir().unwrap();
let next_dir_path = temp_dir.path().join("nvme0n1");
fs::create_dir(&next_dir_path).unwrap();
let mut removable_file = fs::File::create(next_dir_path.join("removable")).unwrap();
removable_file.write_all("0".as_bytes()).unwrap();
let mut size_file = fs::File::create(next_dir_path.join("size")).unwrap();
size_file.write_all("1000215216".as_bytes()).unwrap();
let part_one_dir_path = next_dir_path.join("nvme0n1p1");
fs::create_dir(&part_one_dir_path).unwrap();
size_file = fs::File::create(part_one_dir_path.as_path().join("size")).unwrap();
size_file.write_all("1050624".as_bytes()).unwrap();
let mut partition_file = fs::File::create(part_one_dir_path.as_path().join("partition")).unwrap();
partition_file.write_all("1".as_bytes()).unwrap();
let part_two_dir_path = next_dir_path.join("nvme0n1p2");
fs::create_dir(&part_two_dir_path).unwrap();
size_file = fs::File::create(part_two_dir_path.as_path().join("size")).unwrap();
size_file.write_all("999162511".as_bytes()).unwrap();
let mut partition_file = fs::File::create(part_two_dir_path.as_path().join("partition")).unwrap();
partition_file.write_all("2".as_bytes()).unwrap();
let power_dir_path = next_dir_path.join("power");
fs::create_dir(power_dir_path).unwrap();
let drives = Drives {
base_path: temp_dir.path().to_str().unwrap().to_owned(),
mounts: Mounts::new(),
};
let devices = drives.get_devices().unwrap();
assert_eq!(1, devices.len());
let device = devices.get(0).unwrap();
assert_eq!("nvme0n1", device.name);
assert!(!device.is_removable);
assert_eq!(2, device.partitions.len());
let part1 = device
.partitions
.iter()
.find(|part| part.name.eq("nvme0n1p1"));
assert!(part1.is_some());
let part2 = device
.partitions
.iter()
.find(|part| part.name.eq("nvme0n1p2"));
assert!(part2.is_some());
}
}