1#[cfg(feature = "alloc")]
9extern crate alloc;
10
11#[cfg(feature = "alloc")]
12use alloc::vec::Vec;
13
14#[cfg(feature = "alloc")]
15use crate::error::{Error, Result};
16use crate::gpt::Guid;
17#[cfg(feature = "alloc")]
18use crate::gpt::{GptHeader, GptPartitionEntry};
19use crate::hybrid::is_hybrid_mbr;
20use crate::mbr::MasterBootRecord;
21#[cfg(feature = "alloc")]
22use endian_num::Le;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum PartitionSchemeType {
27 Mbr,
29 Gpt,
31 Hybrid,
33}
34
35impl core::fmt::Display for PartitionSchemeType {
36 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
37 match self {
38 Self::Mbr => write!(f, "MBR"),
39 Self::Gpt => write!(f, "GPT"),
40 Self::Hybrid => write!(f, "Hybrid MBR"),
41 }
42 }
43}
44
45#[derive(Debug, Clone, Copy)]
47pub struct PartitionInfo {
48 pub index: usize,
50 pub start_lba: u64,
52 pub end_lba: u64,
54 pub size_sectors: u64,
56 pub bootable: bool,
58 pub partition_type: PartitionType,
60}
61
62#[derive(Debug, Clone, Copy)]
64pub enum PartitionType {
65 Mbr(u8),
67 Gpt(Guid),
69}
70
71impl PartitionInfo {
72 pub const fn size_bytes(&self) -> u64 {
77 self.size_sectors.saturating_mul(512)
78 }
79
80 pub const fn size_bytes_with_sector_size(&self, sector_size: u32) -> u64 {
85 self.size_sectors.saturating_mul(sector_size as u64)
86 }
87}
88
89#[cfg(feature = "alloc")]
91#[derive(Debug, Clone)]
92pub struct GptDisk {
93 pub primary_header: GptHeader,
95 pub backup_header: GptHeader,
97 pub entries: Vec<GptPartitionEntry>,
99 pub block_size: u32,
101}
102
103#[cfg(feature = "alloc")]
104impl GptDisk {
105 pub const DEFAULT_ENTRY_COUNT: u32 = 128;
107
108 pub fn new(disk_sectors: u64, block_size: u32) -> Self {
110 let entry_count = Self::DEFAULT_ENTRY_COUNT;
111 let entry_size = core::mem::size_of::<GptPartitionEntry>() as u32;
112 let entries_per_sector = (block_size / entry_size).max(1);
113 let entry_sectors = entry_count.div_ceil(entries_per_sector);
114
115 let first_usable = 2 + entry_sectors as u64;
117 let last_usable = disk_sectors.saturating_sub(2 + entry_sectors as u64);
119
120 let disk_guid = {
121 #[cfg(feature = "rand")]
122 {
123 Guid::generate_v4()
124 }
125 #[cfg(not(feature = "rand"))]
126 {
127 Guid::UNUSED
128 }
129 };
130
131 #[cfg_attr(not(feature = "crc"), allow(unused_mut))]
132 let mut primary_header = GptHeader {
133 signature: GptHeader::SIGNATURE,
134 revision: Le::<u32>::from_ne(GptHeader::REVISION_1_0),
135 header_size: Le::<u32>::from_ne(GptHeader::STANDARD_HEADER_SIZE),
136 header_crc32: Le::<u32>::from_ne(0),
137 reserved: Le::<u32>::from_ne(0),
138 my_lba: Le::<u64>::from_ne(1),
139 alternate_lba: Le::<u64>::from_ne(disk_sectors.saturating_sub(1)),
140 first_usable_lba: Le::<u64>::from_ne(first_usable),
141 last_usable_lba: Le::<u64>::from_ne(last_usable),
142 disk_guid,
143 partition_entry_lba: Le::<u64>::from_ne(2),
144 num_partition_entries: Le::<u32>::from_ne(entry_count),
145 size_of_partition_entry: Le::<u32>::from_ne(entry_size),
146 partition_entry_array_crc32: Le::<u32>::from_ne(0),
147 };
148
149 #[cfg_attr(not(feature = "crc"), allow(unused_mut))]
150 let mut backup_header = GptHeader {
151 my_lba: Le::<u64>::from_ne(disk_sectors.saturating_sub(1)),
152 alternate_lba: Le::<u64>::from_ne(1),
153 partition_entry_lba: Le::<u64>::from_ne(
154 disk_sectors.saturating_sub(1 + entry_sectors as u64),
155 ),
156 ..primary_header
157 };
158
159 let entries = alloc::vec![GptPartitionEntry::default(); entry_count as usize];
160
161 #[cfg(feature = "crc")]
163 {
164 let entries_crc = crate::gpt::calculate_partition_array_crc32(&entries);
165 primary_header.partition_entry_array_crc32 = Le::<u32>::from_ne(entries_crc);
166 backup_header.partition_entry_array_crc32 = Le::<u32>::from_ne(entries_crc);
167 primary_header.update_crc32();
168 backup_header.update_crc32();
169 }
170
171 Self {
172 primary_header,
173 backup_header,
174 entries,
175 block_size,
176 }
177 }
178
179 pub fn partition_count(&self) -> usize {
181 self.entries.iter().filter(|e| !e.is_unused()).count()
182 }
183
184 pub fn partitions(&self) -> impl Iterator<Item = (usize, &GptPartitionEntry)> {
186 self.entries
187 .iter()
188 .enumerate()
189 .filter(|(_, e)| !e.is_unused())
190 }
191
192 pub fn add_partition(&mut self, entry: GptPartitionEntry) -> Result<usize> {
196 for (i, slot) in self.entries.iter_mut().enumerate() {
197 if slot.is_unused() {
198 *slot = entry;
199 self.update_crcs();
200 return Ok(i);
201 }
202 }
203 Err(Error::TooManyPartitions {
204 max: self.entries.len(),
205 requested: self.entries.len() + 1,
206 })
207 }
208
209 pub fn validate(&self) -> Result<()> {
211 if !self.primary_header.has_valid_signature() {
213 return Err(Error::InvalidGptSignature {
214 found: self.primary_header.signature,
215 });
216 }
217
218 #[cfg(feature = "crc")]
220 {
221 if !self.primary_header.verify_crc32() {
222 return Err(Error::GptHeaderCrcMismatch {
223 expected: self.primary_header.header_crc32.to_ne(),
224 actual: self.primary_header.calculate_crc32(),
225 });
226 }
227
228 let entries_crc = crate::gpt::calculate_partition_array_crc32(&self.entries);
229 if self.primary_header.partition_entry_array_crc32.to_ne() != entries_crc {
230 return Err(Error::GptEntriesCrcMismatch {
231 expected: self.primary_header.partition_entry_array_crc32.to_ne(),
232 actual: entries_crc,
233 });
234 }
235 }
236
237 let used: Vec<_> = self.partitions().collect();
239 for i in 0..used.len() {
240 for j in (i + 1)..used.len() {
241 let (idx1, p1) = used[i];
242 let (idx2, p2) = used[j];
243 if p1.first_lba.to_ne() <= p2.last_lba.to_ne()
244 && p2.first_lba.to_ne() <= p1.last_lba.to_ne()
245 {
246 let overlap_start = p1.first_lba.to_ne().max(p2.first_lba.to_ne());
247 let overlap_end = p1.last_lba.to_ne().min(p2.last_lba.to_ne());
248 return Err(Error::PartitionOverlap {
249 index1: idx1,
250 index2: idx2,
251 overlap_start,
252 overlap_end,
253 });
254 }
255 }
256 }
257
258 for (idx, entry) in self.partitions() {
260 if entry.first_lba.to_ne() < self.primary_header.first_usable_lba.to_ne()
261 || entry.last_lba.to_ne() > self.primary_header.last_usable_lba.to_ne()
262 {
263 return Err(Error::PartitionOutOfBounds {
264 index: idx,
265 partition_end: entry.last_lba.to_ne(),
266 disk_end: self.primary_header.last_usable_lba.to_ne(),
267 });
268 }
269 }
270
271 Ok(())
272 }
273
274 #[cfg(feature = "crc")]
276 pub fn update_crcs(&mut self) {
277 let entries_crc = crate::gpt::calculate_partition_array_crc32(&self.entries);
278 self.primary_header.partition_entry_array_crc32 = Le::<u32>::from_ne(entries_crc);
279 self.backup_header.partition_entry_array_crc32 = Le::<u32>::from_ne(entries_crc);
280 self.primary_header.update_crc32();
281 self.backup_header.update_crc32();
282 }
283
284 #[cfg(not(feature = "crc"))]
285 pub fn update_crcs(&mut self) {
290 }
292
293 pub fn create_protective_mbr(&self) -> MasterBootRecord {
295 let disk_sectors = self.backup_header.my_lba.to_ne().saturating_add(1);
296 MasterBootRecord::protective(disk_sectors)
297 }
298}
299
300#[cfg(feature = "alloc")]
302#[derive(Debug, Clone)]
303pub enum PartitionTable {
304 Mbr(MasterBootRecord),
306 Gpt {
308 protective_mbr: MasterBootRecord,
310 gpt: GptDisk,
312 },
313 Hybrid {
315 hybrid_mbr: MasterBootRecord,
317 gpt: GptDisk,
319 },
320}
321
322#[cfg(feature = "alloc")]
323impl PartitionTable {
324 pub fn new_mbr() -> Self {
326 Self::Mbr(MasterBootRecord::default())
327 }
328
329 pub fn new_gpt(disk_sectors: u64, block_size: u32) -> Self {
331 let gpt = GptDisk::new(disk_sectors, block_size);
332 let protective_mbr = gpt.create_protective_mbr();
333 Self::Gpt {
334 protective_mbr,
335 gpt,
336 }
337 }
338
339 pub fn scheme_type(&self) -> PartitionSchemeType {
341 match self {
342 Self::Mbr(_) => PartitionSchemeType::Mbr,
343 Self::Gpt { .. } => PartitionSchemeType::Gpt,
344 Self::Hybrid { .. } => PartitionSchemeType::Hybrid,
345 }
346 }
347
348 pub fn partitions(&self) -> Vec<PartitionInfo> {
350 match self {
351 Self::Mbr(mbr) => {
352 let pt = mbr.get_partition_table();
353 pt.partitions
354 .iter()
355 .enumerate()
356 .filter(|(_, p)| !p.is_empty())
357 .map(|(i, p)| PartitionInfo {
358 index: i,
359 start_lba: p.start_lba.to_ne() as u64,
360 end_lba: p.end_lba() as u64,
361 size_sectors: p.sector_count.to_ne() as u64,
362 bootable: p.is_bootable(),
363 partition_type: PartitionType::Mbr(p.part_type),
364 })
365 .collect()
366 }
367 Self::Gpt { gpt, .. } | Self::Hybrid { gpt, .. } => gpt
368 .partitions()
369 .map(|(i, e)| PartitionInfo {
370 index: i,
371 start_lba: e.first_lba.to_ne(),
372 end_lba: e.last_lba.to_ne(),
373 size_sectors: e.size_sectors(),
374 bootable: e.attributes.is_legacy_bios_bootable(),
375 partition_type: PartitionType::Gpt(e.type_guid),
376 })
377 .collect(),
378 }
379 }
380
381 pub fn validate(&self) -> Result<()> {
383 match self {
384 Self::Mbr(mbr) => {
385 if !mbr.has_valid_signature() {
386 return Err(Error::InvalidMbrSignature {
387 found: mbr.signature,
388 });
389 }
390 let pt = mbr.get_partition_table();
391 if !pt.is_valid() {
392 return Err(Error::InvalidHybridMbr {
393 reason: "invalid MBR partition table",
394 });
395 }
396 Ok(())
397 }
398 Self::Gpt {
399 protective_mbr,
400 gpt,
401 } => {
402 if !protective_mbr.has_valid_signature() {
403 return Err(Error::InvalidMbrSignature {
404 found: protective_mbr.signature,
405 });
406 }
407 let pt = protective_mbr.get_partition_table();
408 if !pt.is_protective() {
409 return Err(Error::NoProtectiveMbr);
410 }
411 gpt.validate()
412 }
413 Self::Hybrid { hybrid_mbr, gpt } => {
414 if !hybrid_mbr.has_valid_signature() {
415 return Err(Error::InvalidMbrSignature {
416 found: hybrid_mbr.signature,
417 });
418 }
419 if !is_hybrid_mbr(hybrid_mbr) {
420 return Err(Error::InvalidHybridMbr {
421 reason: "not a valid hybrid MBR",
422 });
423 }
424 gpt.validate()
425 }
426 }
427 }
428}
429
430pub fn detect_scheme_from_mbr(mbr: &MasterBootRecord) -> PartitionSchemeType {
435 if !mbr.has_valid_signature() {
436 return PartitionSchemeType::Mbr;
437 }
438
439 let pt = mbr.get_partition_table();
440 if is_hybrid_mbr(mbr) {
441 PartitionSchemeType::Hybrid
442 } else if pt.is_protective() {
443 PartitionSchemeType::Gpt
444 } else {
445 PartitionSchemeType::Mbr
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452 use crate::mbr::MbrPartition;
453
454 #[cfg(feature = "alloc")]
455 #[test]
456 fn test_gpt_disk_creation() {
457 let disk = GptDisk::new(1000000, 512);
458 assert!(disk.primary_header.has_valid_signature());
459 assert_eq!(disk.entries.len(), 128);
460 assert_eq!(disk.partition_count(), 0);
461 }
462
463 #[cfg(feature = "alloc")]
464 #[test]
465 fn test_scheme_detection() {
466 let mbr = MasterBootRecord::default();
468 assert_eq!(detect_scheme_from_mbr(&mbr), PartitionSchemeType::Mbr);
469
470 let protective = MasterBootRecord::protective(1000000);
472 assert_eq!(
473 detect_scheme_from_mbr(&protective),
474 PartitionSchemeType::Gpt
475 );
476
477 let mut hybrid = protective;
479 hybrid.with_partition_table(|pt| {
480 pt[1] = MbrPartition::new(crate::mbr::MbrPartitionType::Fat32, 2048, 100000);
481 });
482 assert_eq!(detect_scheme_from_mbr(&hybrid), PartitionSchemeType::Hybrid);
483 }
484
485 #[cfg(feature = "alloc")]
486 #[test]
487 fn test_partition_scheme_new_gpt() {
488 let scheme = PartitionTable::new_gpt(1000000, 512);
489 assert_eq!(scheme.scheme_type(), PartitionSchemeType::Gpt);
490 assert!(scheme.validate().is_ok());
491 assert!(scheme.partitions().is_empty());
492 }
493}