1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize))]
6pub struct Chs {
7 pub cylinder: u16,
8 pub head: u8,
9 pub sector: u8,
10}
11
12impl Chs {
13 #[must_use]
22 pub fn from_bytes(b: [u8; 3]) -> Self {
23 let head = b[0];
24 let sector = b[1] & 0x3F;
25 let cylinder = ((b[1] as u16 & 0xC0) << 2) | b[2] as u16;
26 Chs {
27 cylinder,
28 head,
29 sector,
30 }
31 }
32
33 #[must_use]
37 pub fn to_lba(self, heads_per_cylinder: u8, sectors_per_track: u8) -> Option<u32> {
38 if self.sector == 0 {
39 return None;
40 }
41 let hpc = heads_per_cylinder as u32;
42 let spt = sectors_per_track as u32;
43 if hpc == 0 || spt == 0 {
44 return None;
45 }
46 Some(
47 (self.cylinder as u32) * hpc * spt
48 + (self.head as u32) * spt
49 + (self.sector as u32 - 1),
50 )
51 }
52
53 #[must_use]
58 pub fn is_unused(self) -> bool {
59 self.cylinder == 0 && self.head == 0 && self.sector == 0
60 }
61
62 #[must_use]
68 pub fn is_overflow_marker(self) -> bool {
69 self.cylinder == CHS_MAX_CYLINDER
70 && self.sector == STD_SECTORS_PER_TRACK
71 && (self.head == 254 || self.head == 255)
72 }
73}
74
75pub const STD_HEADS_PER_CYL: u8 = 255;
79pub const STD_SECTORS_PER_TRACK: u8 = 63;
81const CHS_MAX_CYLINDER: u16 = 1023;
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize))]
87pub enum ChsConsistency {
88 Consistent,
90 Unused,
92 Inconsistent,
94}
95
96#[must_use]
101fn max_chs_addressable_lba(hpc: u8, spt: u8) -> u64 {
102 1024u64 * hpc as u64 * spt as u64 - 1
103}
104
105#[must_use]
112pub fn chs_consistency(chs: Chs, lba: u32, hpc: u8, spt: u8) -> ChsConsistency {
113 if chs.is_unused() {
114 return ChsConsistency::Unused;
115 }
116 if chs.is_overflow_marker() {
117 return ChsConsistency::Consistent;
118 }
119 if u64::from(lba) > max_chs_addressable_lba(hpc, spt) {
120 return ChsConsistency::Inconsistent;
123 }
124 match chs.to_lba(hpc, spt) {
125 Some(translated) if translated == lba => ChsConsistency::Consistent,
126 _ => ChsConsistency::Inconsistent,
127 }
128}
129
130#[derive(Debug, Clone, PartialEq, Eq)]
132#[cfg_attr(feature = "serde", derive(serde::Serialize))]
133pub struct PartitionEntry {
134 pub status: u8,
136 pub chs_first: Chs,
138 pub type_code: TypeCode,
140 pub chs_last: Chs,
142 pub lba_start: u32,
144 pub lba_count: u32,
146}
147
148impl PartitionEntry {
149 #[must_use]
151 pub fn from_bytes(b: &[u8; 16]) -> Self {
152 PartitionEntry {
153 status: b[0],
154 chs_first: Chs::from_bytes([b[1], b[2], b[3]]),
155 type_code: TypeCode(b[4]),
156 chs_last: Chs::from_bytes([b[5], b[6], b[7]]),
157 lba_start: u32::from_le_bytes([b[8], b[9], b[10], b[11]]),
158 lba_count: u32::from_le_bytes([b[12], b[13], b[14], b[15]]),
159 }
160 }
161
162 #[must_use]
164 pub fn is_empty(&self) -> bool {
165 self.type_code.is_empty() && self.lba_start == 0 && self.lba_count == 0
166 }
167
168 #[must_use]
170 pub fn is_bootable(&self) -> bool {
171 self.status == 0x80
172 }
173
174 #[must_use]
176 pub fn lba_end(&self) -> u32 {
177 self.lba_start
178 .saturating_add(self.lba_count)
179 .saturating_sub(1)
180 }
181
182 #[must_use]
184 pub fn is_extended(&self) -> bool {
185 self.type_code.is_extended()
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
191#[cfg_attr(feature = "serde", derive(serde::Serialize))]
192pub struct TypeCode(pub u8);
193
194impl TypeCode {
195 #[must_use]
200 pub fn name(self) -> &'static str {
201 forensicnomicon::partition_types::type_name(self.0).unwrap_or("Unknown")
202 }
203
204 #[must_use]
206 pub fn family(self) -> PartitionFamily {
207 match self.0 {
208 0x00 => PartitionFamily::Empty,
209 0x01 | 0x11 => PartitionFamily::Fat12,
210 0x04 | 0x06 | 0x0E | 0x14 | 0x16 | 0x1E => PartitionFamily::Fat16,
211 0x0B | 0x0C | 0x1B | 0x1C => PartitionFamily::Fat32,
212 0x07 | 0x17 | 0x87 => PartitionFamily::Ntfs,
213 0x05 | 0x0F | 0x85 => PartitionFamily::ExtendedMbr,
214 0x82 => PartitionFamily::LinuxSwap,
215 0x83 => PartitionFamily::Linux,
216 0x8E => PartitionFamily::LinuxLvm,
217 0xFD => PartitionFamily::LinuxRaid,
218 0x27 => PartitionFamily::WindowsRecovery,
219 0x42 => PartitionFamily::WindowsDynamic,
220 0xA5 => PartitionFamily::FreeBsd,
221 0xA6 => PartitionFamily::OpenBsd,
222 0xA9 => PartitionFamily::NetBsd,
223 0xAF | 0xAB => PartitionFamily::Hfs,
224 0xEE => PartitionFamily::GptProtective,
225 0xEF => PartitionFamily::EfiSystem,
226 0xFB | 0xFC => PartitionFamily::Vmware,
227 _ => PartitionFamily::Unknown(self.0),
228 }
229 }
230
231 #[must_use]
233 pub fn is_empty(self) -> bool {
234 self.0 == 0x00
235 }
236
237 #[must_use]
239 pub fn is_extended(self) -> bool {
240 matches!(self.0, 0x05 | 0x0F | 0x85)
241 }
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq)]
246#[cfg_attr(feature = "serde", derive(serde::Serialize))]
247pub enum PartitionFamily {
248 Empty,
249 Fat12,
250 Fat16,
251 Fat32,
252 Ntfs,
253 ExtendedMbr,
254 LinuxSwap,
255 Linux,
256 LinuxLvm,
257 LinuxRaid,
258 WindowsRecovery,
259 WindowsDynamic,
260 FreeBsd,
261 OpenBsd,
262 NetBsd,
263 Hfs,
264 GptProtective,
265 EfiSystem,
266 Vmware,
267 Unknown(u8),
268}