Skip to main content

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}