Skip to main content

ahrightos_fs/
disk_format.rs

1//! Lightweight disk-format detection for safe BlkFS mounting.
2//!
3//! This module intentionally detects only the small set of signatures needed to
4//! avoid destructive classroom mistakes. It is **not** a partition driver and it
5//! does not parse ext4/FAT; it only reports that such formats are unsupported.
6//! Blank-device classification is also bounded: large all-zero prefixes are
7//! treated as `Unknown` instead of scanning an entire host disk image.
8
9use super::blkfs::{BlkFS, BLKFS_MIN_BLOCKS};
10use super::block_dev::{BlockDeviceHandle, BLOCK_SIZE};
11use super::file::FileError;
12
13/// Coarse block-device format classification used before BlkFS mount.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum DiskFormat {
16    /// Every block is zero, so compatibility mode may format BlkFS.
17    Blank,
18    /// Valid AhRightOS BlkFS superblock and root metadata.
19    BlkFS,
20    /// The BlkFS magic is present but the full superblock fails validation.
21    InvalidBlkFS,
22    /// Device is too small for the AhRightOS BlkFS layout.
23    TooSmall,
24    /// MBR signature was detected. AhRightOS does not mount partitions yet.
25    MasterBootRecord,
26    /// GPT header signature was detected. AhRightOS does not mount partitions yet.
27    GuidPartitionTable,
28    /// FAT boot-sector markers were detected. FAT is unsupported.
29    FatFamily,
30    /// ext-family superblock magic was detected. ext is unsupported.
31    ExtFamily,
32    /// Non-empty device with no supported/recognized AhRightOS BlkFS signature.
33    Unknown,
34}
35
36impl DiskFormat {
37    pub const fn description(self) -> &'static str {
38        match self {
39            DiskFormat::Blank => "blank device",
40            DiskFormat::BlkFS => "AhRightOS BlkFS",
41            DiskFormat::InvalidBlkFS => "invalid AhRightOS BlkFS",
42            DiskFormat::TooSmall => "device too small for BlkFS",
43            DiskFormat::MasterBootRecord => "MBR partition table",
44            DiskFormat::GuidPartitionTable => "GPT partition table",
45            DiskFormat::FatFamily => "FAT filesystem",
46            DiskFormat::ExtFamily => "ext-family filesystem",
47            DiskFormat::Unknown => "unknown disk format",
48        }
49    }
50}
51
52const BLKFS_MAGIC_LE: [u8; 4] = 0x424C_4B46u32.to_le_bytes();
53const BLANK_SCAN_MAX_BLOCKS: usize = BLKFS_MIN_BLOCKS * 64;
54
55/// Detect the broad format of a block device without mutating it.
56pub fn detect_disk_format(dev: &BlockDeviceHandle) -> Result<DiskFormat, FileError> {
57    if dev.block_count() < BLKFS_MIN_BLOCKS {
58        return Ok(DiskFormat::TooSmall);
59    }
60
61    let mut block0 = [0u8; BLOCK_SIZE];
62    dev.read_block(0, &mut block0)?;
63
64    if block0[..4] == BLKFS_MAGIC_LE {
65        return if BlkFS::open(dev.clone()).is_ok() {
66            Ok(DiskFormat::BlkFS)
67        } else {
68            Ok(DiskFormat::InvalidBlkFS)
69        };
70    }
71
72    if looks_like_gpt(dev)? {
73        return Ok(DiskFormat::GuidPartitionTable);
74    }
75    if looks_like_ext(dev)? {
76        return Ok(DiskFormat::ExtFamily);
77    }
78    if looks_like_fat(&block0) {
79        return Ok(DiskFormat::FatFamily);
80    }
81    if has_mbr_signature(&block0) {
82        return Ok(DiskFormat::MasterBootRecord);
83    }
84    if let Some(true) = device_is_blank(dev, &block0)? {
85        return Ok(DiskFormat::Blank);
86    }
87
88    Ok(DiskFormat::Unknown)
89}
90
91fn device_is_blank(
92    dev: &BlockDeviceHandle,
93    block0: &[u8; BLOCK_SIZE],
94) -> Result<Option<bool>, FileError> {
95    if !block0.iter().all(|&byte| byte == 0) {
96        return Ok(Some(false));
97    }
98    if dev.block_count() > BLANK_SCAN_MAX_BLOCKS {
99        return Ok(None);
100    }
101
102    let mut block = [0u8; BLOCK_SIZE];
103    for block_id in 1..dev.block_count() {
104        dev.read_block(block_id, &mut block)?;
105        if !block.iter().all(|&byte| byte == 0) {
106            return Ok(Some(false));
107        }
108    }
109    Ok(Some(true))
110}
111
112fn looks_like_gpt(dev: &BlockDeviceHandle) -> Result<bool, FileError> {
113    if dev.block_count() <= 1 {
114        return Ok(false);
115    }
116    let mut block1 = [0u8; BLOCK_SIZE];
117    dev.read_block(1, &mut block1)?;
118    Ok(block1[..8] == *b"EFI PART")
119}
120
121fn looks_like_ext(dev: &BlockDeviceHandle) -> Result<bool, FileError> {
122    if dev.block_count() <= 2 {
123        return Ok(false);
124    }
125    let mut block2 = [0u8; BLOCK_SIZE];
126    dev.read_block(2, &mut block2)?;
127    // ext superblock begins at byte 1024; magic lives at offset 56 in that
128    // superblock, i.e. block 2 offset 56 for 512-byte AhRightOS block devices.
129    Ok(block2[56] == 0x53 && block2[57] == 0xEF)
130}
131
132fn looks_like_fat(block0: &[u8; BLOCK_SIZE]) -> bool {
133    let has_boot_signature = has_mbr_signature(block0);
134    let fat12_or_16 = block0[54..59] == *b"FAT12" || block0[54..59] == *b"FAT16";
135    let fat32 = block0[82..87] == *b"FAT32";
136    has_boot_signature && (fat12_or_16 || fat32)
137}
138
139fn has_mbr_signature(block0: &[u8; BLOCK_SIZE]) -> bool {
140    block0[510] == 0x55 && block0[511] == 0xAA
141}