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