mbr_forensic/gap.rs
1//! Unpartitioned LBA space analysis.
2
3/// A region of unpartitioned disk space.
4#[derive(Debug, Clone, PartialEq, Eq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize))]
6pub struct Gap {
7 /// First LBA of the unpartitioned region.
8 pub lba_start: u64,
9 /// Last LBA of the unpartitioned region (inclusive).
10 pub lba_end: u64,
11 /// Size in bytes (`(lba_end - lba_start + 1) * sector_size`).
12 pub byte_size: u64,
13 /// Why this gap exists in the layout.
14 pub kind: GapKind,
15}
16
17/// Classification of a gap's position on the disk.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20pub enum GapKind {
21 /// Space between LBA 0 (MBR sector) and the first partition.
22 PrePartition,
23 /// Space between two adjacent partitions.
24 Between,
25 /// Space after the last partition, before the end of the disk.
26 PostPartition,
27}
28
29/// Compute all unpartitioned gaps in a set of sorted partition extents.
30///
31/// `extents` should be `(lba_start, lba_end)` inclusive pairs, sorted by
32/// `lba_start`. `disk_last_lba` is the inclusive last LBA of the disk.
33/// `first_usable` is the first LBA available for partition data (typically 1
34/// for MBR, but callers may pass the real first-available value).
35#[must_use]
36pub fn compute_gaps(
37 extents: &[(u64, u64)],
38 first_usable: u64,
39 disk_last_lba: u64,
40 sector_size: u64,
41) -> Vec<Gap> {
42 let mut gaps = Vec::new();
43 let mut cursor = first_usable;
44
45 for &(start, end) in extents {
46 if start > cursor {
47 let kind = if cursor == first_usable {
48 GapKind::PrePartition
49 } else {
50 GapKind::Between
51 };
52 let lba_end = start - 1;
53 gaps.push(Gap {
54 lba_start: cursor,
55 lba_end,
56 byte_size: (lba_end - cursor + 1) * sector_size,
57 kind,
58 });
59 }
60 if end >= cursor {
61 cursor = end + 1;
62 }
63 }
64
65 if cursor <= disk_last_lba {
66 gaps.push(Gap {
67 lba_start: cursor,
68 lba_end: disk_last_lba,
69 byte_size: (disk_last_lba - cursor + 1) * sector_size,
70 kind: GapKind::PostPartition,
71 });
72 }
73
74 gaps
75}