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}