Skip to content

Commit

Permalink
added fat16/32 superblock detection
Browse files Browse the repository at this point in the history
  • Loading branch information
cameronbraid committed Feb 13, 2025
1 parent a2e432d commit cb60f2e
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 1 deletion.
169 changes: 169 additions & 0 deletions crates/superblock/src/fat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers
//
// SPDX-License-Identifier: MPL-2.0

//! Fat32
//!
//! This module implements parsing and access to the FAT32 filesystem boot sector,
//! which contains critical metadata about the filesystem including:
//! - Version information
//! - Volume name and UUID
//! - Encryption settings
use std::io;

use crate::{Detection, Error};
use zerocopy::*;

/// Starting position of superblock in bytes
pub const START_POSITION: u64 = 0;

const MAGIC: [u8; 2] = [0x55, 0xAA];

#[repr(C, packed)]
#[derive(FromBytes, Unaligned, Debug)]
pub struct Fat {
/// Boot strap short or near jump
pub ignored: [u8; 3],
/// Name - can be used to special case partition manager volumes
pub system_id: [u8; 8],
/// Bytes per logical sector
pub sector_size: U16<LittleEndian>,
/// Sectors per cluster
pub sec_per_clus: u8,
/// Reserved sectors
pub _reserved: U16<LittleEndian>,
/// Number of FATs
pub fats: u8,
/// Root directory entries
pub dir_entries: U16<LittleEndian>,
/// Number of sectors
pub sectors: U16<LittleEndian>,
/// Media code
pub media: u8,
/// Sectors/FAT
pub fat_length: U16<LittleEndian>,
/// Sectors per track
pub secs_track: U16<LittleEndian>,
/// Number of heads
pub heads: U16<LittleEndian>,
/// Hidden sectors (unused)
pub hidden: U32<LittleEndian>,
/// Number of sectors (if sectors == 0)
pub total_sect: U32<LittleEndian>,

// Shared memory region for FAT16 and FAT32
// Best way is to use a union with zerocopy, however that requires having to use `--cfg zerocopy_derive_union_into_bytes` https://github.com/google/zerocopy/issues/1792`
pub shared: [u8; 54], // The size of the union fields in bytes
}

#[derive(FromBytes, Unaligned)]
#[repr(C, packed)]
pub struct Fat16And32Fields {
// Physical drive number
pub drive_number: u8,
// Mount state
pub state: u8,
// Extended boot signature
pub signature: u8,
// Volume ID
pub vol_id: U32<LittleEndian>,
// Volume label
pub vol_label: [u8; 11],
// File system type
pub fs_type: [u8; 8],
}

#[derive(FromBytes, Unaligned)]
#[repr(C, packed)]
pub struct Fat16Fields {
pub common: Fat16And32Fields,
}

impl Fat16Fields {}

#[derive(FromBytes, Unaligned)]
#[repr(C, packed)]
pub struct Fat32Fields {
// FAT32-specific fields
/// Sectors/FAT
pub fat32_length: U32<LittleEndian>,
/// FAT mirroring flags
pub fat32_flags: U16<LittleEndian>,
/// Major, minor filesystem version
pub fat32_version: [u8; 2],
/// First cluster in root directory
pub root_cluster: U32<LittleEndian>,
/// Filesystem info sector
pub info_sector: U16<LittleEndian>,
/// Backup boot sector
pub backup_boot: U16<LittleEndian>,
/// Unused
pub reserved2: [U16<LittleEndian>; 6],

pub common: Fat16And32Fields,
}

impl Detection for Fat {
type Magic = [u8; 2];

const OFFSET: u64 = START_POSITION;

const MAGIC_OFFSET: u64 = 0x1FE;

const SIZE: usize = std::mem::size_of::<Fat>();

fn is_valid_magic(magic: &Self::Magic) -> bool {
*magic == MAGIC
}
}

pub enum FatType {
Fat16,
Fat32,
}

impl Fat {
pub fn fat_type(&self) -> Result<FatType, Error> {
// this is how the linux kernel does it in https://github.com/torvalds/linux/blob/master/fs/fat/inode.c
if self.fat_length == 0 && self.fat32()?.fat32_length != 0 {
Ok(FatType::Fat32)
} else {
Ok(FatType::Fat16)
}
}

/// Returns the filesystem id
pub fn uuid(&self) -> Result<String, Error> {
match self.fat_type()? {
FatType::Fat16 => vol_id(self.fat16()?.common.vol_id),
FatType::Fat32 => vol_id(self.fat32()?.common.vol_id),
}
}

/// Returns the volume label
pub fn label(&self) -> Result<String, Error> {
match self.fat_type()? {
FatType::Fat16 => vol_label(&self.fat16()?.common.vol_label),
FatType::Fat32 => vol_label(&self.fat32()?.common.vol_label),
}
}

fn fat16(&self) -> Result<Fat16Fields, Error> {
Ok(Fat16Fields::read_from_bytes(&self.shared[..size_of::<Fat16Fields>()])
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Error Reading FAT16 Superblock"))?)
}

fn fat32(&self) -> Result<Fat32Fields, Error> {
Ok(Fat32Fields::read_from_bytes(&self.shared)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Error Reading FAT32 Superblock"))?)
}
}

fn vol_label(vol_label: &[u8; 11]) -> Result<String, Error> {
Ok(String::from_utf8_lossy(vol_label).trim_end_matches(' ').to_string())
}

fn vol_id(vol_id: U32<LittleEndian>) -> Result<String, Error> {
Ok(format!("{:04X}-{:04X}", vol_id >> 16, vol_id & 0xFFFF))
}
14 changes: 13 additions & 1 deletion crates/superblock/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use zerocopy::FromBytes;
pub mod btrfs;
pub mod ext4;
pub mod f2fs;
pub mod fat;
pub mod luks2;
pub mod xfs;

Expand Down Expand Up @@ -99,6 +100,8 @@ pub enum Kind {
F2FS,
/// XFS filesystem
XFS,
/// FAT filesystem
FAT,
}

impl std::fmt::Display for Kind {
Expand All @@ -109,6 +112,7 @@ impl std::fmt::Display for Kind {
Kind::LUKS2 => f.write_str("luks2"),
Kind::F2FS => f.write_str("f2fs"),
Kind::XFS => f.write_str("xfs"),
Kind::FAT => f.write_str("fat"),
}
}
}
Expand All @@ -119,6 +123,7 @@ pub enum Superblock {
F2FS(Box<f2fs::F2FS>),
LUKS2(Box<luks2::Luks2>),
XFS(Box<xfs::XFS>),
FAT(Box<fat::Fat>),
}

impl Superblock {
Expand All @@ -130,6 +135,7 @@ impl Superblock {
Superblock::F2FS(_) => Kind::F2FS,
Superblock::LUKS2(_) => Kind::LUKS2,
Superblock::XFS(_) => Kind::XFS,
Superblock::FAT(_) => Kind::FAT,
}
}

Expand All @@ -141,6 +147,7 @@ impl Superblock {
Superblock::F2FS(block) => block.uuid(),
Superblock::LUKS2(block) => block.uuid(),
Superblock::XFS(block) => block.uuid(),
Superblock::FAT(block) => block.uuid(),
}
}

Expand All @@ -152,6 +159,7 @@ impl Superblock {
Superblock::F2FS(block) => block.label(),
Superblock::LUKS2(block) => block.label(),
Superblock::XFS(block) => block.label(),
Superblock::FAT(block) => block.label(),
}
}
}
Expand Down Expand Up @@ -179,7 +187,9 @@ impl Superblock {
if let Some(sb) = detect_superblock::<luks2::Luks2, _>(&mut cursor)? {
return Ok(Self::LUKS2(Box::new(sb)));
}

if let Some(sb) = detect_superblock::<fat::Fat, _>(&mut cursor)? {
return Ok(Self::FAT(Box::new(sb)));
}
Err(Error::UnknownSuperblock)
}

Expand Down Expand Up @@ -231,6 +241,8 @@ mod tests {
),
("luks+ext4", Kind::LUKS2, "", "be373cae-2bd1-4ad5-953f-3463b2e53e59"),
("xfs", Kind::XFS, "BLSFORME", "45e8a3bf-8114-400f-95b0-380d0fb7d42d"),
("fat16", Kind::FAT, "TESTLABEL", "A1B2-C3D4"),
("fat32", Kind::FAT, "TESTLABEL", "A1B2-C3D4"),
];

// Pre-allocate a buffer for determination tests
Expand Down
24 changes: 24 additions & 0 deletions crates/superblock/tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,27 @@ Limited to 12-char volume name

UUID : 45e8a3bf-8114-400f-95b0-380d0fb7d42d
LABEL: BLSFORME

## fat16.img.zst

UUID : A1B2-C3D4 (volume id not a uuid)
LABEL: TESTLABEL

created with commands :

dd if=/dev/zero of=fat16.img bs=512 count=32768
mkfs.fat -F 16 -n "TESTLABEL" -i A1B2C3D4 fat16.img
zstd fat16.img
rm fat16.img

## fat32.img.zst

UUID : A1B2-C3D4 (volume id not a uuid)
LABEL: TESTLABEL

created with commands :

dd if=/dev/zero of=fat32.img bs=512 count=32768
mkfs.fat -F 32 -n "TESTLABEL" -i A1B2C3D4 fat32.img
zstd fat32.img
rm fat32.img
Binary file added crates/superblock/tests/fat16.img
Binary file not shown.
Binary file added crates/superblock/tests/fat16.img.zst
Binary file not shown.
Binary file added crates/superblock/tests/fat32.img
Binary file not shown.
Binary file added crates/superblock/tests/fat32.img.zst
Binary file not shown.

0 comments on commit cb60f2e

Please sign in to comment.