Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added fat16/32 superblock detection #15

Merged
merged 1 commit into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Loading