partitions/probe.rs
1//! Top-level probe: try GPT first, fall back to MBR. Logical (extended-MBR)
2//! chains are not walked yet — the four primary entries are reported as-is.
3
4use crate::error::{Error, Result};
5use crate::gpt;
6use crate::mbr;
7use crate::BlockRead;
8
9/// Which on-disk partition table produced a result.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum TableKind {
12 Gpt,
13 Mbr,
14}
15
16/// One partition. `start`/`length` are byte values relative to the start of
17/// the whole device.
18#[derive(Debug, Clone)]
19pub struct Partition {
20 pub start: u64,
21 pub length: u64,
22 pub kind: PartitionKind,
23 pub label: Option<String>,
24 pub uuid: Option<[u8; 16]>,
25}
26
27/// The on-disk type-tag for the partition. For GPT this is the type GUID +
28/// the 64-bit attributes field; for MBR it's the one-byte type code + the
29/// active/boot flag. `Whole` is reserved for the (future) "no partition
30/// table" probe result.
31///
32/// Callers that only need a "did the firmware mark this as bootable?" answer
33/// should use [`Partition::is_bootable`] instead of inspecting bits directly.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum PartitionKind {
36 /// `type_guid`: 16-byte on-disk partition type GUID.
37 /// `attributes`: 64-bit attributes field from entry offset +48. Bit 0 =
38 /// required partition, bit 2 = legacy BIOS bootable; see
39 /// [`crate::gpt::attr`] for the full set of named bits.
40 Gpt {
41 type_guid: [u8; 16],
42 attributes: u64,
43 },
44 /// `type_byte`: MBR partition type code.
45 /// `active`: bit 0x80 of the entry's status byte (offset +0). Marks the
46 /// "active" / "bootable" partition that legacy BIOS firmware boots.
47 Mbr {
48 type_byte: u8,
49 active: bool,
50 },
51 Whole,
52}
53
54impl Partition {
55 /// True when this partition is marked bootable in the on-disk table.
56 ///
57 /// - MBR: the active flag is set on the entry (legacy BIOS boots from
58 /// the active partition).
59 /// - GPT: the type GUID is the EFI System Partition GUID *or* the
60 /// `LEGACY_BIOS_BOOTABLE` attribute bit is set.
61 /// - Whole: never (no firmware-level bootability for table-less media).
62 pub fn is_bootable(&self) -> bool {
63 match self.kind {
64 PartitionKind::Mbr { active, .. } => active,
65 PartitionKind::Gpt {
66 type_guid,
67 attributes,
68 } => {
69 type_guid == crate::gpt::type_guids::EFI_SYSTEM
70 || (attributes & crate::gpt::attr::LEGACY_BIOS_BOOTABLE) != 0
71 }
72 PartitionKind::Whole => false,
73 }
74 }
75}
76
77/// Probe the device. Returns `(table_kind, partitions)` on success.
78///
79/// Order of attempts:
80/// 1. GPT primary header at LBA 1. If signature matches and CRC validates,
81/// parse the entry array.
82/// 2. MBR at LBA 0. The protective-MBR case (single 0xEE entry) means GPT
83/// was supposed to be there but its parse failed — propagate the GPT
84/// error rather than reporting a single GPT-protective MBR partition.
85pub fn probe(dev: &dyn BlockRead) -> Result<(TableKind, Vec<Partition>)> {
86 // --- LBA 0 + LBA 1: enough to decide which table type ---
87 let mut lba0 = [0u8; 512];
88 let mut lba1 = [0u8; 512];
89 dev.read_at(0, &mut lba0)?;
90 if dev.size_bytes() >= 1024 {
91 dev.read_at(512, &mut lba1)?;
92 }
93
94 let has_mbr_sig = lba0[510] == 0x55 && lba0[511] == 0xAA;
95 let gpt_sig = &lba1[0..8];
96 let has_gpt_sig = gpt_sig == gpt::SIGNATURE;
97
98 if has_gpt_sig {
99 let parts = gpt::parse(dev, &lba1)?;
100 return Ok((TableKind::Gpt, parts));
101 }
102
103 if has_mbr_sig {
104 // Detect protective-MBR (single 0xEE entry). Per the spec this means
105 // the disk *should* be GPT — but GPT signature was missing, so the
106 // table is broken. Surface that explicitly.
107 if mbr::is_protective(&lba0) {
108 return Err(Error::GptCorrupt(
109 "protective MBR present but no GPT signature",
110 ));
111 }
112 let parts = mbr::parse(&lba0)?;
113 return Ok((TableKind::Mbr, parts));
114 }
115
116 Err(Error::NoPartitionTable)
117}