use crate::error::Result;
use crate::probe::Partition;
use crate::BlockRead;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FsKind {
Ext { version: ExtVersion },
Ntfs,
ExFat,
Fat32,
Fat16,
HfsPlus,
Apfs,
LinuxSwap,
Iso9660,
Squashfs,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExtVersion {
Ext2OrAny,
Ext3,
Ext4,
}
pub fn sniff(dev: &dyn BlockRead, partition: &Partition) -> Result<FsKind> {
let want = std::cmp::min(0x8800u64, partition.length) as usize;
let mut buf = vec![0u8; want];
dev.read_at(partition.start, &mut buf)?;
Ok(classify(&buf))
}
pub fn classify(buf: &[u8]) -> FsKind {
if buf.len() >= 4 && &buf[0..4] == b"hsqs" {
return FsKind::Squashfs;
}
if buf.len() >= 512 && buf[510] == 0x55 && buf[511] == 0xAA {
let oem = &buf[3..11];
if oem == b"NTFS " {
return FsKind::Ntfs;
}
if oem == b"EXFAT " {
return FsKind::ExFat;
}
if buf.len() >= 0x5A {
let fat32_tag = &buf[0x52..0x5A];
if fat32_tag == b"FAT32 " {
return FsKind::Fat32;
}
}
if buf.len() >= 0x3E {
let fat16_tag = &buf[0x36..0x3E];
if fat16_tag == b"FAT16 " || fat16_tag == b"FAT12 " {
return FsKind::Fat16;
}
}
}
if buf.len() >= 1082 {
let magic = u16::from_le_bytes([buf[1080], buf[1081]]);
if magic == 0xEF53 {
return FsKind::Ext {
version: classify_ext(buf),
};
}
}
if buf.len() >= 1026 {
let sig = &buf[1024..1026];
if sig == b"H+" || sig == b"HX" {
return FsKind::HfsPlus;
}
}
if buf.len() >= 36 && &buf[32..36] == b"NXSB" {
return FsKind::Apfs;
}
for page in [4096usize, 8192, 16384, 32768, 65536] {
if buf.len() >= page {
let off = page - 10;
if &buf[off..off + 10] == b"SWAPSPACE2" {
return FsKind::LinuxSwap;
}
}
}
if buf.len() >= 0x8006 && &buf[0x8001..0x8006] == b"CD001" {
return FsKind::Iso9660;
}
FsKind::Unknown
}
fn classify_ext(buf: &[u8]) -> ExtVersion {
let sb = 1024usize;
if buf.len() < sb + 0x68 {
return ExtVersion::Ext2OrAny;
}
let feature_compat = u32::from_le_bytes(buf[sb + 0x5C..sb + 0x60].try_into().unwrap());
let feature_incompat = u32::from_le_bytes(buf[sb + 0x60..sb + 0x64].try_into().unwrap());
const EXT3_FEATURE_COMPAT_HAS_JOURNAL: u32 = 0x4;
const EXT4_INCOMPAT_MASK: u32 = 0x040 | 0x080 | 0x100 | 0x200 | 0x400 | 0x1000 | 0x2000 | 0x4000 | 0x8000;
if feature_incompat & EXT4_INCOMPAT_MASK != 0 {
return ExtVersion::Ext4;
}
if feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL != 0 {
return ExtVersion::Ext3;
}
ExtVersion::Ext2OrAny
}