use std::path::PathBuf;
use serde::Serialize;
use crate::error::Result;
use crate::layout::PartitionType;
use crate::mbr::MbrHeader;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum DiagnosticLevel {
Error,
Warning,
}
#[derive(Clone, Debug, Serialize)]
pub struct Diagnostic {
pub level: DiagnosticLevel,
pub code: &'static str,
pub message: String,
}
#[derive(Clone, Debug, Serialize)]
pub struct PartitionReport {
pub slot: usize,
pub used: bool,
pub bootable: bool,
pub partition_type: u8,
pub partition_type_name: Option<&'static str>,
pub start_lba: u64,
pub sectors: u64,
pub end_lba: Option<u64>,
pub start_offset: u64,
pub byte_len: u64,
}
#[derive(Clone, Debug, Serialize)]
pub struct InspectReport {
pub disk: PathBuf,
pub disk_size: u64,
pub sector_count: u64,
pub mbr_signature_valid: bool,
pub disk_signature: u32,
pub mbr: MbrHeader,
pub partitions: Vec<PartitionReport>,
pub diagnostics: Vec<Diagnostic>,
}
#[derive(Clone, Debug, Serialize)]
pub struct VerifyReport {
pub ok: bool,
pub strict: bool,
pub inspect: InspectReport,
pub diagnostics: Vec<Diagnostic>,
}
impl InspectReport {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string_pretty(self)?)
}
pub fn to_text(&self) -> String {
let mut output = String::new();
output.push_str(&format!("Disk: {}\n", self.disk.display()));
output.push_str(&format!("Size: {} bytes\n", self.disk_size));
output.push_str(&format!("Sectors: {}\n", self.sector_count));
output.push_str(&format!(
"MBR signature: {}\n",
if self.mbr_signature_valid {
"valid"
} else {
"invalid"
}
));
output.push_str(&format!(
"Disk signature: 0x{:08x}\n\n",
self.disk_signature
));
output.push_str("Slot Boot Type Start Sectors End Bytes\n");
output.push_str("---- ---- ----- ------- --------- ------- ---------\n");
for partition in &self.partitions {
let boot = if partition.bootable { "*" } else { "-" };
let type_name = format!("0x{:02x}", partition.partition_type);
let end_lba = partition
.end_lba
.map(|value| value.to_string())
.unwrap_or_else(|| "-".into());
output.push_str(&format!(
"{:<4} {:<4} {:<5} {:<7} {:<9} {:<7} {}\n",
partition.slot,
boot,
type_name,
partition.start_lba,
partition.sectors,
end_lba,
partition.byte_len
));
}
if !self.diagnostics.is_empty() {
output.push_str("\nDiagnostics:\n");
for diagnostic in &self.diagnostics {
output.push_str(&format!(
"- {:?} {}: {}\n",
diagnostic.level, diagnostic.code, diagnostic.message
));
}
}
output
}
}
impl VerifyReport {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string_pretty(self)?)
}
pub fn to_text(&self) -> String {
let mut output = String::new();
output.push_str(&format!(
"Verification: {}\n",
if self.ok { "ok" } else { "failed" }
));
output.push_str(&format!("Strict mode: {}\n\n", self.strict));
output.push_str(&self.inspect.to_text());
if !self.diagnostics.is_empty() {
output.push_str("\nVerification diagnostics:\n");
for diagnostic in &self.diagnostics {
output.push_str(&format!(
"- {:?} {}: {}\n",
diagnostic.level, diagnostic.code, diagnostic.message
));
}
}
output
}
}
pub fn build_partition_reports(mbr: &MbrHeader) -> Vec<PartitionReport> {
mbr.partitions
.iter()
.enumerate()
.map(|(index, entry)| {
let partition_type = PartitionType(entry.partition_type);
let start_lba = entry.starting_lba as u64;
let sectors = entry.sectors as u64;
PartitionReport {
slot: index + 1,
used: !entry.is_empty(),
bootable: entry.is_bootable(),
partition_type: entry.partition_type,
partition_type_name: partition_type.known_name(),
start_lba,
sectors,
end_lba: entry.end_lba(),
start_offset: start_lba * crate::mbr::SECTOR_SIZE as u64,
byte_len: sectors * crate::mbr::SECTOR_SIZE as u64,
}
})
.collect()
}