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)]
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}