use core::fmt::Debug;
use core::ops::{Index, IndexMut};
use endian_num::Le;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MbrPartitionType {
Empty,
Fat12,
Fat16,
Extended,
Fat16Lba,
Ntfs,
Fat32,
Fat32Lba,
ExtendedLba,
Iso9660,
LinuxSwap,
LinuxNative,
LinuxLvm,
LinuxRaid,
ProtectiveMbr,
EfiSystemPartition,
Unknown(u8),
}
impl MbrPartitionType {
pub const fn from_u8(value: u8) -> Self {
match value {
0x00 => Self::Empty,
0x01 => Self::Fat12,
0x04 => Self::Fat16,
0x05 => Self::Extended,
0x06 => Self::Fat16Lba,
0x07 => Self::Ntfs,
0x0b => Self::Fat32,
0x0c => Self::Fat32Lba,
0x0f => Self::ExtendedLba,
0x17 => Self::Iso9660,
0x82 => Self::LinuxSwap,
0x83 => Self::LinuxNative,
0x8E => Self::LinuxLvm,
0xFD => Self::LinuxRaid,
0xEE => Self::ProtectiveMbr,
0xEF => Self::EfiSystemPartition,
_ => Self::Unknown(value),
}
}
pub const fn to_u8(&self) -> u8 {
match self {
Self::Empty => 0x00,
Self::Fat12 => 0x01,
Self::Fat16 => 0x04,
Self::Extended => 0x05,
Self::Fat16Lba => 0x06,
Self::Ntfs => 0x07,
Self::Fat32 => 0x0b,
Self::Fat32Lba => 0x0c,
Self::ExtendedLba => 0x0f,
Self::Iso9660 => 0x17,
Self::LinuxSwap => 0x82,
Self::LinuxNative => 0x83,
Self::LinuxLvm => 0x8E,
Self::LinuxRaid => 0xFD,
Self::ProtectiveMbr => 0xEE,
Self::EfiSystemPartition => 0xEF,
Self::Unknown(value) => *value,
}
}
pub const fn is_empty(&self) -> bool {
matches!(self, Self::Empty)
}
pub const fn is_protective(&self) -> bool {
matches!(self, Self::ProtectiveMbr)
}
}
impl core::fmt::Display for MbrPartitionType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Empty => write!(f, "Empty"),
Self::Fat12 => write!(f, "FAT12"),
Self::Fat16 => write!(f, "FAT16"),
Self::Extended => write!(f, "Extended"),
Self::Fat16Lba => write!(f, "FAT16 LBA"),
Self::Ntfs => write!(f, "NTFS"),
Self::Fat32 => write!(f, "FAT32"),
Self::Fat32Lba => write!(f, "FAT32 LBA"),
Self::ExtendedLba => write!(f, "Extended LBA"),
Self::Iso9660 => write!(f, "ISO 9660"),
Self::LinuxSwap => write!(f, "Linux swap"),
Self::LinuxNative => write!(f, "Linux"),
Self::LinuxLvm => write!(f, "Linux LVM"),
Self::LinuxRaid => write!(f, "Linux RAID"),
Self::ProtectiveMbr => write!(f, "GPT Protective"),
Self::EfiSystemPartition => write!(f, "EFI System"),
Self::Unknown(id) => write!(f, "Unknown (0x{:02X})", id),
}
}
}
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Chs([u8; 3]);
impl Debug for Chs {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Chs")
.field("c", &self.cylinder())
.field("h", &self.head())
.field("s", &self.sector())
.finish()
}
}
impl Default for Chs {
fn default() -> Self {
Self::new(0)
}
}
impl Chs {
pub const OUT_OF_RANGE: Chs = Chs([0xFF, 0xFF, 0xFF]);
const SECTORS_PER_TRACK: u32 = 63;
const HEADS_PER_CYLINDER: u32 = 255;
pub const fn new(lba: u32) -> Self {
let cylinder = lba / (Self::SECTORS_PER_TRACK * Self::HEADS_PER_CYLINDER);
if cylinder > 0x03FF {
return Self::OUT_OF_RANGE;
}
let tmp = lba % (Self::SECTORS_PER_TRACK * Self::HEADS_PER_CYLINDER);
let head = tmp / Self::SECTORS_PER_TRACK;
let sector = tmp % Self::SECTORS_PER_TRACK + 1;
assert!(
sector <= 0b00111111,
"Sector overflow, this should never happen"
);
Self([
(head & 0x00ff) as u8,
(sector & 0b00111111) as u8 | ((cylinder & 0x0300) >> 2) as u8,
(cylinder & 0xFF) as u8,
])
}
pub const fn head(&self) -> u8 {
self.0[0]
}
pub const fn sector(&self) -> u8 {
self.0[1] & 0b00111111
}
pub const fn cylinder(&self) -> u16 {
((self.0[1] as u16 & 0b11000000) << 2) | (self.0[2] as u16)
}
pub const fn as_lba(&self) -> u32 {
if self.0[0] == 0xFF && self.0[1] == 0xFF && self.0[2] == 0xFF {
return u32::MAX;
}
self.cylinder() as u32 * Self::SECTORS_PER_TRACK * Self::HEADS_PER_CYLINDER
+ self.head() as u32 * Self::SECTORS_PER_TRACK
+ self.sector() as u32
- 1
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct MbrPartition {
pub boot_indicator: u8,
pub start_chs: Chs,
pub part_type: u8,
pub end_chs: Chs,
pub start_lba: Le<u32>,
pub sector_count: Le<u32>,
}
impl Default for MbrPartition {
fn default() -> Self {
Self {
boot_indicator: 0x00,
start_chs: Chs::new(0),
part_type: 0x00,
end_chs: Chs::new(0),
start_lba: Le::<u32>::from_ne(0),
sector_count: Le::<u32>::from_ne(0),
}
}
}
impl MbrPartition {
pub const fn new(part_type: MbrPartitionType, start_lba: u32, sector_count: u32) -> Self {
let end_lba = if sector_count > 0 {
start_lba + sector_count - 1
} else {
start_lba
};
Self {
boot_indicator: 0x00,
start_chs: Chs::new(start_lba),
part_type: part_type.to_u8(),
end_chs: Chs::new(end_lba),
start_lba: Le::<u32>::from_ne(start_lba),
sector_count: Le::<u32>::from_ne(sector_count),
}
}
pub const fn protective(disk_sectors: u64) -> Self {
let size = if disk_sectors > 0xFFFFFFFF {
0xFFFFFFFF
} else if disk_sectors > 1 {
(disk_sectors - 1) as u32
} else {
1
};
Self::new(MbrPartitionType::ProtectiveMbr, 1, size)
}
pub const fn is_empty(&self) -> bool {
self.part_type == 0x00
}
pub const fn partition_type(&self) -> MbrPartitionType {
MbrPartitionType::from_u8(self.part_type)
}
pub const fn is_bootable(&self) -> bool {
self.boot_indicator == 0x80
}
pub fn set_bootable(&mut self, bootable: bool) {
self.boot_indicator = if bootable { 0x80 } else { 0x00 };
}
pub const fn end_lba(&self) -> u32 {
let start = self.start_lba.to_ne();
let count = self.sector_count.to_ne();
if count == 0 { start } else { start + count - 1 }
}
}
#[repr(transparent)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct MbrPartitionTable {
pub partitions: [MbrPartition; 4],
}
impl Default for MbrPartitionTable {
fn default() -> Self {
Self::new()
}
}
impl Debug for MbrPartitionTable {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let non_empty: usize = self.partitions.iter().filter(|p| !p.is_empty()).count();
f.debug_struct("MbrPartitionTable")
.field("partitions", &&self.partitions[..non_empty])
.finish()
}
}
impl MbrPartitionTable {
const EMPTY_PARTITION: MbrPartition = MbrPartition {
boot_indicator: 0,
start_chs: Chs([0, 0, 0]),
part_type: 0,
end_chs: Chs([0, 0, 0]),
start_lba: Le::<u32>::from_ne(0),
sector_count: Le::<u32>::from_ne(0),
};
pub const fn new() -> Self {
Self {
partitions: [Self::EMPTY_PARTITION; 4],
}
}
pub const fn protective(disk_sectors: u64) -> Self {
let mut table = Self::new();
table.partitions[0] = MbrPartition::protective(disk_sectors);
table
}
pub fn count(&self) -> usize {
self.partitions.iter().filter(|p| !p.is_empty()).count()
}
pub fn is_valid(&self) -> bool {
for partition in &self.partitions {
if (partition.boot_indicator & !0x80) != 0 {
return false;
}
}
true
}
pub fn is_protective(&self) -> bool {
!self.partitions[0].is_empty() && self.partitions[0].partition_type().is_protective()
}
pub fn iter(&self) -> impl Iterator<Item = &MbrPartition> {
self.partitions.iter().filter(|p| !p.is_empty())
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut MbrPartition> {
self.partitions.iter_mut()
}
}
impl Index<usize> for MbrPartitionTable {
type Output = MbrPartition;
fn index(&self, index: usize) -> &Self::Output {
&self.partitions[index]
}
}
impl IndexMut<usize> for MbrPartitionTable {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.partitions[index]
}
}
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct MasterBootRecord {
pub bootstrap: [u8; 446],
pub partition_table: MbrPartitionTable,
pub signature: [u8; 2],
}
unsafe impl bytemuck::Pod for MasterBootRecord {}
unsafe impl bytemuck::Zeroable for MasterBootRecord {}
impl Default for MasterBootRecord {
fn default() -> Self {
Self {
bootstrap: [0; 446],
partition_table: MbrPartitionTable::default(),
signature: [0x55, 0xAA],
}
}
}
impl MasterBootRecord {
pub const SIGNATURE: [u8; 2] = [0x55, 0xAA];
pub const fn new(partition_table: MbrPartitionTable) -> Self {
Self {
bootstrap: [0; 446],
partition_table,
signature: Self::SIGNATURE,
}
}
pub const fn protective(disk_sectors: u64) -> Self {
Self::new(MbrPartitionTable::protective(disk_sectors))
}
pub const fn has_valid_signature(&self) -> bool {
self.signature[0] == 0x55 && self.signature[1] == 0xAA
}
pub fn get_partition_table(&self) -> MbrPartitionTable {
self.partition_table
}
pub fn set_partition_table(&mut self, table: MbrPartitionTable) {
self.partition_table = table;
}
pub fn with_partition_table<F>(&mut self, f: F)
where
F: FnOnce(&mut MbrPartitionTable),
{
let mut pt = self.get_partition_table();
f(&mut pt);
self.set_partition_table(pt);
}
pub fn is_valid(&self) -> bool {
let pt = self.get_partition_table();
self.has_valid_signature() && pt.is_valid()
}
}
impl Debug for MasterBootRecord {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let pt = self.get_partition_table();
f.debug_struct("MasterBootRecord")
.field("partition_table", &pt)
.field(
"signature",
&format_args!("0x{:02X}{:02X}", self.signature[0], self.signature[1]),
)
.finish()
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MbrPartitionTypeFull {
Empty = 0x00,
Fat12 = 0x01,
XenixRoot = 0x02,
XenixUsr = 0x03,
Fat16S = 0x04,
Extended = 0x05,
Fat16L = 0x06,
Installable = 0x07,
AixBoot = 0x08,
AixData = 0x09,
Os2Boot = 0x0A,
Fat32 = 0x0B,
Fat32Lba = 0x0C,
Reserved0D = 0x0D,
Fat16Lba = 0x0E,
ExtendedLba = 0x0F,
Opus = 0x10,
HiddenFat12 = 0x11,
CompaqDiagnosis = 0x12,
Reserved13 = 0x13,
HiddenFat16S = 0x14,
Reserved15 = 0x15,
HiddenFat16L = 0x16,
HiddenIfs = 0x17,
AstWindowsSwap = 0x18,
WillowtechPhotonCos = 0x19,
Reserved1A = 0x1A,
HiddenFat32 = 0x1B,
HiddenFat32Lba = 0x1C,
Reserved1D = 0x1D,
HiddenFat16Lba = 0x1E,
Reserved1F = 0x1F,
Ofs1 = 0x20,
Reserved21 = 0x21,
OxygenExt = 0x22,
Reserved23 = 0x23,
NecMsDos = 0x24,
Reserved25 = 0x25,
Reserved26 = 0x26,
Reserved27 = 0x27,
Reserved28 = 0x28,
Reserved29 = 0x29,
Reserved2A = 0x2A,
Reserved2B = 0x2B,
Reserved2C = 0x2C,
Reserved2D = 0x2D,
Reserved2E = 0x2E,
Reserved2F = 0x2F,
Reserved30 = 0x30,
Reserved31 = 0x31,
Reserved32 = 0x32,
Reserved33 = 0x33,
Reserved34 = 0x34,
Reserved35 = 0x35,
Reserved36 = 0x36,
Reserved37 = 0x37,
Theos = 0x38,
Reserved39 = 0x39,
Reserved3A = 0x3A,
Reserved3B = 0x3B,
PowerQuestFiles = 0x3C,
HiddenNetWare = 0x3D,
Reserved3E = 0x3E,
Reserved3F = 0x3F,
Venix80286 = 0x40,
PpcBoot = 0x41,
SecureFileSystem = 0x42,
AltExt2Fs = 0x43,
Reserved44 = 0x44,
Priam = 0x45,
EumelElan46 = 0x46,
EumelElan47 = 0x47,
EumelElan48 = 0x48,
Reserved49 = 0x49,
Alfs = 0x4A,
Reserved4B = 0x4B,
Reserved4C = 0x4C,
Qnx4D = 0x4D,
Qnx4E = 0x4E,
Qnx4F = 0x4F,
OdmReadOnly = 0x50,
OdmReadWrite = 0x51,
CPM = 0x52,
OdmWriteOnly = 0x53,
Odm6 = 0x54,
EzDrive = 0x55,
GoldenBow = 0x56,
Reserved57 = 0x57,
Reserved58 = 0x58,
Reserved59 = 0x59,
Reserved5A = 0x5A,
Reserved5B = 0x5B,
PriamEDisk = 0x5C,
Reserved5D = 0x5D,
Reserved5E = 0x5E,
Reserved5F = 0x5F,
Reserved60 = 0x60,
StorageDimension1 = 0x61,
Reserved62 = 0x62,
GnuHurd = 0x63,
NovellNetware286 = 0x64,
NovellNetware311 = 0x65,
NovellNetware386 = 0x66,
NovellNetware67 = 0x67,
NovellNetware68 = 0x68,
NovellNetware5 = 0x69,
Reserved6A = 0x6A,
Reserved6B = 0x6B,
Reserved6C = 0x6C,
Reserved6D = 0x6D,
Reserved6E = 0x6E,
Reserved6F = 0x6F,
DiskSecureMultiBoot = 0x70,
Reserved71 = 0x71,
Reserved72 = 0x72,
Reserved73 = 0x73,
Reserved74 = 0x74,
IbmPcIx = 0x75,
Reserved76 = 0x76,
Reserved77 = 0x77,
Reserved78 = 0x78,
Reserved79 = 0x79,
Reserved7A = 0x7A,
Reserved7B = 0x7B,
Reserved7C = 0x7C,
Reserved7D = 0x7D,
Reserved7E = 0x7E,
Reserved7F = 0x7F,
OldMinix = 0x80,
LinuxMinix = 0x81,
LinuxSwap = 0x82,
LinuxNative = 0x83,
Os2Hidden = 0x84,
LinuxExtended = 0x85,
NtStripeSet = 0x86,
HpfsFtMirrored = 0x87,
Reserved88 = 0x88,
Reserved89 = 0x89,
Reserved8A = 0x8A,
Reserved8B = 0x8B,
Reserved8C = 0x8C,
Reserved8D = 0x8D,
LinuxLvm = 0x8E,
Reserved8F = 0x8F,
Reserved90 = 0x90,
Reserved91 = 0x91,
Reserved92 = 0x92,
HiddenLinuxNative = 0x93,
AmoebaBadBlockTable = 0x94,
Reserved95 = 0x95,
Reserved96 = 0x96,
Reserved97 = 0x97,
Reserved98 = 0x98,
Mylex = 0x99,
Reserved9A = 0x9A,
Reserved9B = 0x9B,
Reserved9C = 0x9C,
Reserved9D = 0x9D,
Reserved9E = 0x9E,
Bsdi = 0x9F,
IbmHibernation = 0xA0,
HpVolumeExpA1 = 0xA1,
ReservedA2 = 0xA2,
HpVolumeExpA3 = 0xA3,
HpVolumeExpA4 = 0xA4,
FreeBsd386 = 0xA5,
OpenBsd = 0xA6,
HpVolumeExpA7 = 0xA7,
MacOsX = 0xA8,
NetBsd = 0xA9,
Olivetti = 0xAA,
MacOsXBoot = 0xAB,
ReservedAC = 0xAC,
ReservedAD = 0xAD,
ReservedAE = 0xAE,
MacOsXHfsPlus = 0xAF,
BootMngrBootStar = 0xB0,
HpVolumeExpB1 = 0xB1,
HpVolumeExpB2 = 0xB2,
HpVolumeExpB3 = 0xB3,
HpVolumeExpB4 = 0xB4,
ReservedB5 = 0xB5,
HpVolumeExpB6 = 0xB6,
BsdiFs = 0xB7,
BsdiSwap = 0xB8,
ReservedB9 = 0xB9,
ReservedBA = 0xBA,
PtsBootWizard = 0xBB,
AcronisBackup = 0xBC,
ReservedBD = 0xBD,
SolarisBoot = 0xBE,
Solaris = 0xBF,
NovellDos = 0xC0,
DrDos12 = 0xC1,
ReservedC2 = 0xC2,
ReservedC3 = 0xC3,
DrDos16 = 0xC4,
ReservedC5 = 0xC5,
DrDosHuge = 0xC6,
HpfsFtMirroredDisabled = 0xC7,
ReservedC8 = 0xC8,
ReservedC9 = 0xC9,
ReservedCA = 0xCA,
ReservedCB = 0xCB,
ReservedCC = 0xCC,
ReservedCD = 0xCD,
ReservedCE = 0xCE,
ReservedCF = 0xCF,
MultiuserDos = 0xD0,
OldMultiuserDos = 0xD1,
ReservedD2 = 0xD2,
ReservedD3 = 0xD3,
OldMultiuserDos2 = 0xD4,
OldMultiuserDos3 = 0xD5,
OldMultiuserDos4 = 0xD6,
ReservedD7 = 0xD7,
Cpm86 = 0xD8,
ReservedD9 = 0xD9,
ReservedDA = 0xDA,
Cpm = 0xDB,
ReservedDC = 0xDC,
ReservedDD = 0xDD,
Dell = 0xDE,
Embrm = 0xDF,
ReservedE0 = 0xE0,
SpeedStorFat12Ext = 0xE1,
DosReadOnly = 0xE2,
SpeedStor = 0xE3,
SpeedStor16Ext = 0xE4,
ReservedE5 = 0xE5,
StorageDimension2 = 0xE6,
ReservedE7 = 0xE7,
ReservedE8 = 0xE8,
ReservedE9 = 0xE9,
ReservedEA = 0xEA,
BeOs = 0xEB,
ReservedEC = 0xEC,
ReservedED = 0xED,
GptProtectiveMbr = 0xEE,
EfiSystemPartition = 0xEF,
ReservedF0 = 0xF0,
SpeedStorDimensions = 0xF1,
UnisysDos = 0xF2,
StorageDimension3 = 0xF3,
SpeedStorDimensions2 = 0xF4,
Prolugue = 0xF5,
StorageDimension4 = 0xF6,
ReservedF7 = 0xF7,
ReservedF8 = 0xF8,
ReservedF9 = 0xF9,
ReservedFA = 0xFA,
ReservedFB = 0xFB,
ReservedFC = 0xFC,
LinuxRaid = 0xFD,
LanStep = 0xFE,
BadBlockTable = 0xFF,
}
impl MbrPartitionTypeFull {
pub const fn from_u8(value: u8) -> Self {
unsafe { core::mem::transmute(value) }
}
pub const fn to_u8(&self) -> u8 {
*self as u8
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chs_create() {
assert_eq!(Chs::new(0), Chs([0, 1, 0]));
assert_eq!(Chs::new(1), Chs([0, 2, 0]));
assert_eq!(Chs::new(62), Chs([0, 63, 0]));
assert_eq!(Chs::new(63), Chs([1, 1, 0]));
assert_eq!(Chs::new(63 * 254), Chs([254, 1, 0]));
assert_eq!(Chs::new(63 * 255), Chs([0, 1, 1]));
assert_eq!(Chs::new(63 * 255 * 255), Chs([0, 1, 255]));
assert_eq!(Chs::new(63 * 255 * 1023), Chs([0, (15 << 6) + 1, 255]));
assert_eq!(Chs::new(63 * 255 * 1024), Chs::OUT_OF_RANGE);
}
#[test]
fn test_chs_get_lba() {
assert_eq!(Chs([0, 1, 0]).as_lba(), 0);
assert_eq!(Chs([0, 2, 0]).as_lba(), 1);
assert_eq!(Chs([0, 63, 0]).as_lba(), 62);
assert_eq!(Chs([1, 1, 0]).as_lba(), 63);
assert_eq!(Chs([254, 1, 0]).as_lba(), 63 * 254);
assert_eq!(Chs([0, 1, 1]).as_lba(), 63 * 255);
assert_eq!(Chs([0, 1, 255]).as_lba(), 63 * 255 * 255);
assert_eq!(Chs([0, (15 << 6) + 1, 255]).as_lba(), 63 * 255 * 1023);
assert_eq!(Chs::OUT_OF_RANGE.as_lba(), u32::MAX);
}
#[test]
fn test_mbr_partition_table_size() {
assert_eq!(core::mem::size_of::<MbrPartitionTable>(), 64);
assert_eq!(core::mem::size_of::<MbrPartition>(), 16);
assert_eq!(core::mem::size_of::<MasterBootRecord>(), 512);
}
#[test]
fn test_protective_mbr() {
let mbr = MasterBootRecord::protective(1000);
assert!(mbr.has_valid_signature());
let pt = mbr.get_partition_table();
assert!(pt.is_protective());
assert_eq!(pt[0].start_lba.to_ne(), 1);
assert_eq!(pt[0].sector_count.to_ne(), 999);
}
#[test]
fn test_partition_type_full_transmute() {
for i in 0u8..=255 {
let pt = MbrPartitionTypeFull::from_u8(i);
assert_eq!(pt.to_u8(), i);
}
}
}