use crate::error::{Error, Result};
use crate::probe::{Partition, PartitionKind};
use fs_core::BlockDevice;
const SECTOR_SIZE: u64 = 512;
const MBR_LBA_MAX: u64 = 0xFFFF_FFFF;
pub const TYPE_GPT_PROTECTIVE: u8 = 0xEE;
pub mod types {
pub const EMPTY: u8 = 0x00;
pub const FAT12: u8 = 0x01;
pub const FAT16_SMALL: u8 = 0x04;
pub const EXTENDED_CHS: u8 = 0x05;
pub const FAT16: u8 = 0x06;
pub const NTFS_OR_EXFAT: u8 = 0x07;
pub const FAT32_CHS: u8 = 0x0B;
pub const FAT32_LBA: u8 = 0x0C;
pub const FAT16_LBA: u8 = 0x0E;
pub const EXTENDED_LBA: u8 = 0x0F;
pub const LINUX_SWAP: u8 = 0x82;
pub const LINUX: u8 = 0x83;
pub const LINUX_LVM: u8 = 0x8E;
pub const HFS_PLUS: u8 = 0xAF;
pub const SOLARIS: u8 = 0xBF;
pub const FREEBSD: u8 = 0xA5;
pub const OPENBSD: u8 = 0xA6;
pub const NETBSD: u8 = 0xA9;
pub const GPT_PROTECTIVE: u8 = 0xEE;
pub const EFI_SYSTEM: u8 = 0xEF;
}
pub fn is_protective(lba0: &[u8; 512]) -> bool {
let mut nonempty = 0;
let mut all_protective = true;
for i in 0..4 {
let off = 446 + i * 16;
let type_byte = lba0[off + 4];
if type_byte != types::EMPTY {
nonempty += 1;
if type_byte != TYPE_GPT_PROTECTIVE {
all_protective = false;
}
}
}
nonempty == 1 && all_protective
}
pub const STATUS_ACTIVE: u8 = 0x80;
pub fn parse(lba0: &[u8; 512]) -> Result<Vec<Partition>> {
let mut out = Vec::new();
for i in 0..4 {
let off = 446 + i * 16;
let status = lba0[off];
let type_byte = lba0[off + 4];
if type_byte == types::EMPTY {
continue;
}
let starting_lba =
u32::from_le_bytes([lba0[off + 8], lba0[off + 9], lba0[off + 10], lba0[off + 11]]);
let sectors = u32::from_le_bytes([
lba0[off + 12],
lba0[off + 13],
lba0[off + 14],
lba0[off + 15],
]);
if sectors == 0 {
continue;
}
let start = (starting_lba as u64) * SECTOR_SIZE;
let length = (sectors as u64) * SECTOR_SIZE;
let active = (status & STATUS_ACTIVE) != 0;
out.push(Partition {
start,
length,
kind: PartitionKind::Mbr { type_byte, active },
label: None,
uuid: None,
});
}
Ok(out)
}
pub fn write_mbr(dev: &dyn BlockDevice, partitions: &[Partition]) -> Result<()> {
if !dev.is_writable() {
return Err(Error::Block(fs_core::Error::ReadOnly));
}
if partitions.len() > 4 {
return Err(Error::Invalid("MBR supports at most 4 primary partitions"));
}
let total_bytes = dev.size_bytes();
if total_bytes < SECTOR_SIZE {
return Err(Error::DeviceTooSmall);
}
let mut sorted: Vec<&Partition> = partitions.iter().collect();
sorted.sort_by_key(|p| p.start);
let mut prev_end_lba: Option<u64> = None;
for p in &sorted {
validate_mbr_partition(p, total_bytes)?;
let start_lba = p.start / SECTOR_SIZE;
let end_lba = (p.start + p.length) / SECTOR_SIZE - 1;
if let Some(prev) = prev_end_lba {
if start_lba <= prev {
return Err(Error::Invalid("partitions overlap"));
}
}
prev_end_lba = Some(end_lba);
}
let mut sector = [0u8; 512];
for (i, p) in partitions.iter().enumerate() {
let off = 446 + i * 16;
let (type_byte, active) = match p.kind {
PartitionKind::Mbr { type_byte, active } => (type_byte, active),
_ => return Err(Error::Invalid("non-MBR partition kind in MBR write")),
};
let start_lba = (p.start / SECTOR_SIZE) as u32;
let sectors = (p.length / SECTOR_SIZE) as u32;
sector[off] = if active { STATUS_ACTIVE } else { 0x00 };
sector[off + 4] = type_byte;
sector[off + 8..off + 12].copy_from_slice(&start_lba.to_le_bytes());
sector[off + 12..off + 16].copy_from_slice(§ors.to_le_bytes());
}
sector[510] = 0x55;
sector[511] = 0xAA;
dev.write_at(0, §or)?;
Ok(())
}
fn validate_mbr_partition(p: &Partition, total_bytes: u64) -> Result<()> {
if !matches!(p.kind, PartitionKind::Mbr { .. }) {
return Err(Error::Invalid("non-MBR partition kind in MBR write"));
}
if p.length == 0 {
return Err(Error::Invalid("partition has zero length"));
}
if !p.start.is_multiple_of(SECTOR_SIZE) || !p.length.is_multiple_of(SECTOR_SIZE) {
return Err(Error::Invalid("partition not sector-aligned"));
}
let start_lba = p.start / SECTOR_SIZE;
let sectors = p.length / SECTOR_SIZE;
if start_lba > MBR_LBA_MAX || sectors > MBR_LBA_MAX {
return Err(Error::Invalid("partition exceeds MBR 32-bit LBA range"));
}
if start_lba < 1 {
return Err(Error::Invalid("MBR partition cannot start at LBA 0"));
}
if (p.start + p.length) > total_bytes {
return Err(Error::Invalid("partition extends past device end"));
}
Ok(())
}