#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct FsSignature {
pub name: &'static str,
pub offset: usize,
pub magic: &'static [u8],
}
pub const FILESYSTEM_SIGNATURES: &[FsSignature] = &[
FsSignature { name: "ext2/3/4", offset: 0x438, magic: &[0x53, 0xEF] },
FsSignature { name: "NTFS", offset: 3, magic: b"NTFS " },
FsSignature { name: "exFAT", offset: 3, magic: b"EXFAT " },
FsSignature { name: "XFS", offset: 0, magic: b"XFSB" },
FsSignature { name: "LUKS", offset: 0, magic: b"LUKS\xba\xbe" },
FsSignature { name: "APFS", offset: 32, magic: b"NXSB" },
FsSignature { name: "FAT32", offset: 0x52, magic: b"FAT32 " },
FsSignature { name: "FAT16", offset: 0x36, magic: b"FAT16 " },
FsSignature { name: "FAT12", offset: 0x36, magic: b"FAT12 " },
FsSignature { name: "Linux swap", offset: 0xFF6, magic: b"SWAPSPACE2" },
FsSignature { name: "LVM2", offset: 0, magic: b"LABELONE" },
FsSignature { name: "LVM2", offset: 512, magic: b"LABELONE" },
FsSignature { name: "ISO 9660", offset: 0x8001, magic: b"CD001" },
FsSignature { name: "HFS+", offset: 0x400, magic: b"H+" },
FsSignature { name: "Btrfs", offset: 65600, magic: b"_BHRfS_M" },
];
#[must_use]
pub fn detect_name(data: &[u8]) -> Option<&'static str> {
FILESYSTEM_SIGNATURES.iter().find_map(|sig| {
let end = sig.offset.checked_add(sig.magic.len())?;
(data.len() >= end && &data[sig.offset..end] == sig.magic).then_some(sig.name)
})
}
#[cfg(test)]
mod tests {
use super::*;
fn buf_with(offset: usize, magic: &[u8]) -> Vec<u8> {
let mut v = vec![0u8; offset + magic.len() + 4];
v[offset..offset + magic.len()].copy_from_slice(magic);
v
}
#[test]
fn detects_ext_at_0x438() {
assert_eq!(detect_name(&buf_with(0x438, &[0x53, 0xEF])), Some("ext2/3/4"));
}
#[test]
fn detects_ntfs_and_exfat_oem() {
assert_eq!(detect_name(&buf_with(3, b"NTFS ")), Some("NTFS"));
assert_eq!(detect_name(&buf_with(3, b"EXFAT ")), Some("exFAT"));
}
#[test]
fn detects_luks_and_xfs_at_zero() {
assert_eq!(detect_name(&buf_with(0, b"LUKS\xba\xbe")), Some("LUKS"));
assert_eq!(detect_name(&buf_with(0, b"XFSB")), Some("XFS"));
}
#[test]
fn detects_apfs_at_offset_32() {
assert_eq!(detect_name(&buf_with(32, b"NXSB")), Some("APFS"));
assert_eq!(detect_name(&buf_with(0, b"NXSB")), None);
}
#[test]
fn detects_btrfs_at_65600() {
assert_eq!(detect_name(&buf_with(65600, b"_BHRfS_M")), Some("Btrfs"));
assert_eq!(detect_name(&buf_with(65536, b"_BHRfS_M")), None);
}
#[test]
fn detects_lvm_at_sector_0_or_1() {
assert_eq!(detect_name(&buf_with(0, b"LABELONE")), Some("LVM2"));
assert_eq!(detect_name(&buf_with(512, b"LABELONE")), Some("LVM2"));
}
#[test]
fn empty_and_unknown_are_none() {
assert_eq!(detect_name(&[]), None);
assert_eq!(detect_name(&[0u8; 512]), None);
}
#[test]
fn short_slice_does_not_panic() {
assert_eq!(detect_name(&[0u8; 8]), None);
}
#[test]
fn signatures_are_well_formed() {
for s in FILESYSTEM_SIGNATURES {
assert!(!s.magic.is_empty(), "{} has empty magic", s.name);
assert!(!s.name.is_empty());
}
}
}