use super::blkfs::{BlkFS, BLKFS_MIN_BLOCKS};
use super::block_dev::{BlockDeviceHandle, BLOCK_SIZE};
use super::file::FileError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiskFormat {
Blank,
BlkFS,
InvalidBlkFS,
TooSmall,
MasterBootRecord,
GuidPartitionTable,
FatFamily,
ExtFamily,
Unknown,
}
impl DiskFormat {
pub const fn description(self) -> &'static str {
match self {
DiskFormat::Blank => "blank device",
DiskFormat::BlkFS => "AhRightOS BlkFS",
DiskFormat::InvalidBlkFS => "invalid AhRightOS BlkFS",
DiskFormat::TooSmall => "device too small for BlkFS",
DiskFormat::MasterBootRecord => "MBR partition table",
DiskFormat::GuidPartitionTable => "GPT partition table",
DiskFormat::FatFamily => "FAT filesystem",
DiskFormat::ExtFamily => "ext-family filesystem",
DiskFormat::Unknown => "unknown disk format",
}
}
}
const BLKFS_MAGIC_LE: [u8; 4] = 0x424C_4B46u32.to_le_bytes();
const BLANK_SCAN_MAX_BLOCKS: usize = BLKFS_MIN_BLOCKS * 64;
pub fn detect_disk_format(dev: &BlockDeviceHandle) -> Result<DiskFormat, FileError> {
if dev.block_count() < BLKFS_MIN_BLOCKS {
return Ok(DiskFormat::TooSmall);
}
let mut block0 = [0u8; BLOCK_SIZE];
dev.read_block(0, &mut block0)?;
if block0[..4] == BLKFS_MAGIC_LE {
return if BlkFS::open(dev.clone()).is_ok() {
Ok(DiskFormat::BlkFS)
} else {
Ok(DiskFormat::InvalidBlkFS)
};
}
if looks_like_gpt(dev)? {
return Ok(DiskFormat::GuidPartitionTable);
}
if looks_like_ext(dev)? {
return Ok(DiskFormat::ExtFamily);
}
if looks_like_fat(&block0) {
return Ok(DiskFormat::FatFamily);
}
if has_mbr_signature(&block0) {
return Ok(DiskFormat::MasterBootRecord);
}
if let Some(true) = device_is_blank(dev, &block0)? {
return Ok(DiskFormat::Blank);
}
Ok(DiskFormat::Unknown)
}
fn device_is_blank(
dev: &BlockDeviceHandle,
block0: &[u8; BLOCK_SIZE],
) -> Result<Option<bool>, FileError> {
if !block0.iter().all(|&byte| byte == 0) {
return Ok(Some(false));
}
if dev.block_count() > BLANK_SCAN_MAX_BLOCKS {
return Ok(None);
}
let mut block = [0u8; BLOCK_SIZE];
for block_id in 1..dev.block_count() {
dev.read_block(block_id, &mut block)?;
if !block.iter().all(|&byte| byte == 0) {
return Ok(Some(false));
}
}
Ok(Some(true))
}
fn looks_like_gpt(dev: &BlockDeviceHandle) -> Result<bool, FileError> {
if dev.block_count() <= 1 {
return Ok(false);
}
let mut block1 = [0u8; BLOCK_SIZE];
dev.read_block(1, &mut block1)?;
Ok(block1[..8] == *b"EFI PART")
}
fn looks_like_ext(dev: &BlockDeviceHandle) -> Result<bool, FileError> {
if dev.block_count() <= 2 {
return Ok(false);
}
let mut block2 = [0u8; BLOCK_SIZE];
dev.read_block(2, &mut block2)?;
Ok(block2[56] == 0x53 && block2[57] == 0xEF)
}
fn looks_like_fat(block0: &[u8; BLOCK_SIZE]) -> bool {
let has_boot_signature = has_mbr_signature(block0);
let fat12_or_16 = block0[54..59] == *b"FAT12" || block0[54..59] == *b"FAT16";
let fat32 = block0[82..87] == *b"FAT32";
has_boot_signature && (fat12_or_16 || fat32)
}
fn has_mbr_signature(block0: &[u8; BLOCK_SIZE]) -> bool {
block0[510] == 0x55 && block0[511] == 0xAA
}