#[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());
}
}
}