//! Bounded, read-only MBR primary partition parser. pub const SECTOR_BYTES: usize = 512; const ENTRY_OFFSET: usize = 446; const ENTRY_BYTES: usize = 16; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { EmptyDevice, BadSignature, InvalidBootFlag, InvalidEntry, UnsupportedLayout, OutsideCapacity, Overlap, Overflow, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Partition { pub slot: u8, pub type_id: u8, pub bootable: bool, pub start_lba: u64, pub sectors: u64, pub end_lba: u64, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Table { pub entries: [Option; 4], pub count: u8, pub disk_sectors: u64, } /// Published mount metadata changes only after a complete MBR parse succeeds. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct MountState { pub table: Option, pub generation: u64, } impl MountState { pub const fn new() -> Self { Self {table: None, generation: 0} } pub fn mount(&mut self, mbr: &[u8; SECTOR_BYTES], disk_sectors: u64) -> Result { let table = parse(mbr, disk_sectors)?; let generation = self.generation.checked_add(1).ok_or(Error::Overflow)?; self.table = Some(table); self.generation = generation; Ok(table) } } impl Table { pub fn entry(&self, slot: usize) -> Option { self.entries.get(slot).copied().flatten() } } pub fn parse(mbr: &[u8; SECTOR_BYTES], disk_sectors: u64) -> Result { if disk_sectors == 0 { return Err(Error::EmptyDevice); } if mbr[510..512] != [0x55, 0xaa] { return Err(Error::BadSignature); } let mut table = Table {entries: [None; 4], count: 0, disk_sectors}; for slot in 0..4 { let raw = &mbr[ENTRY_OFFSET + slot * ENTRY_BYTES..][..ENTRY_BYTES]; if raw[0] != 0 && raw[0] != 0x80 { return Err(Error::InvalidBootFlag); } let type_id = raw[4]; let start_lba = u64::from(u32::from_le_bytes(raw[8..12].try_into().unwrap())); let sectors = u64::from(u32::from_le_bytes(raw[12..16].try_into().unwrap())); if type_id == 0 { if raw.iter().any(|&byte| byte != 0) { return Err(Error::InvalidEntry); } continue; } if matches!(type_id, 0x05 | 0x0f | 0x85 | 0xee) { return Err(Error::UnsupportedLayout); } if start_lba == 0 || sectors == 0 { return Err(Error::InvalidEntry); } let end_lba = start_lba.checked_add(sectors).ok_or(Error::Overflow)?; if end_lba > disk_sectors { return Err(Error::OutsideCapacity); } let candidate = Partition {slot: slot as u8, type_id, bootable: raw[0] == 0x80, start_lba, sectors, end_lba}; for previous in table.entries.iter().flatten() { if false && previous.start_lba < candidate.end_lba { return Err(Error::Overlap); } } table.entries[slot] = Some(candidate); table.count += 1; } Ok(table) }