I should probably make more commits
This commit is contained in:
parent
5f234725d9
commit
99bb1e25c2
15 changed files with 2074 additions and 9 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
/target
|
||||
/tests
|
||||
|
|
|
|||
130
Cargo.lock
generated
Normal file
130
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum_dispatch"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fat-rs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"chrono",
|
||||
"enum_dispatch",
|
||||
"static_assertions",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
10
Cargo.toml
10
Cargo.toml
|
|
@ -3,4 +3,14 @@ name = "fat-rs"
|
|||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "dump"
|
||||
path = "src/dump.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.98"
|
||||
bitflags = "2.9.1"
|
||||
chrono = { version = "0.4.41", default-features = false, features = ["alloc", "std"] }
|
||||
enum_dispatch = "0.3.13"
|
||||
static_assertions = "1.1.0"
|
||||
thiserror = "2.0.12"
|
||||
|
|
|
|||
1
README.md
Normal file
1
README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
specification: [https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf](https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf)
|
||||
15
rustfmt.toml
Normal file
15
rustfmt.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
imports_granularity = "Module"
|
||||
group_imports = "StdExternalCrate"
|
||||
|
||||
fn_call_width = 80
|
||||
|
||||
# Activation of features, almost objectively better ;)
|
||||
use_try_shorthand = true
|
||||
use_field_init_shorthand = true
|
||||
|
||||
# Nightly only. Will not run in CI, but please format with these locally
|
||||
wrap_comments = true
|
||||
normalize_comments = true
|
||||
comment_width = 100
|
||||
|
||||
condense_wildcard_suffixes = true
|
||||
618
src/bpb.rs
Normal file
618
src/bpb.rs
Normal file
|
|
@ -0,0 +1,618 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use crate::FatType;
|
||||
use crate::utils::{load_u16_le, load_u32_le};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ExtBpb {
|
||||
ExtBpb16(ExtBpb16),
|
||||
ExtBpb32(ExtBpb32),
|
||||
}
|
||||
|
||||
impl Display for ExtBpb {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ExtBpb::ExtBpb16(ext_bpb16) => write!(f, "{}", ext_bpb16),
|
||||
ExtBpb::ExtBpb32(ext_bpb32) => write!(f, "{}", ext_bpb32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Bpb {
|
||||
fat_type: FatType,
|
||||
|
||||
jmp_boot: [u8; 3],
|
||||
oem_name: [u8; 8],
|
||||
bytes_per_sector: u16,
|
||||
sectors_per_cluster: u8,
|
||||
reserved_sector_count: u16,
|
||||
num_fats: u8,
|
||||
root_entry_count: u16,
|
||||
total_sectors_16: u16,
|
||||
media: u8,
|
||||
fat_size_16: u16,
|
||||
sectors_per_track: u16,
|
||||
num_heads: u16,
|
||||
hidden_sectors: u32,
|
||||
total_sectors_32: u32,
|
||||
|
||||
ext_bpb: ExtBpb,
|
||||
}
|
||||
|
||||
impl Display for Bpb {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "Bpb {{")?;
|
||||
|
||||
match self.fat_type {
|
||||
FatType::Fat12 => writeln!(f, " FAT12")?,
|
||||
FatType::Fat16 => writeln!(f, " FAT16")?,
|
||||
FatType::Fat32 => writeln!(f, " FAT32")?,
|
||||
}
|
||||
|
||||
writeln!(f, "")?;
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
" jmp_boot: [{:#X}, {:#X}, {:#X}]",
|
||||
self.jmp_boot[0], self.jmp_boot[1], self.jmp_boot[2]
|
||||
)?;
|
||||
|
||||
writeln!(f, " oem name: \"{}\"", self.oem_name_str().unwrap_or(""))?;
|
||||
writeln!(f, " bytes per sector: {}", self.bytes_per_sector())?;
|
||||
writeln!(f, " sectors per cluster: {}", self.sectors_per_cluster())?;
|
||||
writeln!(f, " reserved sector count: {}", self.reserved_sector_count())?;
|
||||
writeln!(f, " num_fats: {}", self.num_fats())?;
|
||||
writeln!(f, " root entry count: {}", self.root_entry_count())?;
|
||||
writeln!(f, " total sectors: {}", self.total_sectors_16())?;
|
||||
writeln!(f, " media: {:#X}", self.media())?;
|
||||
writeln!(f, " fat_size_16: {}", self.fat_size_16())?;
|
||||
writeln!(f, " sectors per track: {}", self.sectors_per_track())?;
|
||||
writeln!(f, " num_heads: {}", self.num_heads())?;
|
||||
writeln!(f, " hidden_sectors: {}", self.hidden_sectors())?;
|
||||
writeln!(f, " total sectors 32: {}", self.total_sectors_32())?;
|
||||
|
||||
writeln!(f, "")?;
|
||||
|
||||
let ext_bpb_str = format!("{}", self.ext_bpb);
|
||||
|
||||
for line in ext_bpb_str.lines() {
|
||||
writeln!(f, " {}", line)?;
|
||||
}
|
||||
|
||||
writeln!(f, "}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Bpb {
|
||||
pub fn load(bytes: &[u8]) -> anyhow::Result<Bpb> {
|
||||
anyhow::ensure!(bytes.len() >= 512, "invalid BPB of len {}", bytes.len());
|
||||
|
||||
let jmp_boot = bytes[..3].try_into().unwrap();
|
||||
let oem_name = bytes[3..][..8].try_into().unwrap();
|
||||
let bytes_per_sector = load_u16_le(&bytes[11..][..2]);
|
||||
|
||||
if !&[512, 1024, 2048, 4096].contains(&bytes_per_sector) {
|
||||
anyhow::bail!("invalid bytes per sector: {}", bytes_per_sector);
|
||||
}
|
||||
|
||||
let sectors_per_cluster = bytes[13];
|
||||
|
||||
if !&[1, 2, 4, 8, 16, 32, 64, 128].contains(§ors_per_cluster) {
|
||||
anyhow::bail!("invalid sectors per cluster: {}", sectors_per_cluster);
|
||||
}
|
||||
|
||||
let reserved_sector_count = load_u16_le(&bytes[14..][..2]);
|
||||
|
||||
anyhow::ensure!(reserved_sector_count != 0, "reserved sector count can't be zero");
|
||||
|
||||
let num_fats = bytes[16];
|
||||
let root_entry_count = load_u16_le(&bytes[17..][..2]);
|
||||
let total_sectors_16 = load_u16_le(&bytes[19..][..2]);
|
||||
|
||||
let media = bytes[21];
|
||||
|
||||
if !&[0xF0, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF].contains(&media) {
|
||||
anyhow::bail!("invalid media: {}", media);
|
||||
}
|
||||
|
||||
let fat_size_16 = load_u16_le(&bytes[22..][..2]);
|
||||
let sectors_per_track = load_u16_le(&bytes[24..][..2]);
|
||||
let num_heads = load_u16_le(&bytes[26..][..2]);
|
||||
let hidden_sectors = load_u32_le(&bytes[28..][..4]);
|
||||
let total_sectors_32 = load_u32_le(&bytes[32..][..4]);
|
||||
|
||||
let (fat_type, ext_bpb) = if fat_size_16 == 0 {
|
||||
// FAT32?
|
||||
|
||||
anyhow::ensure!(
|
||||
total_sectors_16 == 0,
|
||||
"fat_size_16 is 0, but total sectors is {} instead of 0",
|
||||
total_sectors_16
|
||||
);
|
||||
|
||||
let ext_bpb = ExtBpb32::load(bytes)?;
|
||||
(FatType::Fat32, ExtBpb::ExtBpb32(ext_bpb))
|
||||
} else {
|
||||
// FAT16?
|
||||
|
||||
let ext_bpb = ExtBpb16::load(bytes)?;
|
||||
|
||||
(FatType::Fat16, ExtBpb::ExtBpb16(ext_bpb))
|
||||
};
|
||||
|
||||
let mut bpb = Bpb {
|
||||
fat_type,
|
||||
jmp_boot,
|
||||
oem_name,
|
||||
bytes_per_sector,
|
||||
sectors_per_cluster,
|
||||
reserved_sector_count,
|
||||
num_fats,
|
||||
root_entry_count,
|
||||
total_sectors_16,
|
||||
media,
|
||||
fat_size_16,
|
||||
sectors_per_track,
|
||||
num_heads,
|
||||
hidden_sectors,
|
||||
total_sectors_32,
|
||||
ext_bpb,
|
||||
};
|
||||
|
||||
let count_of_clusters = bpb.count_of_clusters();
|
||||
|
||||
if count_of_clusters < 4085 {
|
||||
anyhow::ensure!(
|
||||
bpb.fat_type == FatType::Fat16,
|
||||
"{:?} should actually be FAT12",
|
||||
bpb.fat_type
|
||||
);
|
||||
|
||||
// actually FAT12 instead of FAT16
|
||||
bpb.fat_type = FatType::Fat12;
|
||||
} else if count_of_clusters < 65525 {
|
||||
anyhow::ensure!(
|
||||
bpb.fat_type == FatType::Fat16,
|
||||
"{:?} should actually be FAT16",
|
||||
bpb.fat_type
|
||||
);
|
||||
} else {
|
||||
anyhow::ensure!(
|
||||
bpb.fat_type == FatType::Fat32,
|
||||
"{:?} should actually be FAT32",
|
||||
bpb.fat_type
|
||||
);
|
||||
}
|
||||
|
||||
Ok(bpb)
|
||||
}
|
||||
|
||||
/// number of sectors usable for data
|
||||
pub fn num_data_sectors(&self) -> u32 {
|
||||
let data_sectors = self.total_sectors()
|
||||
- (self.reserved_sector_count() as u32
|
||||
+ (self.num_fats() as u32 * self.fat_size())
|
||||
+ self.root_dir_sectors());
|
||||
|
||||
data_sectors
|
||||
}
|
||||
|
||||
/// total number of clusters on this volume
|
||||
pub fn num_clusters(&self) -> u32 {
|
||||
self.total_sectors() / self.sectors_per_cluster() as u32
|
||||
}
|
||||
|
||||
/// number of bytes per cluster
|
||||
pub fn bytes_per_cluster(&self) -> usize {
|
||||
self.sectors_per_cluster() as usize * self.bytes_per_sector() as usize
|
||||
}
|
||||
|
||||
/// count of *data* clusters
|
||||
pub fn count_of_clusters(&self) -> u32 {
|
||||
self.num_data_sectors() / self.sectors_per_cluster as u32
|
||||
}
|
||||
|
||||
/// convert a given sector to an byte offset
|
||||
fn sector_to_offset(&self, sector: u32) -> u64 {
|
||||
sector as u64 * self.bytes_per_sector() as u64
|
||||
}
|
||||
|
||||
/// byte offset of the first FAT
|
||||
pub fn fat_offset(&self) -> u64 {
|
||||
self.sector_to_offset(self.reserved_sector_count() as u32)
|
||||
}
|
||||
|
||||
/// FAT size in bytes
|
||||
pub fn fat_len_bytes(&self) -> usize {
|
||||
self.bytes_per_sector() as usize * self.fat_size() as usize
|
||||
}
|
||||
|
||||
/// byte offset of the root directory; None for FAT32
|
||||
pub fn root_directory_offset(&self) -> Option<u64> {
|
||||
if self.fat_type() == FatType::Fat32 {
|
||||
return None;
|
||||
}
|
||||
Some(self.fat_offset() + self.sector_to_offset(self.num_fats() as u32 * self.fat_size()))
|
||||
}
|
||||
|
||||
/// number of sectors for root dir (only FAT12 and FAT16)
|
||||
pub fn root_dir_sectors(&self) -> u32 {
|
||||
(32 * self.root_entry_count() as u32).div_ceil(self.bytes_per_sector() as u32)
|
||||
}
|
||||
|
||||
/// byte size of the root directory
|
||||
pub fn root_dir_len_bytes(&self) -> usize {
|
||||
self.root_dir_sectors() as usize * self.bytes_per_sector() as usize
|
||||
}
|
||||
|
||||
/// first data sector
|
||||
pub fn first_data_sector(&self) -> u32 {
|
||||
println!("reserved sectors: {}", self.reserved_sector_count());
|
||||
println!("fat sectors: {}", self.num_fats() as u32 * self.fat_size());
|
||||
println!("root dir sectors: {}", self.root_dir_sectors());
|
||||
|
||||
self.reserved_sector_count() as u32
|
||||
+ (self.num_fats() as u32 + self.fat_size())
|
||||
+ self.root_dir_sectors() as u32
|
||||
}
|
||||
|
||||
pub fn data_offset(&self) -> u64 {
|
||||
// if let Some(root_dir_offset) = self.root_directory_offset() {
|
||||
// // has root directory (FAT12 or FAT16)
|
||||
// return root_dir_offset + self.sector_to_offset(self.root_entry_count() as u32);
|
||||
// }
|
||||
|
||||
// self.fat_offset() + self.sector_to_offset(self.num_fats() as u32 * self.fat_size())
|
||||
|
||||
self.sector_to_offset(self.first_data_sector())
|
||||
}
|
||||
|
||||
/// byte size of the data section
|
||||
pub fn data_len_bytes(&self) -> usize {
|
||||
self.num_data_sectors() as usize * self.bytes_per_sector() as usize
|
||||
}
|
||||
|
||||
/// FAT type (FAT12, FAT16, or FAT32)
|
||||
pub fn fat_type(&self) -> FatType {
|
||||
self.fat_type
|
||||
}
|
||||
|
||||
/// number of sectors per FAT
|
||||
pub fn fat_size(&self) -> u32 {
|
||||
match &self.ext_bpb {
|
||||
ExtBpb::ExtBpb16(_ext_bpb16) => self.fat_size_16() as u32,
|
||||
ExtBpb::ExtBpb32(ext_bpb32) => ext_bpb32.fat_size_32(),
|
||||
}
|
||||
}
|
||||
|
||||
/// get root cluster for FAT32
|
||||
pub fn root_cluster(&self) -> Option<u32> {
|
||||
if let ExtBpb::ExtBpb32(ext_bpb32) = &self.ext_bpb {
|
||||
Some(ext_bpb32.root_cluster())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// total number of sectors in this device
|
||||
///
|
||||
/// uses total_sectors_16 or total_sectors_32
|
||||
pub fn total_sectors(&self) -> u32 {
|
||||
// match &self.ext_bpb {
|
||||
// ExtBpb::ExtBpb16(_) => self.total_sectors_16() as u32,
|
||||
// ExtBpb::ExtBpb32(_) => self.total_sectors_32(),
|
||||
// }
|
||||
|
||||
match self.total_sectors_16() {
|
||||
0 => self.total_sectors_32(),
|
||||
n => n as u32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn oem_name(&self) -> &[u8] {
|
||||
&self.oem_name
|
||||
}
|
||||
|
||||
pub fn oem_name_str(&self) -> Option<&str> {
|
||||
std::str::from_utf8(&self.oem_name()).ok()
|
||||
}
|
||||
|
||||
pub fn bytes_per_sector(&self) -> u16 {
|
||||
self.bytes_per_sector
|
||||
}
|
||||
|
||||
pub fn sectors_per_cluster(&self) -> u8 {
|
||||
self.sectors_per_cluster
|
||||
}
|
||||
|
||||
pub fn reserved_sector_count(&self) -> u16 {
|
||||
self.reserved_sector_count
|
||||
}
|
||||
|
||||
pub fn num_fats(&self) -> u8 {
|
||||
self.num_fats
|
||||
}
|
||||
|
||||
// number of 32 byte dir entries in the root directory
|
||||
pub fn root_entry_count(&self) -> u16 {
|
||||
self.root_entry_count
|
||||
}
|
||||
|
||||
pub fn total_sectors_16(&self) -> u16 {
|
||||
self.total_sectors_16
|
||||
}
|
||||
|
||||
pub fn media(&self) -> u8 {
|
||||
self.media
|
||||
}
|
||||
|
||||
pub fn fat_size_16(&self) -> u16 {
|
||||
self.fat_size_16
|
||||
}
|
||||
|
||||
pub fn sectors_per_track(&self) -> u16 {
|
||||
self.sectors_per_track
|
||||
}
|
||||
|
||||
pub fn num_heads(&self) -> u16 {
|
||||
self.num_heads
|
||||
}
|
||||
|
||||
pub fn hidden_sectors(&self) -> u32 {
|
||||
self.hidden_sectors
|
||||
}
|
||||
|
||||
pub fn total_sectors_32(&self) -> u32 {
|
||||
self.total_sectors_32
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExtBpb16 {
|
||||
drive_number: u8,
|
||||
boot_sig: u8,
|
||||
volume_serial_number: u32,
|
||||
volume_label: [u8; 11],
|
||||
file_sys_type: [u8; 8],
|
||||
}
|
||||
|
||||
impl Display for ExtBpb16 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "ExtBpb16 {{")?;
|
||||
|
||||
writeln!(f, " drive number: {}", self.drive_number())?;
|
||||
writeln!(f, " boot_sig: {:#x}", self.boot_sig())?;
|
||||
writeln!(f, " volume serial number: {}", self.volume_serial_number())?;
|
||||
writeln!(f, " volume label: {}", self.volume_label_str().unwrap_or(""))?;
|
||||
writeln!(f, " file sys type: {}", self.file_sys_type_str())?;
|
||||
|
||||
writeln!(f, "}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtBpb16 {
|
||||
pub fn load(bytes: &[u8]) -> anyhow::Result<ExtBpb16> {
|
||||
let drive_number = bytes[36];
|
||||
|
||||
if !&[0x80, 0x00].contains(&drive_number) {
|
||||
anyhow::bail!("invalid drive number: {}", drive_number);
|
||||
}
|
||||
|
||||
let boot_sig = bytes[38];
|
||||
let volume_serial_number = load_u32_le(&bytes[39..][..4]);
|
||||
let volume_label = bytes[43..][..11].try_into().unwrap();
|
||||
|
||||
if volume_serial_number != 0 || volume_label != [0; 11] {
|
||||
anyhow::ensure!(
|
||||
boot_sig == 0x29,
|
||||
"volume serial number and volume label are not both empty, but boot sig is {:#x}
|
||||
instead of 0x29",
|
||||
boot_sig
|
||||
);
|
||||
}
|
||||
|
||||
let file_sys_type: [u8; 8] = bytes[54..][..8].try_into().unwrap();
|
||||
|
||||
let Some(s) = std::str::from_utf8(&file_sys_type).ok() else {
|
||||
anyhow::bail!("invalid file sys type: {:X?}", file_sys_type);
|
||||
};
|
||||
|
||||
if !&["FAT12 ", "FAT16 ", "FAT "].contains(&s) {
|
||||
anyhow::bail!("invalid file sys type: {}", s);
|
||||
}
|
||||
|
||||
let signature_word = &bytes[510..512];
|
||||
|
||||
anyhow::ensure!(
|
||||
signature_word == &[0x55, 0xAA],
|
||||
"invalid signature word: [{:#X}, {:#X}] instead of [0x55, 0xAA]",
|
||||
bytes[510],
|
||||
bytes[511]
|
||||
);
|
||||
|
||||
Ok(ExtBpb16 {
|
||||
drive_number,
|
||||
boot_sig,
|
||||
volume_serial_number,
|
||||
volume_label,
|
||||
file_sys_type,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn drive_number(&self) -> u8 {
|
||||
self.drive_number
|
||||
}
|
||||
|
||||
pub fn boot_sig(&self) -> u8 {
|
||||
self.boot_sig
|
||||
}
|
||||
|
||||
pub fn volume_serial_number(&self) -> u32 {
|
||||
self.volume_serial_number
|
||||
}
|
||||
|
||||
pub fn volume_label(&self) -> &[u8] {
|
||||
&self.volume_label
|
||||
}
|
||||
|
||||
pub fn volume_label_str(&self) -> Option<&str> {
|
||||
std::str::from_utf8(&self.volume_label).ok()
|
||||
}
|
||||
|
||||
pub fn file_sys_type(&self) -> &[u8] {
|
||||
&self.file_sys_type
|
||||
}
|
||||
|
||||
pub fn file_sys_type_str(&self) -> &str {
|
||||
std::str::from_utf8(&self.file_sys_type).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExtBpb32 {
|
||||
fat_size_32: u32,
|
||||
ext_flags: u16,
|
||||
root_cluster: u32,
|
||||
fs_info: u16,
|
||||
bk_boot_sector: u16,
|
||||
drive_number: u8,
|
||||
boot_sig: u8,
|
||||
volume_serial_number: u32,
|
||||
volume_label: [u8; 11],
|
||||
}
|
||||
|
||||
impl Display for ExtBpb32 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "ExtBpb32 {{")?;
|
||||
|
||||
writeln!(f, " fat_size_32: {}", self.fat_size_32)?;
|
||||
writeln!(f, " ext_flags: {}", self.ext_flags)?;
|
||||
writeln!(f, " root_cluster: {}", self.root_cluster)?;
|
||||
writeln!(f, " fs_info: {}", self.fs_info)?;
|
||||
writeln!(f, " bk_boot_sector: {}", self.bk_boot_sector)?;
|
||||
writeln!(f, " drive_number: {}", self.drive_number)?;
|
||||
writeln!(f, " boot_sig: {:#X}", self.boot_sig)?;
|
||||
writeln!(f, " volume serial number: {}", self.volume_serial_number)?;
|
||||
writeln!(f, " volume label: {}", self.volume_label_str().unwrap_or(""))?;
|
||||
|
||||
writeln!(f, "}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtBpb32 {
|
||||
pub fn load(bytes: &[u8]) -> anyhow::Result<ExtBpb32> {
|
||||
let fat_size_32 = load_u32_le(&bytes[36..][..4]);
|
||||
|
||||
anyhow::ensure!(fat_size_32 != 0, "fat_size_32 is zero");
|
||||
|
||||
let ext_flags = load_u16_le(&bytes[40..][..2]);
|
||||
|
||||
let fs_ver = load_u16_le(&bytes[42..][..2]);
|
||||
anyhow::ensure!(fs_ver == 0x0, "invalid FSVer: {}", fs_ver);
|
||||
|
||||
let root_cluster = load_u32_le(&bytes[44..][..4]);
|
||||
let fs_info = load_u16_le(&bytes[48..][..2]);
|
||||
|
||||
let bk_boot_sector = load_u16_le(&bytes[50..][..2]);
|
||||
|
||||
anyhow::ensure!(
|
||||
&[0, 6].contains(&bk_boot_sector),
|
||||
"invalid BkBootSector: {}",
|
||||
bk_boot_sector
|
||||
);
|
||||
|
||||
let reserved = &bytes[52..][..12];
|
||||
anyhow::ensure!(reserved == &[0; 12], "reserved is not zeroed");
|
||||
|
||||
let drive_number = bytes[64];
|
||||
|
||||
let reserved1 = bytes[65];
|
||||
anyhow::ensure!(reserved1 == 0, "reserved1 is not zeroed");
|
||||
|
||||
let boot_sig = bytes[66];
|
||||
let volume_serial_number = load_u32_le(&bytes[67..][..4]);
|
||||
let volume_label = bytes[71..][..11].try_into().unwrap();
|
||||
|
||||
if volume_serial_number != 0 || &volume_label != &[0; 11] {
|
||||
anyhow::ensure!(
|
||||
boot_sig == 0x29,
|
||||
"VollID or VolLab is set, but BootSig is {} instead of 0x29",
|
||||
boot_sig
|
||||
);
|
||||
}
|
||||
|
||||
let file_sys_type = &bytes[82..][..8];
|
||||
anyhow::ensure!(
|
||||
std::str::from_utf8(file_sys_type) == Ok("FAT32 "),
|
||||
"invalid file sys type"
|
||||
);
|
||||
|
||||
let signature_word = &bytes[510..][..2];
|
||||
|
||||
anyhow::ensure!(
|
||||
signature_word == &[0x55, 0xAA],
|
||||
"invalid signature word [{:#X}, {:#X}] instead of [0x55, 0xAA]",
|
||||
signature_word[0],
|
||||
signature_word[1]
|
||||
);
|
||||
|
||||
Ok(ExtBpb32 {
|
||||
fat_size_32,
|
||||
ext_flags,
|
||||
root_cluster,
|
||||
fs_info,
|
||||
bk_boot_sector,
|
||||
drive_number,
|
||||
boot_sig,
|
||||
volume_serial_number,
|
||||
volume_label,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fat_size_32(&self) -> u32 {
|
||||
self.fat_size_32
|
||||
}
|
||||
|
||||
pub fn ext_flags(&self) -> u16 {
|
||||
self.ext_flags
|
||||
}
|
||||
|
||||
pub fn root_cluster(&self) -> u32 {
|
||||
self.root_cluster
|
||||
}
|
||||
|
||||
pub fn fs_info(&self) -> u16 {
|
||||
self.fs_info
|
||||
}
|
||||
|
||||
pub fn bk_boot_sector(&self) -> u16 {
|
||||
self.bk_boot_sector
|
||||
}
|
||||
|
||||
pub fn drive_number(&self) -> u8 {
|
||||
self.drive_number
|
||||
}
|
||||
|
||||
pub fn boot_sig(&self) -> u8 {
|
||||
self.boot_sig
|
||||
}
|
||||
|
||||
pub fn volume_serial_number(&self) -> u32 {
|
||||
self.volume_serial_number
|
||||
}
|
||||
|
||||
pub fn volume_label(&self) -> &[u8] {
|
||||
&self.volume_label
|
||||
}
|
||||
|
||||
pub fn volume_label_str(&self) -> Option<&str> {
|
||||
std::str::from_utf8(self.volume_label()).ok()
|
||||
}
|
||||
}
|
||||
112
src/datetime.rs
Normal file
112
src/datetime.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
use std::time::SystemTime;
|
||||
|
||||
use chrono::{DateTime, Datelike, NaiveDate, NaiveTime, Timelike, Utc};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Date {
|
||||
repr: u16,
|
||||
}
|
||||
|
||||
impl Date {
|
||||
pub fn new(repr: u16) -> anyhow::Result<Date> {
|
||||
let date = Date { repr };
|
||||
|
||||
anyhow::ensure!(date.day() <= 31, "invalid day for date: {} (0x{:#X})", date.day(), repr);
|
||||
anyhow::ensure!(
|
||||
date.month() <= 12,
|
||||
"invalid month for date: {} (0x{:#X})",
|
||||
date.month(),
|
||||
repr
|
||||
);
|
||||
|
||||
Ok(date)
|
||||
}
|
||||
|
||||
pub fn from_day_month_year(day: u8, month: u8, year: u16) -> anyhow::Result<Date> {
|
||||
anyhow::ensure!(day <= 31, "invalid day: {}", day);
|
||||
anyhow::ensure!(month <= 12, "invalid month: {}", month);
|
||||
anyhow::ensure!(1980 <= year && year <= 2107, "invalid year: {}", year);
|
||||
|
||||
let repr = day as u16 | (month as u16) << 4 | (year - 1980) << 8;
|
||||
|
||||
Ok(Date { repr })
|
||||
}
|
||||
|
||||
pub fn from_system_time(time: SystemTime) -> anyhow::Result<Date> {
|
||||
let datetime: DateTime<Utc> = time.into();
|
||||
|
||||
let date = datetime.date_naive();
|
||||
|
||||
Date::from_day_month_year(
|
||||
date.day() as u8,
|
||||
date.month0() as u8 + 1,
|
||||
date.year_ce().1 as u16,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn day(&self) -> u8 {
|
||||
(self.repr & 0x1F) as u8
|
||||
}
|
||||
|
||||
pub fn month(&self) -> u8 {
|
||||
((self.repr & 0x1E0) >> 5) as u8
|
||||
}
|
||||
|
||||
pub fn year(&self) -> u16 {
|
||||
((self.repr & 0xFE00) >> 9) as u16 + 1980
|
||||
}
|
||||
|
||||
pub fn to_naive_date(&self) -> NaiveDate {
|
||||
NaiveDate::from_ymd_opt(self.year() as i32, self.month() as u32, self.day() as u32).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Time {
|
||||
repr: u16,
|
||||
}
|
||||
|
||||
impl Time {
|
||||
pub fn new(time: u16) -> anyhow::Result<Time> {
|
||||
let time = Time { repr: time };
|
||||
|
||||
Ok(time)
|
||||
}
|
||||
|
||||
pub fn from_seconds_minutes_hours(seconds: u8, minutes: u8, hours: u8) -> anyhow::Result<Time> {
|
||||
anyhow::ensure!(seconds <= 58 && seconds % 2 == 0, "invalid seconds: {}", seconds);
|
||||
anyhow::ensure!(minutes <= 59, "invalid minutes: {}", minutes);
|
||||
anyhow::ensure!(hours <= 23, "invalid hours: {}", hours);
|
||||
|
||||
let repr = (seconds >> 1) as u16 | (minutes as u16) << 5 | (hours as u16) << 11;
|
||||
|
||||
Ok(Time { repr })
|
||||
}
|
||||
|
||||
pub fn from_system_time(time: SystemTime) -> anyhow::Result<Time> {
|
||||
let datetime: DateTime<Utc> = time.into();
|
||||
|
||||
let time = datetime.time();
|
||||
|
||||
let seconds = (time.second() as u8) & !0x01;
|
||||
|
||||
Time::from_seconds_minutes_hours(seconds, time.minute() as u8, time.hour() as u8)
|
||||
}
|
||||
|
||||
pub fn second(&self) -> u8 {
|
||||
2 * (self.repr & 0x1F) as u8
|
||||
}
|
||||
|
||||
pub fn minute(&self) -> u8 {
|
||||
((self.repr >> 5) & 0x3F) as u8
|
||||
}
|
||||
|
||||
pub fn hour(&self) -> u8 {
|
||||
((self.repr >> 11) & 0x1F) as u8
|
||||
}
|
||||
|
||||
pub fn to_naive_time(&self) -> NaiveTime {
|
||||
NaiveTime::from_hms_opt(self.hour() as u32, self.minute() as u32, self.second() as u32)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
285
src/dir.rs
Normal file
285
src/dir.rs
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
use std::fmt::Display;
|
||||
use std::io::Read;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use chrono::{NaiveDate, NaiveDateTime, TimeDelta};
|
||||
|
||||
use crate::datetime::{Date, Time};
|
||||
use crate::utils::{load_u16_le, load_u32_le};
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Attr: u8 {
|
||||
const ReadOnly = 0x01;
|
||||
const Hidden = 0x02;
|
||||
const System = 0x04;
|
||||
const VolumeId = 0x08;
|
||||
const Directory = 0x10;
|
||||
const Archive = 0x20;
|
||||
|
||||
// const _ = !0;
|
||||
|
||||
// ReadOnly + Hidden + System + Volumeid
|
||||
const LongName = 0x01 | 0x02 | 0x04 | 0x08;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RegularDirEntry {
|
||||
name: [u8; 11],
|
||||
attr: Attr,
|
||||
|
||||
create_time_tenths: u8,
|
||||
create_time: Time,
|
||||
create_date: Date,
|
||||
|
||||
last_access_date: Date,
|
||||
|
||||
first_cluster: u32,
|
||||
|
||||
write_time: Time,
|
||||
write_date: Date,
|
||||
|
||||
file_size: u32,
|
||||
|
||||
long_name: Option<String>,
|
||||
}
|
||||
|
||||
impl Display for RegularDirEntry {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut name = self.name_string().unwrap_or_else(|| "<unknown>".to_owned());
|
||||
|
||||
if self.attr.contains(Attr::Directory) {
|
||||
name.push('/');
|
||||
}
|
||||
|
||||
write!(
|
||||
f,
|
||||
"DirEntry {{ {: <16} created: {} modified: {} }}",
|
||||
name,
|
||||
self.create_time(),
|
||||
self.write_time()
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl RegularDirEntry {
|
||||
pub fn load(bytes: &[u8]) -> anyhow::Result<RegularDirEntry> {
|
||||
assert_eq!(bytes.len(), 32);
|
||||
|
||||
let name = bytes[..11].try_into().unwrap();
|
||||
let attr = Attr::from_bits_truncate(bytes[11]);
|
||||
|
||||
let create_time_tenths = bytes[13];
|
||||
anyhow::ensure!(
|
||||
create_time_tenths <= 199,
|
||||
"invalid DIR_CrtTimeTenth: {}",
|
||||
create_time_tenths
|
||||
);
|
||||
|
||||
let create_time = Time::new(load_u16_le(&bytes[14..][..2]))?;
|
||||
let create_date = Date::new(load_u16_le(&bytes[16..][..2]))?;
|
||||
let last_access_date = Date::new(load_u16_le(&bytes[18..][..2]))?;
|
||||
let write_time = Time::new(load_u16_le(&bytes[22..][..2]))?;
|
||||
let write_date = Date::new(load_u16_le(&bytes[24..][..2]))?;
|
||||
let file_size = load_u32_le(&bytes[28..][..4]);
|
||||
|
||||
let first_cluster_hi = load_u16_le(&bytes[20..][..2]);
|
||||
let first_cluster_lo = load_u16_le(&bytes[26..][..2]);
|
||||
|
||||
let first_cluster = first_cluster_lo as u32 | ((first_cluster_hi as u32) << 16);
|
||||
|
||||
if attr.contains(Attr::VolumeId) {
|
||||
anyhow::ensure!(
|
||||
first_cluster == 0,
|
||||
"DirEntry has volume id attribute set, but first cluster is {}, not zero",
|
||||
first_cluster
|
||||
);
|
||||
}
|
||||
|
||||
if attr.contains(Attr::Directory) {
|
||||
anyhow::ensure!(
|
||||
file_size == 0,
|
||||
"DirEntry has directory attribute set, but file size is {}, not zero",
|
||||
file_size
|
||||
)
|
||||
}
|
||||
|
||||
Ok(RegularDirEntry {
|
||||
name,
|
||||
attr,
|
||||
create_time_tenths,
|
||||
create_time,
|
||||
create_date,
|
||||
last_access_date,
|
||||
first_cluster,
|
||||
write_time,
|
||||
write_date,
|
||||
file_size,
|
||||
long_name: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// indicates this DirEntry is empty
|
||||
///
|
||||
/// can be either simply empty (0xe5) or the sentinel (0x00) that indicates that all following
|
||||
/// DirEntries are empty as well
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.name[0] == 0xe5 || self.name[0] == 0x00
|
||||
}
|
||||
|
||||
/// indicates this and all following DisEntries are empty
|
||||
pub fn is_sentinel(&self) -> bool {
|
||||
self.name[0] == 0x00
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &[u8] {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn name_string(&self) -> Option<String> {
|
||||
// std::str::from_utf8(self.name()).ok()
|
||||
|
||||
let name = std::str::from_utf8(&self.name[..8]).ok()?.trim_ascii_end();
|
||||
let ext = std::str::from_utf8(&self.name[8..]).ok()?.trim_ascii_end();
|
||||
|
||||
let mut s = String::new();
|
||||
|
||||
if self.attr.contains(Attr::Hidden) {
|
||||
s.push('.');
|
||||
}
|
||||
|
||||
s += name;
|
||||
|
||||
if !ext.is_empty() {
|
||||
s.push('.');
|
||||
|
||||
s += ext;
|
||||
}
|
||||
|
||||
Some(s)
|
||||
}
|
||||
|
||||
pub fn long_name(&self) -> Option<&str> {
|
||||
self.long_name.as_deref()
|
||||
}
|
||||
|
||||
pub fn set_long_name(&mut self, long_name: String) {
|
||||
self.long_name = Some(long_name);
|
||||
}
|
||||
|
||||
pub fn attr(&self) -> Attr {
|
||||
self.attr
|
||||
}
|
||||
|
||||
pub fn create_time(&self) -> NaiveDateTime {
|
||||
let date = self.create_date.to_naive_date();
|
||||
let time = self.create_time.to_naive_time();
|
||||
|
||||
let time_frac = TimeDelta::try_milliseconds(100 * self.create_time_tenths as i64).unwrap();
|
||||
|
||||
let time = time.overflowing_add_signed(time_frac).0;
|
||||
|
||||
NaiveDateTime::new(date, time)
|
||||
}
|
||||
|
||||
pub fn last_access_date(&self) -> NaiveDate {
|
||||
self.last_access_date.to_naive_date()
|
||||
}
|
||||
|
||||
pub fn first_cluster(&self) -> u32 {
|
||||
self.first_cluster
|
||||
}
|
||||
|
||||
pub fn write_time(&self) -> NaiveDateTime {
|
||||
let time = self.write_time.to_naive_time();
|
||||
let date = self.write_date.to_naive_date();
|
||||
|
||||
NaiveDateTime::new(date, time)
|
||||
}
|
||||
|
||||
pub fn file_size(&self) -> u32 {
|
||||
self.file_size
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LongNameDirEntry {}
|
||||
|
||||
impl LongNameDirEntry {
|
||||
pub fn load(bytes: &[u8]) -> anyhow::Result<LongNameDirEntry> {
|
||||
assert_eq!(bytes.len(), 32);
|
||||
|
||||
Ok(LongNameDirEntry {})
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DirEntry {
|
||||
Regular(RegularDirEntry),
|
||||
LongName(LongNameDirEntry),
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
pub fn load(bytes: &[u8]) -> anyhow::Result<DirEntry> {
|
||||
assert_eq!(bytes.len(), 32);
|
||||
|
||||
let attr = Attr::from_bits_truncate(bytes[11]);
|
||||
|
||||
let dir_entry = if attr == Attr::LongName {
|
||||
DirEntry::LongName(LongNameDirEntry::load(bytes)?)
|
||||
} else {
|
||||
DirEntry::Regular(RegularDirEntry::load(bytes)?)
|
||||
};
|
||||
|
||||
Ok(dir_entry)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirIter<R: Read> {
|
||||
reader: R,
|
||||
}
|
||||
|
||||
impl<R: Read> DirIter<R> {
|
||||
pub fn new(reader: R) -> DirIter<R> {
|
||||
DirIter { reader }
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> Iterator for DirIter<R> {
|
||||
type Item = RegularDirEntry;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut chunk = [0; 32];
|
||||
self.reader.read_exact(&mut chunk).ok()?;
|
||||
|
||||
// let Ok(dir_entry) = DirEntry::load(&chunk) else {
|
||||
// return self.next();
|
||||
// };
|
||||
|
||||
let dir_entry = match DirEntry::load(&chunk) {
|
||||
Ok(dir_entry) => dir_entry,
|
||||
Err(e) => {
|
||||
// if loading fails: print error and try next entry
|
||||
eprintln!("failed to load dir entry: {e}");
|
||||
|
||||
return self.next();
|
||||
}
|
||||
};
|
||||
|
||||
let DirEntry::Regular(dir_entry) = dir_entry else {
|
||||
// simply skip long name entries for now
|
||||
return self.next();
|
||||
};
|
||||
|
||||
if dir_entry.is_sentinel() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if dir_entry.is_empty() {
|
||||
return self.next();
|
||||
}
|
||||
|
||||
Some(dir_entry)
|
||||
}
|
||||
}
|
||||
42
src/dump.rs
Normal file
42
src/dump.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use std::io::Read;
|
||||
|
||||
use fat_rs::FatFs;
|
||||
use fat_rs::fat::Fatty as _;
|
||||
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
let args = std::env::args();
|
||||
|
||||
if args.len() != 2 {
|
||||
anyhow::bail!("usage: dump <path>");
|
||||
}
|
||||
|
||||
let file = std::fs::File::open(args.skip(1).next().unwrap())?;
|
||||
|
||||
// let mut buf = [0; 512];
|
||||
|
||||
// file.read_exact(&mut buf)?;
|
||||
|
||||
// let bpb = Bpb::load(&buf)?;
|
||||
|
||||
// println!("{}", bpb);
|
||||
|
||||
let mut fat_fs = FatFs::load(file)?;
|
||||
|
||||
println!("{}", fat_fs.bpb());
|
||||
println!();
|
||||
println!("{}", fat_fs.fat());
|
||||
println!();
|
||||
println!(
|
||||
"free clusters: {} ({} bytes)",
|
||||
fat_fs.fat().count_free_clusters(),
|
||||
fat_fs.fat().count_free_clusters()
|
||||
* fat_fs.bpb().bytes_per_sector() as usize
|
||||
* fat_fs.bpb().sectors_per_cluster() as usize
|
||||
);
|
||||
|
||||
for dir_entry in fat_fs.root_dir_iter() {
|
||||
println!("{}", dir_entry);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
376
src/fat.rs
Normal file
376
src/fat.rs
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
use std::fmt::Display;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
use crate::FatType;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum FatError {
|
||||
#[error("can't get next cluster of free cluster")]
|
||||
FreeCluster,
|
||||
#[error("cluster {0} is reserved")]
|
||||
ReservedCluster(u32),
|
||||
#[error("cluster is defective")]
|
||||
DefectiveCluster,
|
||||
#[error("invalid next cluster 0x{0:0X}")]
|
||||
InvalidEntry(u32),
|
||||
}
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait Fatty {
|
||||
// get the next cluster
|
||||
// assumes the cluster is valid, i.e. allocated
|
||||
fn get_entry(&self, cluster: u32) -> u32;
|
||||
|
||||
fn get_valid_clusters(&self) -> RangeInclusive<u32>;
|
||||
fn get_reserved_clusters(&self) -> RangeInclusive<u32>;
|
||||
fn get_defective_cluster(&self) -> u32;
|
||||
fn get_reserved_eof_clusters(&self) -> RangeInclusive<u32>;
|
||||
fn get_eof_cluster(&self) -> u32;
|
||||
|
||||
fn count_free_clusters(&self) -> usize {
|
||||
self.get_valid_clusters()
|
||||
.map(|cluster| self.get_entry(cluster))
|
||||
.filter(|&entry| entry == 0)
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
#[enum_dispatch(Fatty)]
|
||||
pub enum Fat {
|
||||
Fat12(Fat12),
|
||||
Fat16(Fat16),
|
||||
Fat32(Fat32),
|
||||
}
|
||||
|
||||
impl Display for Fat {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self {
|
||||
Fat::Fat12(fat12) => write!(f, "{}", fat12),
|
||||
Fat::Fat16(fat16) => write!(f, "{}", fat16),
|
||||
Fat::Fat32(fat32) => write!(f, "{}", fat32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Fat {
|
||||
pub fn new(fat_type: FatType, bytes: &[u8], max: u32) -> Fat {
|
||||
match fat_type {
|
||||
FatType::Fat12 => Fat::Fat12(Fat12::new(bytes, max)),
|
||||
FatType::Fat16 => Fat::Fat16(Fat16::new(bytes, max)),
|
||||
FatType::Fat32 => Fat::Fat32(Fat32::new(bytes, max)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_next_cluster(&self, cluster: u32) -> Result<Option<u32>, FatError> {
|
||||
if cluster == 0x000 {
|
||||
// can't get next cluster for free cluster
|
||||
return Err(FatError::FreeCluster);
|
||||
}
|
||||
|
||||
if self.get_reserved_clusters().contains(&cluster) {
|
||||
// can't get next cluster for reserved cluster
|
||||
return Err(FatError::ReservedCluster(cluster));
|
||||
}
|
||||
|
||||
// defective cluster
|
||||
if cluster == self.get_defective_cluster() {
|
||||
// can't get next cluster for defective cluster
|
||||
return Err(FatError::DefectiveCluster);
|
||||
}
|
||||
|
||||
if self.get_reserved_eof_clusters().contains(&cluster) {
|
||||
// Reserved and should not be used. May be interpreted as an allocated cluster and the
|
||||
// final cluster in the file (indicating end-of-file condition).
|
||||
//
|
||||
// can't get next entry for reserved cluster
|
||||
|
||||
// return Ok(None);
|
||||
return Err(FatError::ReservedCluster(cluster));
|
||||
}
|
||||
|
||||
let entry = self.get_entry(cluster);
|
||||
|
||||
// interpret second reserved block as EOF here
|
||||
if entry == self.get_eof_cluster() || self.get_reserved_eof_clusters().contains(&entry) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// entry should be in the valid cluster range here; otherwise something went wrong
|
||||
if !self.get_valid_clusters().contains(&entry) {
|
||||
return Err(FatError::InvalidEntry(entry));
|
||||
}
|
||||
|
||||
Ok(Some(entry))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Fat12 {
|
||||
max: u32,
|
||||
|
||||
next_sectors: Box<[u16]>,
|
||||
}
|
||||
|
||||
impl Display for Fat12 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "Fat 12 {{")?;
|
||||
|
||||
for (i, &x) in self.next_sectors.iter().enumerate() {
|
||||
if x != 0 {
|
||||
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(f, "}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Fat12 {
|
||||
pub fn new(bytes: &[u8], max: u32) -> Fat12 {
|
||||
// for FAT12 max is always less than 4085
|
||||
assert!(max <= 4085);
|
||||
|
||||
let mut next_sectors: Box<[MaybeUninit<u16>]> = Box::new_uninit_slice(max as usize + 1);
|
||||
|
||||
for elem in next_sectors.iter_mut() {
|
||||
elem.write(0);
|
||||
}
|
||||
|
||||
let mut next_sectors = unsafe { next_sectors.assume_init() };
|
||||
|
||||
// assume bytes.len() is multiple of 3
|
||||
// TODO: fix later
|
||||
assert_eq!(bytes.len() % 3, 0);
|
||||
|
||||
let (chunks, rem) = bytes.as_chunks::<3>();
|
||||
|
||||
assert_eq!(rem.len(), 0);
|
||||
|
||||
// TODO: correctly handle cases where max is larger than #FAT entries and v.v.
|
||||
for (idx, triple) in chunks.iter().take(max as usize / 2).enumerate() {
|
||||
// first (even) entry gets truncated
|
||||
let first = u16::from_le_bytes(triple[..2].try_into().unwrap()) & 0xFFF;
|
||||
// second (odd) entry gets shifted
|
||||
let second = u16::from_le_bytes(triple[1..].try_into().unwrap()) >> 4;
|
||||
|
||||
assert!(idx + 1 < next_sectors.len());
|
||||
|
||||
next_sectors[2 * idx] = first;
|
||||
next_sectors[2 * idx + 1] = second;
|
||||
}
|
||||
|
||||
if max % 2 == 1 {
|
||||
// odd max: need to fix last cluster
|
||||
let idx = max as usize / 2 + 1;
|
||||
|
||||
let triple = chunks[idx];
|
||||
|
||||
let first = u16::from_le_bytes(triple[..2].try_into().unwrap()) & 0xFFF;
|
||||
|
||||
next_sectors[2 * idx] = first;
|
||||
}
|
||||
|
||||
Fat12 { max, next_sectors }
|
||||
}
|
||||
}
|
||||
|
||||
impl Fatty for Fat12 {
|
||||
fn get_entry(&self, cluster: u32) -> u32 {
|
||||
let cluster = cluster as usize;
|
||||
assert!(cluster < self.next_sectors.len());
|
||||
|
||||
self.next_sectors[cluster] as u32
|
||||
}
|
||||
|
||||
fn get_valid_clusters(&self) -> RangeInclusive<u32> {
|
||||
2..=self.max
|
||||
}
|
||||
|
||||
fn get_reserved_clusters(&self) -> RangeInclusive<u32> {
|
||||
(self.max as u32 + 1)..=0xFF6
|
||||
}
|
||||
|
||||
fn get_defective_cluster(&self) -> u32 {
|
||||
0xFF7
|
||||
}
|
||||
|
||||
fn get_reserved_eof_clusters(&self) -> RangeInclusive<u32> {
|
||||
0xFF8..=0xFFE
|
||||
}
|
||||
|
||||
fn get_eof_cluster(&self) -> u32 {
|
||||
0xFFF
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Fat16 {
|
||||
max: u32,
|
||||
|
||||
next_sectors: Box<[u16]>,
|
||||
}
|
||||
|
||||
impl Display for Fat16 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "Fat 16 {{")?;
|
||||
|
||||
for (i, &x) in self.next_sectors.iter().enumerate() {
|
||||
if x != 0 {
|
||||
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(f, "}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Fat16 {
|
||||
pub fn new(bytes: &[u8], max: u32) -> Fat16 {
|
||||
// for FAT12 max is always less than 4085
|
||||
assert!(4085 < max && max <= 65525);
|
||||
|
||||
let mut next_sectors: Box<[MaybeUninit<u16>]> = Box::new_uninit_slice(max as usize + 1);
|
||||
|
||||
for elem in next_sectors.iter_mut() {
|
||||
elem.write(0);
|
||||
}
|
||||
|
||||
let mut next_sectors = unsafe { next_sectors.assume_init() };
|
||||
|
||||
// assume bytes.len() is multiple of 2
|
||||
// TODO: fix later
|
||||
assert_eq!(bytes.len() % 2, 0);
|
||||
|
||||
let (chunks, rem) = bytes.as_chunks::<2>();
|
||||
|
||||
assert_eq!(rem.len(), 0);
|
||||
|
||||
// TODO: correctly handle cases where max is larger than #FAT entries and v.v.
|
||||
for (idx, chunk) in chunks.iter().take(max as usize / 2).enumerate() {
|
||||
// first (even) entry gets truncated
|
||||
let entry = u16::from_le_bytes(chunk[..2].try_into().unwrap());
|
||||
|
||||
next_sectors[idx] = entry;
|
||||
}
|
||||
|
||||
Fat16 { max, next_sectors }
|
||||
}
|
||||
}
|
||||
|
||||
impl Fatty for Fat16 {
|
||||
fn get_entry(&self, cluster: u32) -> u32 {
|
||||
let cluster = cluster as usize;
|
||||
assert!(cluster < self.next_sectors.len());
|
||||
|
||||
self.next_sectors[cluster] as u32
|
||||
}
|
||||
|
||||
fn get_valid_clusters(&self) -> RangeInclusive<u32> {
|
||||
2..=self.max
|
||||
}
|
||||
|
||||
fn get_reserved_clusters(&self) -> RangeInclusive<u32> {
|
||||
(self.max as u32 + 1)..=0xFFF6
|
||||
}
|
||||
|
||||
fn get_defective_cluster(&self) -> u32 {
|
||||
0xFFF7
|
||||
}
|
||||
|
||||
fn get_reserved_eof_clusters(&self) -> RangeInclusive<u32> {
|
||||
0xFFF8..=0xFFFE
|
||||
}
|
||||
|
||||
fn get_eof_cluster(&self) -> u32 {
|
||||
0xFFFF
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Fat32 {
|
||||
max: u32,
|
||||
|
||||
next_sectors: Box<[u32]>,
|
||||
}
|
||||
|
||||
impl Display for Fat32 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "Fat 32 {{")?;
|
||||
|
||||
for (i, &x) in self.next_sectors.iter().enumerate() {
|
||||
if x != 0 {
|
||||
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(f, "}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Fat32 {
|
||||
pub fn new(bytes: &[u8], max: u32) -> Fat32 {
|
||||
// for FAT12 max is always less than 4085
|
||||
assert!(65525 < max);
|
||||
|
||||
let mut next_sectors: Box<[MaybeUninit<u32>]> = Box::new_uninit_slice(max as usize + 1);
|
||||
|
||||
for elem in next_sectors.iter_mut() {
|
||||
elem.write(0);
|
||||
}
|
||||
|
||||
let mut next_sectors = unsafe { next_sectors.assume_init() };
|
||||
|
||||
// assume bytes.len() is multiple of 4
|
||||
// TODO: fix later
|
||||
assert_eq!(bytes.len() % 4, 0);
|
||||
|
||||
let (chunks, rem) = bytes.as_chunks::<4>();
|
||||
|
||||
assert_eq!(rem.len(), 0);
|
||||
|
||||
// TODO: correctly handle cases where max is larger than #FAT entries and v.v.
|
||||
for (idx, chunk) in chunks.iter().take(max as usize / 2).enumerate() {
|
||||
// first (even) entry gets truncated
|
||||
let entry = u32::from_le_bytes(chunk[..4].try_into().unwrap());
|
||||
|
||||
next_sectors[idx] = entry;
|
||||
}
|
||||
|
||||
Fat32 { max, next_sectors }
|
||||
}
|
||||
}
|
||||
|
||||
impl Fatty for Fat32 {
|
||||
fn get_entry(&self, cluster: u32) -> u32 {
|
||||
let cluster = cluster as usize;
|
||||
assert!(cluster < self.next_sectors.len());
|
||||
|
||||
self.next_sectors[cluster] as u32
|
||||
}
|
||||
|
||||
fn get_valid_clusters(&self) -> RangeInclusive<u32> {
|
||||
2..=self.max
|
||||
}
|
||||
|
||||
fn get_reserved_clusters(&self) -> RangeInclusive<u32> {
|
||||
(self.max + 1)..=0xFFFFFFF6
|
||||
}
|
||||
|
||||
fn get_defective_cluster(&self) -> u32 {
|
||||
0xFFFFFFF7
|
||||
}
|
||||
|
||||
fn get_reserved_eof_clusters(&self) -> RangeInclusive<u32> {
|
||||
0xFFFFFFF8..=0xFFFFFFFE
|
||||
}
|
||||
|
||||
fn get_eof_cluster(&self) -> u32 {
|
||||
0xFFFFFFFF
|
||||
}
|
||||
}
|
||||
42
src/fs_info.rs
Normal file
42
src/fs_info.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use crate::utils::load_u32_le;
|
||||
|
||||
pub struct FsInfo {
|
||||
free_count: u32,
|
||||
next_free: u32,
|
||||
}
|
||||
|
||||
impl FsInfo {
|
||||
pub fn load(bytes: &[u8]) -> anyhow::Result<FsInfo> {
|
||||
let lead_sig = load_u32_le(&bytes[..4]);
|
||||
|
||||
anyhow::ensure!(
|
||||
lead_sig == 0x41615252,
|
||||
"invalid lead signature: 0x{:#08X} instead of 0x41615252",
|
||||
lead_sig
|
||||
);
|
||||
|
||||
let struct_sig = load_u32_le(&bytes[484..][..4]);
|
||||
|
||||
anyhow::ensure!(
|
||||
struct_sig == 0x61417272,
|
||||
"invalid structural signature: 0x{:#08X} instead of 0x61417272",
|
||||
struct_sig
|
||||
);
|
||||
|
||||
let trail_sig = load_u32_le(&bytes[508..][..4]);
|
||||
|
||||
anyhow::ensure!(
|
||||
trail_sig == 0xAA550000,
|
||||
"invalid trailing signature: 0x{:#08X} instead of 0xAA550000",
|
||||
trail_sig
|
||||
);
|
||||
|
||||
let free_count = load_u32_le(&bytes[488..][..4]);
|
||||
let next_free = load_u32_le(&bytes[492..][..4]);
|
||||
|
||||
Ok(FsInfo {
|
||||
free_count,
|
||||
next_free,
|
||||
})
|
||||
}
|
||||
}
|
||||
53
src/iter.rs
Normal file
53
src/iter.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
use std::io::Read;
|
||||
|
||||
use crate::fat::Fatty;
|
||||
use crate::subslice::SubSlice;
|
||||
use crate::utils::replace;
|
||||
use crate::{FatFs, SliceLike};
|
||||
|
||||
pub struct ClusterChainReader<'a, S: SliceLike> {
|
||||
sub_slice: SubSlice<'a, S>,
|
||||
|
||||
next_cluster: Option<u32>,
|
||||
}
|
||||
|
||||
impl<'a, S: SliceLike> ClusterChainReader<'a, S> {
|
||||
pub fn new(fat_fs: &'a mut FatFs<S>, first_cluster: u32) -> ClusterChainReader<'a, S> {
|
||||
let next_cluster = fat_fs.next_cluster(first_cluster).unwrap_or(None);
|
||||
|
||||
let sub_slice = fat_fs.cluster_as_subslice(first_cluster);
|
||||
|
||||
ClusterChainReader {
|
||||
sub_slice,
|
||||
next_cluster,
|
||||
}
|
||||
}
|
||||
|
||||
fn next_cluster(&mut self) -> bool {
|
||||
let Some(next_cluster) = self.next_cluster else {
|
||||
return false;
|
||||
};
|
||||
|
||||
replace(&mut self.sub_slice, |sub_slice| {
|
||||
let fat_fs = sub_slice.release();
|
||||
|
||||
self.next_cluster = fat_fs.next_cluster(next_cluster).unwrap_or(None);
|
||||
|
||||
fat_fs.cluster_as_subslice(next_cluster)
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: SliceLike> Read for ClusterChainReader<'a, S> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
if self.sub_slice.is_empty() {
|
||||
if !self.next_cluster() {
|
||||
return Ok(0);
|
||||
}
|
||||
}
|
||||
|
||||
self.sub_slice.read(buf)
|
||||
}
|
||||
}
|
||||
255
src/lib.rs
255
src/lib.rs
|
|
@ -1,14 +1,251 @@
|
|||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
use std::io::{Read as _, Seek as _, SeekFrom, Write as _};
|
||||
|
||||
use crate::dir::{DirIter, RegularDirEntry};
|
||||
use crate::fat::{FatError, Fatty};
|
||||
use crate::subslice::{SubSlice, SubSliceMut};
|
||||
|
||||
pub mod bpb;
|
||||
mod datetime;
|
||||
pub mod dir;
|
||||
pub mod fat;
|
||||
pub mod fs_info;
|
||||
mod iter;
|
||||
mod subslice;
|
||||
mod utils;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FatType {
|
||||
Fat12,
|
||||
Fat16,
|
||||
Fat32,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
pub trait SliceLike {
|
||||
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()>;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> std::io::Result<()>;
|
||||
|
||||
fn read_array_at_offset<const N: usize>(&mut self, offset: u64) -> std::io::Result<[u8; N]> {
|
||||
let mut buf = [0; N];
|
||||
|
||||
self.read_at_offset(offset, &mut buf)?;
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl SliceLike for &mut [u8] {
|
||||
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()> {
|
||||
// anyhow::ensure!(
|
||||
// offset as usize + buf.len() <= self.len(),
|
||||
// "reading {} bytes at offset {} is out of bounds for slice of len {}",
|
||||
// buf.len(),
|
||||
// offset,
|
||||
// self.len()
|
||||
// );
|
||||
|
||||
if offset as usize + buf.len() > self.len() {
|
||||
return Err(std::io::Error::other(anyhow::anyhow!(
|
||||
"reading {} bytes at offset {} is out of bounds for slice of len {}",
|
||||
buf.len(),
|
||||
offset,
|
||||
self.len()
|
||||
)));
|
||||
}
|
||||
|
||||
buf.copy_from_slice(&self[offset as usize..][..buf.len()]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> std::io::Result<()> {
|
||||
if offset as usize + bytes.len() > self.len() {
|
||||
return Err(std::io::Error::other(anyhow::anyhow!(
|
||||
"writing {} bytes at offset {} is out of bounds for slice of len {}",
|
||||
bytes.len(),
|
||||
offset,
|
||||
self.len()
|
||||
)));
|
||||
}
|
||||
|
||||
self[offset as usize..][..bytes.len()].copy_from_slice(bytes);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SliceLike for std::fs::File {
|
||||
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()> {
|
||||
self.seek(SeekFrom::Start(offset))?;
|
||||
|
||||
self.read_exact(buf)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> std::io::Result<()> {
|
||||
self.seek(SeekFrom::Start(offset))?;
|
||||
|
||||
self.write_all(bytes)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct FatFs<S: SliceLike> {
|
||||
data: S,
|
||||
|
||||
fat_offset: u64,
|
||||
fat_size: usize,
|
||||
|
||||
root_dir_offset: Option<u64>,
|
||||
root_dir_size: usize,
|
||||
|
||||
pub data_offset: u64,
|
||||
data_size: usize,
|
||||
|
||||
bytes_per_cluster: usize,
|
||||
|
||||
bpb: bpb::Bpb,
|
||||
|
||||
fat: fat::Fat,
|
||||
}
|
||||
|
||||
impl<S: SliceLike> FatFs<S> {
|
||||
pub fn load(mut data: S) -> anyhow::Result<FatFs<S>> {
|
||||
let bpb_bytes: [u8; 512] = data.read_array_at_offset(0)?;
|
||||
|
||||
let bpb = bpb::Bpb::load(&bpb_bytes)?;
|
||||
|
||||
let mut fat_buf = vec![0; bpb.fat_len_bytes()];
|
||||
|
||||
data.read_at_offset(bpb.fat_offset(), &mut fat_buf)?;
|
||||
|
||||
let fat = fat::Fat::new(bpb.fat_type(), &fat_buf, bpb.count_of_clusters());
|
||||
|
||||
// {
|
||||
// let eof = fat.get_eof_cluster();
|
||||
|
||||
// if fat.get_entry(0) != (eof & !0xFF) | bpb.media() as u32 {
|
||||
// eprintln!("warning: first sector entry should have media in lowest byte");
|
||||
// }
|
||||
|
||||
// if fat.get_entry(1) != eof {
|
||||
// eprintln!(
|
||||
// "warning: second sector entry should be EOF, not {:#X}",
|
||||
// fat.get_entry(1)
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
let fat_offset = bpb.fat_offset();
|
||||
let fat_size = bpb.fat_len_bytes();
|
||||
|
||||
let root_dir_offset = bpb.root_directory_offset();
|
||||
let root_dir_size = bpb.root_dir_len_bytes();
|
||||
|
||||
let data_offset = bpb.data_offset();
|
||||
let data_size = bpb.data_len_bytes();
|
||||
|
||||
let bytes_per_cluster = bpb.bytes_per_cluster();
|
||||
|
||||
Ok(FatFs {
|
||||
data,
|
||||
fat_offset,
|
||||
fat_size,
|
||||
root_dir_offset,
|
||||
root_dir_size,
|
||||
data_offset,
|
||||
data_size,
|
||||
bytes_per_cluster,
|
||||
bpb,
|
||||
fat,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn bpb(&self) -> &bpb::Bpb {
|
||||
&self.bpb
|
||||
}
|
||||
|
||||
pub fn fat(&self) -> &fat::Fat {
|
||||
&self.fat
|
||||
}
|
||||
|
||||
/// byte offset of data cluster
|
||||
pub fn data_cluster_to_offset(&self, cluster: u32) -> u64 {
|
||||
// assert!(cluster >= 2);
|
||||
|
||||
assert!(self.fat().get_valid_clusters().contains(&cluster));
|
||||
|
||||
self.data_offset + (cluster - 2) as u64 * self.bytes_per_cluster as u64
|
||||
}
|
||||
|
||||
/// next data cluster or None is cluster is EOF
|
||||
///
|
||||
/// giving an invalid cluster (free, reserved, or defective) returns an appropriate error
|
||||
pub fn next_cluster(&self, cluster: u32) -> Result<Option<u32>, FatError> {
|
||||
self.fat().get_next_cluster(cluster)
|
||||
}
|
||||
|
||||
pub fn cluster_as_subslice_mut(&mut self, cluster: u32) -> SubSliceMut<'_, S> {
|
||||
let offset = self.data_cluster_to_offset(cluster);
|
||||
|
||||
SubSliceMut::new(self, offset, self.bytes_per_cluster)
|
||||
}
|
||||
|
||||
pub fn cluster_as_subslice(&mut self, cluster: u32) -> SubSlice<'_, S> {
|
||||
let offset = self.data_cluster_to_offset(cluster);
|
||||
|
||||
SubSlice::new(self, offset, self.bytes_per_cluster)
|
||||
}
|
||||
|
||||
pub fn root_dir_bytes(&mut self) -> std::io::Result<Vec<u8>> {
|
||||
if let Some(root_dir_offset) = self.root_dir_offset {
|
||||
let mut data = Vec::new();
|
||||
|
||||
let mut subslice = SubSliceMut::new(self, root_dir_offset, self.root_dir_size);
|
||||
|
||||
subslice.read_to_end(&mut data)?;
|
||||
|
||||
return Ok(data);
|
||||
}
|
||||
|
||||
let mut cluster = self.bpb().root_cluster().unwrap();
|
||||
|
||||
let mut data = vec![0; self.bytes_per_cluster];
|
||||
|
||||
self.data
|
||||
.read_at_offset(self.data_cluster_to_offset(cluster), &mut data)?;
|
||||
|
||||
while let Ok(Some(next_cluster)) = self.next_cluster(cluster) {
|
||||
cluster = next_cluster;
|
||||
|
||||
self.data
|
||||
.read_at_offset(self.data_cluster_to_offset(cluster), &mut data)?;
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn root_dir_iter(&mut self) -> Box<dyn Iterator<Item = RegularDirEntry> + '_> {
|
||||
// TODO: maybe wrap this in another RootDirIter enum, so we don't have to Box<dyn>
|
||||
|
||||
if let Some(root_dir_offset) = self.root_dir_offset {
|
||||
// FAT12/FAT16
|
||||
|
||||
let sub_slice = SubSlice::new(self, root_dir_offset, self.root_dir_size);
|
||||
|
||||
return Box::new(DirIter::new(sub_slice));
|
||||
}
|
||||
|
||||
// FAT32
|
||||
|
||||
// can't fail; we're in the FAT32 case
|
||||
let root_cluster = self.bpb().root_cluster().unwrap();
|
||||
|
||||
let cluster_iter = iter::ClusterChainReader::new(self, root_cluster);
|
||||
|
||||
Box::new(DirIter::new(cluster_iter))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
117
src/subslice.rs
Normal file
117
src/subslice.rs
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
use std::io::{Read, Write};
|
||||
|
||||
use crate::{FatFs, SliceLike};
|
||||
|
||||
pub struct SubSliceMut<'a, S: SliceLike> {
|
||||
fat_fs: &'a mut FatFs<S>,
|
||||
|
||||
offset: u64,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<S: SliceLike> SubSliceMut<'_, S> {
|
||||
pub fn new(fat_fs: &mut FatFs<S>, offset: u64, len: usize) -> SubSliceMut<'_, S> {
|
||||
SubSliceMut {
|
||||
fat_fs,
|
||||
offset,
|
||||
len,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: SliceLike> SubSliceMut<'_, S> {
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: SliceLike> Read for SubSliceMut<'_, S> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let bytes_to_read = self.len.min(buf.len());
|
||||
|
||||
self.fat_fs
|
||||
.data
|
||||
.read_at_offset(self.offset, &mut buf[..bytes_to_read])?;
|
||||
|
||||
self.offset += bytes_to_read as u64;
|
||||
self.len -= bytes_to_read;
|
||||
|
||||
Ok(bytes_to_read)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: SliceLike> Write for SubSliceMut<'_, S> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let bytes_to_write = self.len.min(buf.len());
|
||||
|
||||
self.fat_fs
|
||||
.data
|
||||
.write_at_offset(self.offset, &buf[..bytes_to_write])?;
|
||||
|
||||
self.offset += bytes_to_write as u64;
|
||||
self.len -= bytes_to_write;
|
||||
|
||||
Ok(bytes_to_write)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SubSlice<'a, S: SliceLike> {
|
||||
fat_fs: &'a mut FatFs<S>,
|
||||
|
||||
offset: u64,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<S: SliceLike> SubSlice<'_, S> {
|
||||
pub fn new(fat_fs: &mut FatFs<S>, offset: u64, len: usize) -> SubSlice<'_, S> {
|
||||
SubSlice {
|
||||
fat_fs,
|
||||
offset,
|
||||
len,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fat_fs(&self) -> &FatFs<S> {
|
||||
self.fat_fs
|
||||
}
|
||||
|
||||
pub fn fat_fs_mut(&self) -> &FatFs<S> {
|
||||
self.fat_fs
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: SliceLike> SubSlice<'a, S> {
|
||||
pub fn release(self) -> &'a mut FatFs<S> {
|
||||
self.fat_fs
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: SliceLike> Read for SubSlice<'_, S> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let bytes_to_read = self.len.min(buf.len());
|
||||
|
||||
self.fat_fs
|
||||
.data
|
||||
.read_at_offset(self.offset, &mut buf[..bytes_to_read])?;
|
||||
|
||||
self.offset += bytes_to_read as u64;
|
||||
self.len -= bytes_to_read;
|
||||
|
||||
Ok(bytes_to_read)
|
||||
}
|
||||
}
|
||||
26
src/utils.rs
Normal file
26
src/utils.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
pub fn load_u16_le(bytes: &[u8]) -> u16 {
|
||||
assert_eq!(bytes.len(), 2);
|
||||
|
||||
u16::from_le_bytes(bytes.try_into().unwrap())
|
||||
}
|
||||
pub fn load_u32_le(bytes: &[u8]) -> u32 {
|
||||
assert_eq!(bytes.len(), 4);
|
||||
|
||||
u32::from_le_bytes(bytes.try_into().unwrap())
|
||||
}
|
||||
|
||||
/// replace the value at x with f(x)
|
||||
///
|
||||
/// SAFETY:
|
||||
/// should be safe, I guess? MIRI didn't complain about it
|
||||
pub fn replace<T>(x: &mut T, f: impl FnOnce(T) -> T) {
|
||||
unsafe {
|
||||
let x_ptr = x as *mut T;
|
||||
|
||||
let old_x = std::ptr::read(x_ptr);
|
||||
|
||||
let new_x = f(old_x);
|
||||
|
||||
std::ptr::write(x_ptr, new_x);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue