Skip to main content

mbr_forensic/
provenance.rs

1//! Partitioner / era attribution from partition-table geometry.
2//!
3//! The first partition's start LBA is a durable provenance signal. Historically
4//! `fdisk` and Windows XP aligned the first partition to the end of the first
5//! track at **LBA 63** (cylinder alignment). From Windows Vista onward, and in
6//! modern Linux tooling, the first partition is aligned to a **1 MiB boundary
7//! (LBA 2048)** for performance on Advanced Format / SSD media. The transition
8//! is sharp enough to bracket the era — and an *odd* alignment is itself a hint
9//! of hand-editing.
10//!
11//! These are pure, conservative inferences exposed for callers to weight; they
12//! are deliberately not emitted as anomalies.
13
14use crate::boot_code::BootCodeId;
15
16/// Alignment class of a partition's start LBA.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize))]
19pub enum Alignment {
20    /// Aligned to a 1 MiB / 2048-sector boundary (modern convention).
21    Mib1,
22    /// Exactly LBA 63 — the legacy end-of-first-track convention.
23    LegacyTrack,
24    /// Neither convention — unusual; a possible hand-editing hint.
25    Unaligned,
26}
27
28/// Inferred era of the tool that created the partition layout.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize))]
31pub enum PartitioningEra {
32    /// 1 MiB alignment / Vista-plus boot code — modern tooling.
33    Modern,
34    /// LBA-63 alignment / legacy boot code — pre-2008 fdisk / Windows XP era.
35    LegacyCylinder,
36    /// Insufficient signal to attribute.
37    Unknown,
38}
39
40/// Classify a start LBA's alignment.
41///
42/// A 1 MiB boundary takes precedence (a partition at LBA 2048 is "modern"
43/// regardless of also being a multiple of 63 — it isn't, but the ordering keeps
44/// the intent explicit). LBA 0 is the MBR sector itself and classes as
45/// [`Alignment::Unaligned`] (no data partition starts there).
46#[must_use]
47pub fn classify_alignment(lba_start: u64) -> Alignment {
48    if lba_start != 0 && lba_start % 2048 == 0 {
49        Alignment::Mib1
50    } else if lba_start == 63 {
51        Alignment::LegacyTrack
52    } else {
53        Alignment::Unaligned
54    }
55}
56
57/// Infer the partitioning era from the first partition's start LBA, falling back
58/// to the boot-code identity when alignment is inconclusive.
59///
60/// `first_partition_lba` is the lowest start LBA among real (non-extended,
61/// non-protective) primary partitions, or `None` when there are none.
62#[must_use]
63pub fn infer_era(first_partition_lba: Option<u64>, boot: BootCodeId) -> PartitioningEra {
64    match first_partition_lba.map(classify_alignment) {
65        Some(Alignment::Mib1) => PartitioningEra::Modern,
66        Some(Alignment::LegacyTrack) => PartitioningEra::LegacyCylinder,
67        _ => era_from_boot_code(boot),
68    }
69}
70
71/// Tiebreaker: attribute era from the recognised boot loader when geometry is
72/// inconclusive.
73fn era_from_boot_code(boot: BootCodeId) -> PartitioningEra {
74    match boot {
75        BootCodeId::WindowsVista | BootCodeId::Windows7Plus | BootCodeId::Grub2 => {
76            PartitioningEra::Modern
77        }
78        BootCodeId::GrubLegacy => PartitioningEra::LegacyCylinder,
79        _ => PartitioningEra::Unknown,
80    }
81}