Skip to main content

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}