Skip to content

Commit 0e8e8f6

Browse files
authored
Merge pull request #15 from cameronbraid/fat-superblock
added fat16/32 superblock detection
2 parents a2e432d + d318b1e commit 0e8e8f6

File tree

7 files changed

+206
-1
lines changed

7 files changed

+206
-1
lines changed

crates/superblock/src/fat.rs

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers
2+
//
3+
// SPDX-License-Identifier: MPL-2.0
4+
5+
//! Fat32
6+
//!
7+
//! This module implements parsing and access to the FAT32 filesystem boot sector,
8+
//! which contains critical metadata about the filesystem including:
9+
//! - Version information
10+
//! - Volume name and UUID
11+
//! - Encryption settings
12+
13+
use std::io;
14+
15+
use crate::{Detection, Error};
16+
use zerocopy::*;
17+
18+
/// Starting position of superblock in bytes
19+
pub const START_POSITION: u64 = 0;
20+
21+
const MAGIC: [u8; 2] = [0x55, 0xAA];
22+
23+
#[repr(C, packed)]
24+
#[derive(FromBytes, Unaligned, Debug)]
25+
pub struct Fat {
26+
/// Boot strap short or near jump
27+
pub ignored: [u8; 3],
28+
/// Name - can be used to special case partition manager volumes
29+
pub system_id: [u8; 8],
30+
/// Bytes per logical sector
31+
pub sector_size: U16<LittleEndian>,
32+
/// Sectors per cluster
33+
pub sec_per_clus: u8,
34+
/// Reserved sectors
35+
pub _reserved: U16<LittleEndian>,
36+
/// Number of FATs
37+
pub fats: u8,
38+
/// Root directory entries
39+
pub dir_entries: U16<LittleEndian>,
40+
/// Number of sectors
41+
pub sectors: U16<LittleEndian>,
42+
/// Media code
43+
pub media: u8,
44+
/// Sectors/FAT
45+
pub fat_length: U16<LittleEndian>,
46+
/// Sectors per track
47+
pub secs_track: U16<LittleEndian>,
48+
/// Number of heads
49+
pub heads: U16<LittleEndian>,
50+
/// Hidden sectors (unused)
51+
pub hidden: U32<LittleEndian>,
52+
/// Number of sectors (if sectors == 0)
53+
pub total_sect: U32<LittleEndian>,
54+
55+
// Shared memory region for FAT16 and FAT32
56+
// 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`
57+
pub shared: [u8; 54], // The size of the union fields in bytes
58+
}
59+
60+
#[derive(FromBytes, Unaligned)]
61+
#[repr(C, packed)]
62+
pub struct Fat16And32Fields {
63+
// Physical drive number
64+
pub drive_number: u8,
65+
// Mount state
66+
pub state: u8,
67+
// Extended boot signature
68+
pub signature: u8,
69+
// Volume ID
70+
pub vol_id: U32<LittleEndian>,
71+
// Volume label
72+
pub vol_label: [u8; 11],
73+
// File system type
74+
pub fs_type: [u8; 8],
75+
}
76+
77+
#[derive(FromBytes, Unaligned)]
78+
#[repr(C, packed)]
79+
pub struct Fat16Fields {
80+
pub common: Fat16And32Fields,
81+
}
82+
83+
impl Fat16Fields {}
84+
85+
#[derive(FromBytes, Unaligned)]
86+
#[repr(C, packed)]
87+
pub struct Fat32Fields {
88+
// FAT32-specific fields
89+
/// Sectors/FAT
90+
pub fat32_length: U32<LittleEndian>,
91+
/// FAT mirroring flags
92+
pub fat32_flags: U16<LittleEndian>,
93+
/// Major, minor filesystem version
94+
pub fat32_version: [u8; 2],
95+
/// First cluster in root directory
96+
pub root_cluster: U32<LittleEndian>,
97+
/// Filesystem info sector
98+
pub info_sector: U16<LittleEndian>,
99+
/// Backup boot sector
100+
pub backup_boot: U16<LittleEndian>,
101+
/// Unused
102+
pub reserved2: [U16<LittleEndian>; 6],
103+
104+
pub common: Fat16And32Fields,
105+
}
106+
107+
impl Detection for Fat {
108+
type Magic = [u8; 2];
109+
110+
const OFFSET: u64 = START_POSITION;
111+
112+
const MAGIC_OFFSET: u64 = 0x1FE;
113+
114+
const SIZE: usize = std::mem::size_of::<Fat>();
115+
116+
fn is_valid_magic(magic: &Self::Magic) -> bool {
117+
*magic == MAGIC
118+
}
119+
}
120+
121+
pub enum FatType {
122+
Fat16,
123+
Fat32,
124+
}
125+
126+
impl Fat {
127+
pub fn fat_type(&self) -> Result<FatType, Error> {
128+
// this is how the linux kernel does it in https://github.com/torvalds/linux/blob/master/fs/fat/inode.c
129+
if self.fat_length == 0 && self.fat32()?.fat32_length != 0 {
130+
Ok(FatType::Fat32)
131+
} else {
132+
Ok(FatType::Fat16)
133+
}
134+
}
135+
136+
/// Returns the filesystem id
137+
pub fn uuid(&self) -> Result<String, Error> {
138+
match self.fat_type()? {
139+
FatType::Fat16 => vol_id(self.fat16()?.common.vol_id),
140+
FatType::Fat32 => vol_id(self.fat32()?.common.vol_id),
141+
}
142+
}
143+
144+
/// Returns the volume label
145+
pub fn label(&self) -> Result<String, Error> {
146+
match self.fat_type()? {
147+
FatType::Fat16 => vol_label(&self.fat16()?.common.vol_label),
148+
FatType::Fat32 => vol_label(&self.fat32()?.common.vol_label),
149+
}
150+
}
151+
152+
fn fat16(&self) -> Result<Fat16Fields, Error> {
153+
Ok(Fat16Fields::read_from_bytes(&self.shared[..size_of::<Fat16Fields>()])
154+
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Error Reading FAT16 Superblock"))?)
155+
}
156+
157+
fn fat32(&self) -> Result<Fat32Fields, Error> {
158+
Ok(Fat32Fields::read_from_bytes(&self.shared)
159+
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Error Reading FAT32 Superblock"))?)
160+
}
161+
}
162+
163+
fn vol_label(vol_label: &[u8; 11]) -> Result<String, Error> {
164+
Ok(String::from_utf8_lossy(vol_label).trim_end_matches(' ').to_string())
165+
}
166+
167+
fn vol_id(vol_id: U32<LittleEndian>) -> Result<String, Error> {
168+
Ok(format!("{:04X}-{:04X}", vol_id >> 16, vol_id & 0xFFFF))
169+
}

crates/superblock/src/lib.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use zerocopy::FromBytes;
1515
pub mod btrfs;
1616
pub mod ext4;
1717
pub mod f2fs;
18+
pub mod fat;
1819
pub mod luks2;
1920
pub mod xfs;
2021

@@ -99,6 +100,8 @@ pub enum Kind {
99100
F2FS,
100101
/// XFS filesystem
101102
XFS,
103+
/// FAT filesystem
104+
FAT,
102105
}
103106

104107
impl std::fmt::Display for Kind {
@@ -109,6 +112,7 @@ impl std::fmt::Display for Kind {
109112
Kind::LUKS2 => f.write_str("luks2"),
110113
Kind::F2FS => f.write_str("f2fs"),
111114
Kind::XFS => f.write_str("xfs"),
115+
Kind::FAT => f.write_str("fat"),
112116
}
113117
}
114118
}
@@ -119,6 +123,7 @@ pub enum Superblock {
119123
F2FS(Box<f2fs::F2FS>),
120124
LUKS2(Box<luks2::Luks2>),
121125
XFS(Box<xfs::XFS>),
126+
FAT(Box<fat::Fat>),
122127
}
123128

124129
impl Superblock {
@@ -130,6 +135,7 @@ impl Superblock {
130135
Superblock::F2FS(_) => Kind::F2FS,
131136
Superblock::LUKS2(_) => Kind::LUKS2,
132137
Superblock::XFS(_) => Kind::XFS,
138+
Superblock::FAT(_) => Kind::FAT,
133139
}
134140
}
135141

@@ -141,6 +147,7 @@ impl Superblock {
141147
Superblock::F2FS(block) => block.uuid(),
142148
Superblock::LUKS2(block) => block.uuid(),
143149
Superblock::XFS(block) => block.uuid(),
150+
Superblock::FAT(block) => block.uuid(),
144151
}
145152
}
146153

@@ -152,6 +159,7 @@ impl Superblock {
152159
Superblock::F2FS(block) => block.label(),
153160
Superblock::LUKS2(block) => block.label(),
154161
Superblock::XFS(block) => block.label(),
162+
Superblock::FAT(block) => block.label(),
155163
}
156164
}
157165
}
@@ -179,7 +187,9 @@ impl Superblock {
179187
if let Some(sb) = detect_superblock::<luks2::Luks2, _>(&mut cursor)? {
180188
return Ok(Self::LUKS2(Box::new(sb)));
181189
}
182-
190+
if let Some(sb) = detect_superblock::<fat::Fat, _>(&mut cursor)? {
191+
return Ok(Self::FAT(Box::new(sb)));
192+
}
183193
Err(Error::UnknownSuperblock)
184194
}
185195

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

236248
// Pre-allocate a buffer for determination tests

crates/superblock/tests/README.md

+24
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,27 @@ Limited to 12-char volume name
3232

3333
UUID : 45e8a3bf-8114-400f-95b0-380d0fb7d42d
3434
LABEL: BLSFORME
35+
36+
## fat16.img.zst
37+
38+
UUID : A1B2-C3D4 (volume id not a uuid)
39+
LABEL: TESTLABEL
40+
41+
created with commands :
42+
43+
dd if=/dev/zero of=fat16.img bs=512 count=32768
44+
mkfs.fat -F 16 -n "TESTLABEL" -i A1B2C3D4 fat16.img
45+
zstd fat16.img
46+
rm fat16.img
47+
48+
## fat32.img.zst
49+
50+
UUID : A1B2-C3D4 (volume id not a uuid)
51+
LABEL: TESTLABEL
52+
53+
created with commands :
54+
55+
dd if=/dev/zero of=fat32.img bs=512 count=32768
56+
mkfs.fat -F 32 -n "TESTLABEL" -i A1B2C3D4 fat32.img
57+
zstd fat32.img
58+
rm fat32.img

crates/superblock/tests/fat16.img

16 MB
Binary file not shown.

crates/superblock/tests/fat16.img.zst

769 Bytes
Binary file not shown.

crates/superblock/tests/fat32.img

16 MB
Binary file not shown.

crates/superblock/tests/fat32.img.zst

831 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)