use super::error::{DiskError, DiskResult};
use super::BlockDevice;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PartitionType {
LinuxFilesystem,
LinuxSwap,
EfiSystem,
LinuxLvm,
LinuxRaid,
MicrosoftBasicData,
Unknown(u8),
}
impl PartitionType {
pub fn from_gpt_guid(guid: &[u8; 16]) -> Self {
const LINUX_FS: [u8; 16] = [
0xAF, 0x3D, 0xC6, 0x0F, 0x83, 0x84, 0x72, 0x47, 0x8E, 0x79, 0x3D, 0x69, 0xD8, 0x47,
0x7D, 0xE4,
];
const LINUX_SWAP: [u8; 16] = [
0x6D, 0xFD, 0x57, 0x06, 0xAB, 0xA4, 0xC4, 0x43, 0x84, 0xE5, 0x09, 0x33, 0xC8, 0x4B,
0x4F, 0x4F,
];
const EFI_SYSTEM: [u8; 16] = [
0x28, 0x73, 0x2A, 0xC1, 0x1F, 0xF8, 0xD2, 0x11, 0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E,
0xC9, 0x3B,
];
const MS_BASIC_DATA: [u8; 16] = [
0xA2, 0xA0, 0xD0, 0xEB, 0xE5, 0xB9, 0x33, 0x44, 0x87, 0xC0, 0x68, 0xB6, 0xB7, 0x26,
0x99, 0xC7,
];
if guid == &LINUX_FS {
Self::LinuxFilesystem
} else if guid == &LINUX_SWAP {
Self::LinuxSwap
} else if guid == &EFI_SYSTEM {
Self::EfiSystem
} else if guid == &MS_BASIC_DATA {
Self::MicrosoftBasicData
} else {
Self::Unknown(0)
}
}
pub fn from_mbr_type(type_byte: u8) -> Self {
match type_byte {
0x83 => Self::LinuxFilesystem,
0x82 => Self::LinuxSwap,
0xEF => Self::EfiSystem,
0x8E => Self::LinuxLvm,
0xFD => Self::LinuxRaid,
0x07 | 0x0B | 0x0C => Self::MicrosoftBasicData, _ => Self::Unknown(type_byte),
}
}
pub fn should_encode(&self) -> bool {
match self {
Self::LinuxSwap => false, Self::LinuxLvm => false, Self::LinuxRaid => false, _ => true,
}
}
}
#[derive(Debug, Clone)]
pub struct PartitionInfo {
pub number: u32,
pub name: String,
pub partition_type: PartitionType,
pub start_offset: u64,
pub size: u64,
pub uuid: Option<String>,
pub fs_hint: Option<String>,
}
impl PartitionInfo {
pub fn end_offset(&self) -> u64 {
self.start_offset + self.size
}
}
#[derive(Debug)]
pub struct PartitionTable {
pub scheme: PartitionScheme,
partitions: Vec<PartitionInfo>,
pub disk_size: u64,
pub block_size: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PartitionScheme {
Gpt,
Mbr,
None,
}
impl PartitionTable {
pub async fn detect(device: &dyn BlockDevice) -> DiskResult<Self> {
let disk_size = device.size();
let block_size = device.block_size();
if let Ok(gpt) = Self::try_parse_gpt(device).await {
return Ok(gpt);
}
if let Ok(mbr) = Self::try_parse_mbr(device).await {
return Ok(mbr);
}
Ok(Self {
scheme: PartitionScheme::None,
partitions: vec![],
disk_size,
block_size,
})
}
async fn try_parse_gpt(device: &dyn BlockDevice) -> DiskResult<Self> {
let mut header = [0u8; 92];
device.read_at(&mut header, 512).await?;
if &header[0..8] != b"EFI PART" {
return Err(DiskError::PartitionError {
reason: "No GPT signature found".to_string(),
});
}
let revision = u32::from_le_bytes(header[8..12].try_into().unwrap());
let header_size = u32::from_le_bytes(header[12..16].try_into().unwrap());
let _header_crc = u32::from_le_bytes(header[16..20].try_into().unwrap());
let _current_lba = u64::from_le_bytes(header[24..32].try_into().unwrap());
let _backup_lba = u64::from_le_bytes(header[32..40].try_into().unwrap());
let first_usable_lba = u64::from_le_bytes(header[40..48].try_into().unwrap());
let last_usable_lba = u64::from_le_bytes(header[48..56].try_into().unwrap());
let partition_entry_lba = u64::from_le_bytes(header[72..80].try_into().unwrap());
let num_partition_entries = u32::from_le_bytes(header[80..84].try_into().unwrap());
let partition_entry_size = u32::from_le_bytes(header[84..88].try_into().unwrap());
let mut partitions = Vec::new();
let entries_offset = partition_entry_lba * 512;
for i in 0..num_partition_entries {
let entry_offset = entries_offset + (i as u64 * partition_entry_size as u64);
let mut entry = vec![0u8; partition_entry_size as usize];
device.read_at(&mut entry, entry_offset).await?;
let type_guid: [u8; 16] = entry[0..16].try_into().unwrap();
if type_guid == [0u8; 16] {
continue; }
let partition_guid: [u8; 16] = entry[16..32].try_into().unwrap();
let start_lba = u64::from_le_bytes(entry[32..40].try_into().unwrap());
let end_lba = u64::from_le_bytes(entry[40..48].try_into().unwrap());
let _attributes = u64::from_le_bytes(entry[48..56].try_into().unwrap());
let name_bytes = &entry[56..128];
let name: String = name_bytes
.chunks_exact(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.take_while(|&c| c != 0)
.filter_map(|c| char::from_u32(c as u32))
.collect();
let partition_type = PartitionType::from_gpt_guid(&type_guid);
partitions.push(PartitionInfo {
number: i,
name: if name.is_empty() {
format!("Partition {}", i + 1)
} else {
name
},
partition_type,
start_offset: start_lba * 512,
size: (end_lba - start_lba + 1) * 512,
uuid: Some(Self::format_guid(&partition_guid)),
fs_hint: None,
});
}
Ok(Self {
scheme: PartitionScheme::Gpt,
partitions,
disk_size: device.size(),
block_size: 512,
})
}
async fn try_parse_mbr(device: &dyn BlockDevice) -> DiskResult<Self> {
let mut mbr = [0u8; 512];
device.read_at(&mut mbr, 0).await?;
if mbr[510] != 0x55 || mbr[511] != 0xAA {
return Err(DiskError::PartitionError {
reason: "No MBR signature found".to_string(),
});
}
if mbr[450] == 0xEE {
return Err(DiskError::PartitionError {
reason: "Protective MBR detected (use GPT)".to_string(),
});
}
let mut partitions = Vec::new();
for i in 0..4 {
let entry_offset = 446 + i * 16;
let entry = &mbr[entry_offset..entry_offset + 16];
let type_byte = entry[4];
if type_byte == 0 {
continue; }
let start_lba = u32::from_le_bytes(entry[8..12].try_into().unwrap()) as u64;
let num_sectors = u32::from_le_bytes(entry[12..16].try_into().unwrap()) as u64;
let partition_type = PartitionType::from_mbr_type(type_byte);
partitions.push(PartitionInfo {
number: i as u32,
name: format!("Partition {}", i + 1),
partition_type,
start_offset: start_lba * 512,
size: num_sectors * 512,
uuid: None,
fs_hint: None,
});
if type_byte == 0x05 || type_byte == 0x0F || type_byte == 0x85 {
let extended_partitions = Self::parse_extended_partitions(
device,
start_lba,
start_lba, partitions.len() as u32,
)
.await?;
partitions.extend(extended_partitions);
}
}
Ok(Self {
scheme: PartitionScheme::Mbr,
partitions,
disk_size: device.size(),
block_size: 512,
})
}
async fn parse_extended_partitions(
device: &dyn BlockDevice,
ebr_lba: u64,
extended_start_lba: u64,
start_number: u32,
) -> DiskResult<Vec<PartitionInfo>> {
let mut partitions = Vec::new();
let mut current_ebr_lba = ebr_lba;
let mut partition_num = start_number;
const MAX_LOGICAL_PARTITIONS: usize = 128;
while partitions.len() < MAX_LOGICAL_PARTITIONS {
let mut ebr = [0u8; 512];
device.read_at(&mut ebr, current_ebr_lba * 512).await?;
if ebr[510] != 0x55 || ebr[511] != 0xAA {
break; }
let entry0 = &ebr[446..462];
let type0 = entry0[4];
if type0 != 0 {
let start_lba0 = u32::from_le_bytes(entry0[8..12].try_into().unwrap()) as u64;
let num_sectors0 = u32::from_le_bytes(entry0[12..16].try_into().unwrap()) as u64;
let absolute_start = current_ebr_lba + start_lba0;
partitions.push(PartitionInfo {
number: partition_num,
name: format!("Logical Partition {}", partition_num + 1),
partition_type: PartitionType::from_mbr_type(type0),
start_offset: absolute_start * 512,
size: num_sectors0 * 512,
uuid: None,
fs_hint: None,
});
partition_num += 1;
}
let entry1 = &ebr[462..478];
let type1 = entry1[4];
if type1 == 0 {
break; }
if type1 != 0x05 && type1 != 0x0F && type1 != 0x85 {
break; }
let start_lba1 = u32::from_le_bytes(entry1[8..12].try_into().unwrap()) as u64;
current_ebr_lba = extended_start_lba + start_lba1;
}
Ok(partitions)
}
fn format_guid(guid: &[u8; 16]) -> String {
format!(
"{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
u32::from_le_bytes([guid[0], guid[1], guid[2], guid[3]]),
u16::from_le_bytes([guid[4], guid[5]]),
u16::from_le_bytes([guid[6], guid[7]]),
guid[8],
guid[9],
guid[10],
guid[11],
guid[12],
guid[13],
guid[14],
guid[15]
)
}
pub fn partitions(&self) -> &[PartitionInfo] {
&self.partitions
}
pub fn encodable_partitions(&self) -> impl Iterator<Item = &PartitionInfo> {
self.partitions
.iter()
.filter(|p| p.partition_type.should_encode())
}
pub fn get_partition(&self, number: u32) -> Option<&PartitionInfo> {
self.partitions.iter().find(|p| p.number == number)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mbr_partition_types() {
assert_eq!(
PartitionType::from_mbr_type(0x83),
PartitionType::LinuxFilesystem
);
assert_eq!(PartitionType::from_mbr_type(0x82), PartitionType::LinuxSwap);
assert_eq!(PartitionType::from_mbr_type(0xEF), PartitionType::EfiSystem);
}
#[test]
fn test_should_encode() {
assert!(PartitionType::LinuxFilesystem.should_encode());
assert!(PartitionType::EfiSystem.should_encode());
assert!(!PartitionType::LinuxSwap.should_encode());
}
}