deja-vu/deja_vu/src/system/disk.rs

404 lines
12 KiB
Rust

// This file is part of Deja-vu.
//
// Deja-vu is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Deja-vu is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Deja-vu. If not, see <https://www.gnu.org/licenses/>.
//
use serde::{Deserialize, Serialize};
use std::{
fmt,
process::{Command, Stdio},
thread::sleep,
time::Duration,
};
use tracing::info;
use once_cell::sync::Lazy;
use regex::Regex;
use crate::system::diskpart;
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Disk {
pub conn_type: String,
pub description: String,
pub id: usize,
pub model: String,
pub part_type: PartitionTableType,
pub parts: Vec<Partition>,
pub parts_description: Vec<String>,
pub sector_size: usize,
pub serial: String,
pub size: u64, // In bytes
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Partition {
pub id: usize,
pub fs_type: String,
pub label: String,
pub letter: String,
pub part_type: String,
pub size: u64, // In bytes
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum PartitionTableType {
#[default]
Guid,
Legacy,
}
impl Disk {
pub fn generate_descriptions(&mut self) {
self.description = format!("{self}");
self.parts_description = Vec::new();
for part in &self.parts {
self.parts_description.push(format!("{part}"));
}
}
pub fn get_part_letter(&self, part_index: usize) -> String {
// Used to get Boot and OS letters
if let Some(part) = self.parts.get(part_index) {
part.letter.clone()
} else {
String::new()
}
}
pub fn get_parts(&self) -> Vec<Partition> {
self.parts.clone()
}
pub fn num_parts(&self) -> usize {
self.parts.len()
}
}
impl fmt::Display for Disk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Disk {:<3} {:>11} {:<4} {:<4} {} ({})",
self.id,
bytes_to_string(self.size),
self.conn_type,
match self.part_type {
PartitionTableType::Guid => "GPT",
PartitionTableType::Legacy => "MBR",
},
self.model,
self.serial,
)
}
}
impl fmt::Display for Partition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s: String;
let fs = if self.fs_type.is_empty() {
String::from("(?)")
} else {
format!("({})", self.fs_type)
};
s = format!(
"{:<8} {:>11} {:<7}",
self.id,
bytes_to_string(self.size),
fs
);
if !self.label.is_empty() {
s = format!("{s} \"{}\"", &self.label);
}
write!(f, "{s}")
}
}
impl fmt::Display for PartitionTableType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
PartitionTableType::Guid => write!(f, "GPT / UEFI"),
PartitionTableType::Legacy => write!(f, "MBR / Legacy"),
}
}
}
pub fn get_disks() -> Vec<Disk> {
let disks: Vec<Disk>;
if cfg!(windows) {
info!("Get disks via Diskpart");
disks = diskpart::get_disks();
} else {
info!("Get (fake) disks");
disks = get_fake_disks();
sleep(Duration::from_millis(250));
}
disks
}
#[allow(clippy::too_many_lines)]
#[must_use]
pub fn get_fake_disks() -> Vec<Disk> {
let mut disks = vec![
Disk {
conn_type: "SATA".to_string(),
id: 0,
model: "Samsung Evo 870".to_string(),
part_type: PartitionTableType::Legacy,
parts: vec![
Partition {
id: 1,
fs_type: String::from("NTFS"),
label: String::from("System Reserved"),
part_type: String::from("7"),
size: 104_857_600,
..Default::default()
},
Partition {
id: 2,
part_type: String::from("5"),
size: 267_806_310_400,
..Default::default()
},
Partition {
id: 5,
fs_type: String::from("NTFS"),
label: String::from("Win7"),
part_type: String::from("7"),
size: 267_701_452_800,
..Default::default()
},
Partition {
id: 6,
fs_type: String::from("NTFS"),
label: String::from("Tools"),
part_type: String::from("7"),
size: 524_288_000,
..Default::default()
},
],
serial: "MDZ1243".to_string(),
size: 268_435_456_000,
..Default::default()
},
Disk {
conn_type: "SATA".to_string(),
id: 1,
model: "ADATA Garbage".to_string(),
part_type: PartitionTableType::Legacy,
parts: vec![Partition {
id: 1,
fs_type: String::from("NTFS"),
label: String::from("Scratch"),
part_type: String::from("7"),
size: 249_998_951_424,
..Default::default()
}],
serial: "000010000".to_string(),
size: 250_000_000_000,
..Default::default()
},
Disk {
conn_type: "NVMe".to_string(),
id: 2,
model: "Crucial P3 Plus".to_string(),
part_type: PartitionTableType::Guid,
parts: vec![
Partition {
id: 1,
fs_type: String::from("FAT32"),
label: String::from("ESP"),
letter: String::from("Q"),
part_type: String::from("EFI"),
size: 272_629_760,
},
Partition {
id: 2,
part_type: String::from("MSR"),
size: 16_777_216,
..Default::default()
},
Partition {
id: 4,
fs_type: String::from("NTFS"),
label: String::from("Win10"),
letter: String::from("V"),
part_type: String::from("MS Basic Data"),
size: 824_340_119_552,
},
],
serial: "80085".to_string(),
size: 268_435_456_000,
..Default::default()
},
Disk {
conn_type: "IDE".to_string(),
id: 3,
model: "Fireball".to_string(),
part_type: PartitionTableType::Guid,
parts: vec![
Partition {
id: 1,
fs_type: String::from("FAT32"),
label: String::from("EFI Boot"),
part_type: String::from("EFI"),
size: 209_715_200,
..Default::default()
},
Partition {
id: 2,
part_type: String::from("{48465300-0000-11AA-AA11-00306543ECAC}"),
size: 171_586_879_488,
..Default::default()
},
],
serial: "0xfff".to_string(),
size: 171_798_691_840,
..Default::default()
},
Disk {
conn_type: "MISC".to_string(),
id: 4,
part_type: PartitionTableType::Legacy,
model: "Iomega".to_string(),
serial: "000".to_string(),
size: 14,
..Default::default()
},
Disk {
id: 5,
..Default::default()
},
Disk::default(),
];
for disk in &mut disks {
disk.generate_descriptions();
}
disks
}
#[must_use]
pub fn get_disk_serial_number(id: usize) -> String {
let mut serial = String::new();
if cfg!(windows) {
let output = Command::new("wmic")
.args([
"diskdrive",
"where",
format!("index='{id}'").as_str(),
"get",
"serialNumber",
"/value",
])
.stdout(Stdio::piped())
.output();
if let Ok(result) = output {
let s = String::from_utf8_lossy(&result.stdout).trim().to_string();
if s.len() >= 15 {
serial = String::from(&s[14..]);
}
}
}
serial
}
pub fn refresh_disk_info(disk: &mut Disk) -> Disk {
let mut new_disk: Disk;
if cfg!(windows) {
info!("Refresh disk via Diskpart");
new_disk = diskpart::refresh_disk_info(disk);
} else {
info!("Refresh fake disk");
new_disk = disk.clone();
new_disk.parts = refresh_fake_disk_info();
new_disk.generate_descriptions();
}
new_disk
}
fn refresh_fake_disk_info() -> Vec<Partition> {
vec![
Partition {
id: 1,
fs_type: String::from("FAT32"),
label: String::from("New ESP Volume"),
letter: String::from("S"),
part_type: String::from("EFI"),
size: 272_629_760,
},
Partition {
id: 2,
label: String::from("New MSR Partition"),
part_type: String::from("MSR"),
size: 16_777_216,
..Default::default()
},
Partition {
id: 4,
fs_type: String::from("NTFS"),
label: String::from("Cloned OS Volume"),
letter: String::from("W"),
part_type: String::from("MS Basic Data"),
size: 824_340_119_552,
},
]
}
/// Misc
///
/// Clippy exception is fine because this supports sizes up to 2 EiB
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn bytes_to_string(size: u64) -> String {
let units = "KMGTPEZY".chars();
let scale = 1024.0;
let mut size = size as f64;
let mut suffix: Option<char> = None;
for u in units {
if size < scale {
break;
}
size /= scale;
suffix = Some(u);
}
if let Some(s) = suffix {
format!("{size:4.2} {s}iB")
} else {
format!("{size:4.2} B")
}
}
/// # Panics
///
/// Will panic if s is not simliar to 32B, 64MB, etc...
pub fn string_to_bytes(s: &str) -> u64 {
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(\d+)\s+(\w+)B").unwrap());
let base: u64 = 1024;
let mut size: u64 = 0;
for (_, [size_str, suffix]) in RE.captures_iter(s).map(|c| c.extract()) {
let x: u64 = size_str.parse().unwrap();
size += x;
match suffix {
"K" => size *= base,
"M" => size *= base.pow(2),
"G" => size *= base.pow(3),
"T" => size *= base.pow(4),
"P" => size *= base.pow(5),
"E" => size *= base.pow(6),
"Z" => size *= base.pow(7),
"Y" => size *= base.pow(8),
_ => (),
}
}
size
}