Skip to main content

mbr_forensic/
partition.rs

1//! MBR partition entry types and partition-type-code semantics.
2
3/// Decoded CHS (Cylinder-Head-Sector) address.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct Chs {
6    pub cylinder: u16,
7    pub head: u8,
8    pub sector: u8,
9}
10
11impl Chs {
12    /// Decode a 3-byte MBR CHS field.
13    ///
14    /// Byte layout (packed):
15    /// ```text
16    /// byte 0 = head
17    /// byte 1 = [cyl_hi(7:6) | sector(5:0)]
18    /// byte 2 = cyl_lo(7:0)
19    /// ```
20    #[must_use]
21    pub fn from_bytes(b: [u8; 3]) -> Self {
22        let head = b[0];
23        let sector = b[1] & 0x3F;
24        let cylinder = ((b[1] as u16 & 0xC0) << 2) | b[2] as u16;
25        Chs {
26            cylinder,
27            head,
28            sector,
29        }
30    }
31
32    /// Convert to an approximate LBA (≤1023 cylinders, ≤255 heads, ≤63 sectors).
33    ///
34    /// Returns `None` when CHS indicates "not used" (all zeros or all ones).
35    #[must_use]
36    pub fn to_lba(self, heads_per_cylinder: u8, sectors_per_track: u8) -> Option<u32> {
37        if self.sector == 0 {
38            return None;
39        }
40        let hpc = heads_per_cylinder as u32;
41        let spt = sectors_per_track as u32;
42        if hpc == 0 || spt == 0 {
43            return None;
44        }
45        Some(
46            (self.cylinder as u32) * hpc * spt
47                + (self.head as u32) * spt
48                + (self.sector as u32 - 1),
49        )
50    }
51}
52
53/// A single 16-byte primary partition table entry.
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct PartitionEntry {
56    /// `0x80` = bootable, `0x00` = inactive, other values are invalid.
57    pub status: u8,
58    /// CHS address of the partition's first sector.
59    pub chs_first: Chs,
60    /// Partition type code.
61    pub type_code: TypeCode,
62    /// CHS address of the partition's last sector.
63    pub chs_last: Chs,
64    /// LBA address of the partition's first sector.
65    pub lba_start: u32,
66    /// Number of sectors in the partition.
67    pub lba_count: u32,
68}
69
70impl PartitionEntry {
71    /// Decode a 16-byte partition entry slice.
72    #[must_use]
73    pub fn from_bytes(b: &[u8; 16]) -> Self {
74        PartitionEntry {
75            status: b[0],
76            chs_first: Chs::from_bytes([b[1], b[2], b[3]]),
77            type_code: TypeCode(b[4]),
78            chs_last: Chs::from_bytes([b[5], b[6], b[7]]),
79            lba_start: u32::from_le_bytes([b[8], b[9], b[10], b[11]]),
80            lba_count: u32::from_le_bytes([b[12], b[13], b[14], b[15]]),
81        }
82    }
83
84    /// Returns `true` if this entry is entirely zero (unused slot).
85    #[must_use]
86    pub fn is_empty(&self) -> bool {
87        self.type_code.is_empty() && self.lba_start == 0 && self.lba_count == 0
88    }
89
90    /// Returns `true` if the status byte marks this partition as bootable.
91    #[must_use]
92    pub fn is_bootable(&self) -> bool {
93        self.status == 0x80
94    }
95
96    /// Inclusive last LBA of this partition, saturating on overflow.
97    #[must_use]
98    pub fn lba_end(&self) -> u32 {
99        self.lba_start
100            .saturating_add(self.lba_count)
101            .saturating_sub(1)
102    }
103
104    /// `true` if this entry describes an extended partition container.
105    #[must_use]
106    pub fn is_extended(&self) -> bool {
107        self.type_code.is_extended()
108    }
109}
110
111/// Wrapper around an MBR partition type byte with semantic helpers.
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
113pub struct TypeCode(pub u8);
114
115impl TypeCode {
116    /// Human-readable short name for the partition type.
117    #[must_use]
118    pub fn name(self) -> &'static str {
119        match self.0 {
120            0x00 => "Empty",
121            0x01 => "FAT12",
122            0x04 => "FAT16 <32 MB",
123            0x05 => "Extended (CHS)",
124            0x06 => "FAT16",
125            0x07 => "NTFS / exFAT / IFS",
126            0x08 => "FAT32 (EISA / AIX)",
127            0x0B => "FAT32 (CHS)",
128            0x0C => "FAT32 (LBA)",
129            0x0E => "FAT16 (LBA)",
130            0x0F => "Extended (LBA)",
131            0x11 => "Hidden FAT12",
132            0x14 => "Hidden FAT16 <32 MB",
133            0x16 => "Hidden FAT16",
134            0x17 => "Hidden NTFS / IFS",
135            0x1B => "Hidden FAT32 (CHS)",
136            0x1C => "Hidden FAT32 (LBA)",
137            0x1E => "Hidden FAT16 (LBA)",
138            0x27 => "Windows Recovery / Hidden NTFS",
139            0x42 => "Windows LDM / Dynamic Disk",
140            0x82 => "Linux Swap / Solaris",
141            0x83 => "Linux",
142            0x84 => "Hibernate (Windows)",
143            0x85 => "Linux Extended",
144            0x86 => "Linux LVM (old)",
145            0x87 => "NTFS Volume Set",
146            0x8E => "Linux LVM",
147            0x9F => "BSD/OS",
148            0xA5 => "FreeBSD",
149            0xA6 => "OpenBSD",
150            0xA9 => "NetBSD",
151            0xAB => "macOS Boot",
152            0xAF => "macOS HFS+",
153            0xBE => "Solaris Boot",
154            0xBF => "Solaris",
155            0xEB => "BeOS / Haiku",
156            0xEE => "GPT Protective MBR",
157            0xEF => "EFI System Partition (FAT)",
158            0xFB => "VMware VMFS",
159            0xFC => "VMware Swap",
160            0xFD => "Linux RAID",
161            0xFE => "Linux LAF / IBM IML",
162            _ => "Unknown",
163        }
164    }
165
166    /// High-level partition family classification.
167    #[must_use]
168    pub fn family(self) -> PartitionFamily {
169        match self.0 {
170            0x00 => PartitionFamily::Empty,
171            0x01 | 0x11 => PartitionFamily::Fat12,
172            0x04 | 0x06 | 0x0E | 0x14 | 0x16 | 0x1E => PartitionFamily::Fat16,
173            0x0B | 0x0C | 0x1B | 0x1C => PartitionFamily::Fat32,
174            0x07 | 0x17 | 0x87 => PartitionFamily::Ntfs,
175            0x05 | 0x0F | 0x85 => PartitionFamily::ExtendedMbr,
176            0x82 => PartitionFamily::LinuxSwap,
177            0x83 => PartitionFamily::Linux,
178            0x8E => PartitionFamily::LinuxLvm,
179            0xFD => PartitionFamily::LinuxRaid,
180            0x27 => PartitionFamily::WindowsRecovery,
181            0x42 => PartitionFamily::WindowsDynamic,
182            0xA5 => PartitionFamily::FreeBsd,
183            0xA6 => PartitionFamily::OpenBsd,
184            0xA9 => PartitionFamily::NetBsd,
185            0xAF | 0xAB => PartitionFamily::Hfs,
186            0xEE => PartitionFamily::GptProtective,
187            0xEF => PartitionFamily::EfiSystem,
188            0xFB | 0xFC => PartitionFamily::Vmware,
189            _ => PartitionFamily::Unknown(self.0),
190        }
191    }
192
193    /// `true` if this is an empty (unused) slot.
194    #[must_use]
195    pub fn is_empty(self) -> bool {
196        self.0 == 0x00
197    }
198
199    /// `true` if this type code marks an extended partition container.
200    #[must_use]
201    pub fn is_extended(self) -> bool {
202        matches!(self.0, 0x05 | 0x0F | 0x85)
203    }
204}
205
206/// High-level classification of a partition type.
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208pub enum PartitionFamily {
209    Empty,
210    Fat12,
211    Fat16,
212    Fat32,
213    Ntfs,
214    ExtendedMbr,
215    LinuxSwap,
216    Linux,
217    LinuxLvm,
218    LinuxRaid,
219    WindowsRecovery,
220    WindowsDynamic,
221    FreeBsd,
222    OpenBsd,
223    NetBsd,
224    Hfs,
225    GptProtective,
226    EfiSystem,
227    Vmware,
228    Unknown(u8),
229}