Compare commits

..

1 commit

Author SHA1 Message Date
7921064ae2 moved short name str into separate buffer 2025-07-29 20:34:35 +02:00
21 changed files with 844 additions and 2695 deletions

393
Cargo.lock generated
View file

@ -2,15 +2,6 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
@ -26,56 +17,6 @@ dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "anyhow"
version = "1.0.98"
@ -100,21 +41,6 @@ version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "castaway"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
dependencies = [
"rustversion",
]
[[package]]
name = "cc"
version = "1.2.30"
@ -148,52 +74,12 @@ dependencies = [
"windows-link",
]
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "compact_str"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
dependencies = [
"castaway",
"cfg-if",
"itoa",
"rustversion",
"ryu",
"static_assertions",
]
[[package]]
name = "compact_string"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5255b88d8ea09573f588088dea17fbea682b4442abea6761a15d1da2c3a76c5c"
dependencies = [
"serde",
"thiserror 1.0.69",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "ctrlc"
version = "3.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73"
dependencies = [
"nix 0.30.1",
"windows-sys",
]
[[package]]
name = "enum_dispatch"
version = "0.3.13"
@ -206,29 +92,6 @@ dependencies = [
"syn",
]
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]]
name = "fat-bits"
version = "0.1.0"
@ -236,11 +99,9 @@ dependencies = [
"anyhow",
"bitflags",
"chrono",
"compact_str",
"enum_dispatch",
"log",
"static_assertions",
"thiserror 2.0.12",
"thiserror",
]
[[package]]
@ -256,27 +117,13 @@ name = "fat-fuse"
version = "0.1.0"
dependencies = [
"anyhow",
"bitflags",
"chrono",
"compact_string",
"fat-bits",
"fuser",
"fxhash",
"libc",
"log",
"rand",
"thiserror 2.0.12",
]
[[package]]
name = "fat-mount"
version = "0.1.0"
dependencies = [
"anyhow",
"ctrlc",
"env_logger",
"fat-fuse",
"fuser",
"thiserror",
]
[[package]]
@ -288,22 +135,13 @@ dependencies = [
"libc",
"log",
"memchr",
"nix 0.29.0",
"nix",
"page_size",
"pkg-config",
"smallvec",
"zerocopy",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "getrandom"
version = "0.3.3"
@ -340,42 +178,6 @@ dependencies = [
"cc",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
]
[[package]]
name = "jiff-static"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "js-sys"
version = "0.3.77"
@ -416,18 +218,6 @@ dependencies = [
"libc",
]
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@ -443,12 +233,6 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "page_size"
version = "0.6.0"
@ -465,21 +249,6 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "portable-atomic"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
@ -522,67 +291,12 @@ dependencies = [
"getrandom",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustversion"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "shlex"
version = "1.3.0"
@ -612,33 +326,13 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl 2.0.12",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"thiserror-impl",
]
[[package]]
@ -658,12 +352,6 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
@ -812,79 +500,6 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"

View file

@ -1,3 +1,3 @@
[workspace]
resolver = "3"
members = ["fat-bits", "fat-dump", "fat-fuse", "fat-mount"]
members = ["fat-bits", "fat-dump", "fat-fuse"]

View file

@ -1,9 +1 @@
# FUSE implementation for the FAT file system
End-to-end implementation of a FAT driver, with the goal of being able to mount disk images with complete read-write support.
Uses (fuser)[https://docs.rs/fuser/latest/fuser/] as the underlying FUSE library.
## Specification
[https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf](https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf)
specification: [https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf](https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf)

View file

@ -8,11 +8,8 @@ anyhow = "1.0.98"
bitflags = "2.9.1"
chrono = { version = "0.4.41", default-features = false, features = [
"alloc",
"clock",
"std",
] }
compact_str = "0.9.0"
enum_dispatch = "0.3.13"
log = "0.4.27"
static_assertions = "1.1.0"
thiserror = "2.0.12"

View file

@ -236,7 +236,6 @@ impl Bpb {
if self.fat_type() == FatType::Fat32 {
return None;
}
Some(self.fat_offset() + self.sector_to_offset(self.num_fats() as u32 * self.fat_size()))
}

View file

@ -1,6 +1,8 @@
use chrono::{DateTime, Datelike, Local, NaiveDate, NaiveTime, Timelike};
use std::time::SystemTime;
#[derive(Debug, Clone, Copy)]
use chrono::{DateTime, Datelike, NaiveDate, NaiveTime, Timelike, Utc};
#[derive(Debug)]
pub struct Date {
repr: u16,
}
@ -20,7 +22,8 @@ impl Date {
Ok(date)
}
fn from_day_month_year(day: u8, month: u8, year: u16) -> anyhow::Result<Date> {
#[allow(dead_code)]
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);
@ -30,7 +33,10 @@ impl Date {
Ok(Date { repr })
}
pub fn from_datetime(datetime: DateTime<Local>) -> anyhow::Result<Date> {
#[allow(dead_code)]
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(
@ -40,10 +46,6 @@ impl Date {
)
}
pub fn repr(&self) -> u16 {
self.repr
}
pub fn day(&self) -> u8 {
(self.repr & 0x1F) as u8
}
@ -61,7 +63,7 @@ impl Date {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug)]
pub struct Time {
repr: u16,
}
@ -73,7 +75,8 @@ impl Time {
Ok(time)
}
fn from_seconds_minutes_hours(seconds: u8, minutes: u8, hours: u8) -> anyhow::Result<Time> {
#[allow(dead_code)]
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);
@ -83,7 +86,10 @@ impl Time {
Ok(Time { repr })
}
pub fn from_datetime(datetime: DateTime<Local>) -> anyhow::Result<Time> {
#[allow(dead_code)]
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;
@ -91,10 +97,6 @@ impl Time {
Time::from_seconds_minutes_hours(seconds, time.minute() as u8, time.hour() as u8)
}
pub fn repr(&self) -> u16 {
self.repr
}
pub fn second(&self) -> u8 {
2 * (self.repr & 0x1F) as u8
}

View file

@ -1,16 +1,10 @@
use std::fmt::Display;
use std::io::{Read, Write};
use std::time::SystemTime;
use std::io::Read;
use bitflags::bitflags;
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, TimeDelta, Timelike};
use compact_str::CompactString;
use log::debug;
use chrono::{NaiveDate, NaiveDateTime, TimeDelta};
use crate::FatFs;
use crate::datetime::{Date, Time};
use crate::iter::ClusterChainReader;
use crate::subslice::SubSliceMut;
use crate::utils::{load_u16_le, load_u32_le};
bitflags! {
@ -70,29 +64,30 @@ pub struct DirEntry {
file_size: u32,
checksum: u8,
long_name: Option<CompactString>,
n_longname_slots: u8,
offset: u64,
// buffer for holding short name str representation
// initial dot if hidden (1)
// stem (8)
// dot (1)
// extension (3)
short_name: [u8; 13],
long_name: Option<String>,
}
impl Display for DirEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut name = self.name_string();
let name = self.name_str().unwrap_or("<unknown>");
if self.attr.contains(Attr::Directory) {
name.push('/');
}
// add slash to end of dir_names
let dir_slash = if self.is_dir() { "/" } else { " " };
write!(
f,
"{} {}",
"{} {}{}",
self.attr,
// self.create_time().format("%a %b %d %H:%M:%S%.3f %Y"),
// self.write_time().format("%a %b %d %H:%M:%S%.3f %Y"),
name,
dir_slash,
)?;
Ok(())
@ -100,13 +95,12 @@ impl Display for DirEntry {
}
impl DirEntry {
pub fn load(bytes: &[u8], offset: u64) -> anyhow::Result<DirEntry> {
pub fn load(bytes: &[u8]) -> anyhow::Result<DirEntry> {
assert_eq!(bytes.len(), 32);
let name: [u8; 11] = bytes[..11].try_into().unwrap();
let attr = Attr::from_bits_truncate(bytes[11]);
let name = bytes[..11].try_into().unwrap();
let create_time_tenths = bytes[13];
anyhow::ensure!(
create_time_tenths <= 199,
@ -142,6 +136,32 @@ impl DirEntry {
)
}
let mut short_name = [0; 13];
let mut short_name_iter = short_name.iter_mut();
if attr.contains(Attr::Hidden) {
// if hidden: lead with '.'
*short_name_iter.next().unwrap() = b'.';
}
if name[0] != 0 && name[0] != 0xe5 {
// dir_entry is not free
for c in name[..8].iter().filter(|&x| x != &0x20) {
*short_name_iter.next().unwrap() = *c;
}
if &name[8..] != &[0x20, 0x20, 0x20] {
// if extension is not empty: add dot and extension
*short_name_iter.next().unwrap() = b'.';
for c in name[8..].iter().filter(|&x| x != &0x20) {
*short_name_iter.next().unwrap() = *c;
}
}
}
Ok(DirEntry {
name,
attr,
@ -153,95 +173,11 @@ impl DirEntry {
write_time,
write_date,
file_size,
short_name,
long_name: None,
n_longname_slots: 0,
checksum: Self::checksum(&bytes[..11]),
offset,
})
}
pub fn create(_name: &str, attr: Attr) -> anyhow::Result<Self> {
// TODO
let now: DateTime<Local> = SystemTime::now().into();
let create_date = Date::from_datetime(now)?;
let create_time = Time::from_datetime(now)?;
let create_time_tenths = (now.time().nanosecond() / 100_000_000) as u8;
let name = [0; 11];
Ok(DirEntry {
name,
attr,
create_time_tenths,
create_time,
create_date,
last_access_date: create_date,
first_cluster: 0,
write_time: create_time,
write_date: create_date,
file_size: 0,
checksum: Self::checksum(&name),
long_name: None,
n_longname_slots: 0,
offset: !0,
})
}
fn write(&self, mut writer: impl Write) -> std::io::Result<()> {
let mut buf = [0; 32];
buf[..11].copy_from_slice(self.name());
buf[11] = self.attr().bits();
buf[12] = 0;
buf[13] = self.create_time_tenths;
buf[13..15].copy_from_slice(&self.create_time.repr().to_le_bytes());
buf[16..18].copy_from_slice(&self.create_date.repr().to_le_bytes());
buf[18..20].copy_from_slice(&self.last_access_date.repr().to_le_bytes());
buf[20..22].copy_from_slice(&((self.first_cluster() >> 16) as u16).to_le_bytes());
buf[22..24].copy_from_slice(&self.write_time.repr().to_le_bytes());
buf[24..26].copy_from_slice(&self.write_date.repr().to_le_bytes());
buf[26..28].copy_from_slice(&(self.first_cluster as u16).to_le_bytes());
buf[28..].copy_from_slice(&self.file_size.to_le_bytes());
debug!("self: {self:?}");
debug!("writing new dir entry: {:?}", buf);
writer.write_all(&buf)?;
Ok(())
}
/// write this DisEntry back to the underlying data
pub fn write_back(&self, fat_fs: &FatFs) -> std::io::Result<()> {
eprintln!("making new SubSliceMut at offset {:#X}", self.offset);
let sub_slice = SubSliceMut::new(fat_fs.inner.clone(), self.offset, 32);
self.write(sub_slice)
}
/// erase this DirEntry
pub fn erase(self, fat_fs: &FatFs) -> std::io::Result<()> {
let mut sub_slice = SubSliceMut::new(fat_fs.inner.clone(), self.offset, 32);
// set first byte to 0xE5 (free)
sub_slice.write_all(&[0xe5])?;
// paste over with zeros
sub_slice.write_all(&[0; 31])
// TODO: erase long filename entries as well
}
/// indicates this DirEntry is empty
///
/// can be either simply empty (0xe5) or the sentinel (0x00) that indicates that all following
@ -270,6 +206,8 @@ impl DirEntry {
return false;
}
// &self.name[..2] == &[b'.', b' ']
self.name[0] == b'.' && &self.name[1..] == &[b' '; 10]
}
@ -278,6 +216,8 @@ impl DirEntry {
return false;
}
// &self.name[..3] == &[b'.', b'.', b' ']
&self.name[..2] == &[b'.', b'.'] && &self.name[2..] == &[b' '; 9]
}
@ -309,60 +249,54 @@ impl DirEntry {
std::str::from_utf8(self.extension()).ok()
}
pub fn name_string(&self) -> CompactString {
// use a CompactString here to allow inlining of short names
// maybe switch to a Cow instead? has disadvantage that we need to alloc for short names
fn short_name(&self) -> &[u8] {
&self.short_name
}
// can't be empty
assert!(!self.is_empty());
fn short_name_str(&self) -> Option<&str> {
let mut short_name = self.short_name();
if let Some(long_filename) = self.long_name() {
return long_filename.into();
// remove trailing zeros
while short_name.last() == Some(&0) {
short_name = &short_name[..short_name.len() - 1];
}
let name = &self.name[..8];
let ext = &self.name[8..];
let mut s = CompactString::const_new("");
if self.attr.contains(Attr::Hidden) {
s.push('.');
}
const VALID_SYMBOLS: &[u8] = &[
b'$', b'%', b'\'', b'-', b'_', b'@', b'~', b'`', b'!', b'(', b')', b'{', b'}', b'^',
b'#', b'&',
];
fn map_chars(c: u8) -> char {
if !c.is_ascii()
|| c < 0x20
|| !(c.is_ascii_alphanumeric() || VALID_SYMBOLS.contains(&c))
{
'?'
} else {
(c as char).to_ascii_uppercase()
}
}
s.extend(name.trim_ascii_end().iter().copied().map(map_chars));
if !ext.is_empty() {
s.push('.');
s.extend(ext.trim_ascii_end().iter().copied().map(map_chars));
}
s
std::str::from_utf8(short_name).ok()
}
pub fn long_name(&self) -> Option<&str> {
self.long_name.as_deref()
}
pub fn set_long_name(&mut self, long_name: CompactString, n_slots: u8) {
pub fn set_long_name(&mut self, long_name: String) {
self.long_name = Some(long_name);
self.n_longname_slots = n_slots;
}
pub fn name_str(&self) -> Option<&str> {
if let Some(long_filename) = self.long_name() {
return Some(long_filename);
}
self.short_name_str()
// 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 attr(&self) -> Attr {
@ -384,15 +318,6 @@ impl DirEntry {
self.last_access_date.to_naive_date()
}
pub fn update_last_access_date(
&mut self,
time: impl Into<DateTime<Local>>,
) -> anyhow::Result<()> {
self.last_access_date = Date::from_datetime(time.into())?;
Ok(())
}
pub fn first_cluster(&self) -> u32 {
self.first_cluster
}
@ -404,27 +329,14 @@ impl DirEntry {
NaiveDateTime::new(date, time)
}
pub fn update_write_time(&mut self, time: impl Into<DateTime<Local>>) -> anyhow::Result<()> {
let time = time.into();
self.write_date = Date::from_datetime(time)?;
self.write_time = Time::from_datetime(time)?;
Ok(())
}
pub fn file_size(&self) -> u32 {
self.file_size
}
pub fn update_file_size(&mut self, file_size: u32) {
self.file_size = file_size
}
pub fn checksum(name: &[u8]) -> u8 {
pub fn checksum(&self) -> u8 {
let mut checksum: u8 = 0;
for &x in name {
for &x in self.name() {
checksum = checksum.rotate_right(1).wrapping_add(x);
}
@ -435,7 +347,6 @@ impl DirEntry {
/// long filename entry in a directory
///
/// this should not be exposed to end users, only for internal consumption in the DirIter
#[derive(Debug)]
struct LongNameDirEntry {
ordinal: u8,
is_last: bool,
@ -510,14 +421,13 @@ impl LongNameDirEntry {
///
/// should not be exposed publicly, end users only see DirEntries
/// just for making the bytes -> DirEntry loading a bit easier
#[derive(Debug)]
enum DirEntryWrapper {
Regular(DirEntry),
LongName(LongNameDirEntry),
}
impl DirEntryWrapper {
pub fn load(bytes: &[u8], offset: u64) -> anyhow::Result<DirEntryWrapper> {
pub fn load(bytes: &[u8]) -> anyhow::Result<DirEntryWrapper> {
assert_eq!(bytes.len(), 32);
let attr = Attr::from_bits_truncate(bytes[11]);
@ -525,7 +435,7 @@ impl DirEntryWrapper {
let dir_entry = if attr == Attr::LongName {
DirEntryWrapper::LongName(LongNameDirEntry::load(bytes)?)
} else {
DirEntryWrapper::Regular(DirEntry::load(bytes, offset)?)
DirEntryWrapper::Regular(DirEntry::load(bytes)?)
};
Ok(dir_entry)
@ -537,7 +447,6 @@ struct LongFilenameBuf {
rev_buf: Vec<u16>,
checksum: Option<u8>,
last_ordinal: Option<u8>,
n_slots: u8,
}
impl LongFilenameBuf {
@ -545,43 +454,28 @@ impl LongFilenameBuf {
self.rev_buf.clear();
self.checksum = None;
self.last_ordinal = None;
self.n_slots = 0;
}
pub fn next(&mut self, dir_entry: LongNameDirEntry) -> anyhow::Result<()> {
if dir_entry.is_last() {
// first/lasts entry
anyhow::ensure!(
dir_entry.ordinal() <= 20,
"can't have more than 20 long filename dir entries"
);
let mut name = dir_entry.name();
while name.last() == Some(&0xFFFF) {
name = &name[..name.len() - 1];
}
if name.last() == Some(&0) {
name = &name[..name.len() - 1];
} else {
// no null terminator only for names that are multiples of 13, i.e. perfectly fit
assert_eq!(name.len(), 13);
}
assert!(!name.is_empty());
self.extend_name(name);
self.checksum = Some(dir_entry.checksum());
self.last_ordinal = Some(dir_entry.ordinal());
self.n_slots += 1;
return Ok(());
}
assert!(self.checksum.is_some() && self.last_ordinal.is_some());
assert!(self.checksum.is_some());
anyhow::ensure!(
self.checksum == Some(dir_entry.checksum()),
@ -609,10 +503,7 @@ impl LongFilenameBuf {
self.rev_buf.extend(name.iter().rev());
}
pub fn get_buf(
&mut self,
checksum: u8,
) -> anyhow::Result<Option<(impl Iterator<Item = u16>, u8)>> {
pub fn get_buf(&mut self, checksum: u8) -> anyhow::Result<Option<impl Iterator<Item = u16>>> {
if self.checksum.is_none() {
return Ok(None);
}
@ -634,97 +525,100 @@ impl LongFilenameBuf {
self.checksum.unwrap()
);
anyhow::ensure!(self.rev_buf.len() <= 255, "long filename too long");
Ok(Some((self.rev_buf.iter().copied().rev(), self.n_slots)))
Ok(Some(self.rev_buf.iter().copied().rev()))
}
}
pub struct DirIter<'a> {
reader: ClusterChainReader<'a>,
pub struct DirIter<R: Read> {
reader: R,
// long_filename_rev_buf: Vec<u16>,
// long_filename_checksum: Option<u8>,
// long_filename_last_ordinal: Option<u8>,
long_filename_buf: LongFilenameBuf,
}
impl<'a> DirIter<'a> {
pub fn new(reader: ClusterChainReader<'a>) -> Self {
impl<R: Read> DirIter<R> {
pub fn new(reader: R) -> DirIter<R> {
DirIter {
reader,
// long_filename_rev_buf: Vec::new(),
// long_filename_checksum: None,
// long_filename_last_ordinal: None,
long_filename_buf: Default::default(),
}
}
pub fn find_by_name(&mut self, name: &str) -> Option<DirEntry> {
self.find(|dir_entry| &dir_entry.name_string() == name)
/// inner function for iterator
fn next_impl(&mut self) -> anyhow::Result<Option<DirEntry>> {
let mut chunk = [0; 32];
if self.reader.read_exact(&mut chunk).is_err() {
// reading failed; nothing we can do here
return Ok(None);
}
// let Ok(dir_entry) = DirEntry::load(&chunk) else {
// return self.next();
// };
let dir_entry = DirEntryWrapper::load(&chunk)
.map_err(|e| anyhow::anyhow!("failed to load dir entry: {e}"))?;
let mut dir_entry = match dir_entry {
DirEntryWrapper::Regular(dir_entry) => dir_entry,
DirEntryWrapper::LongName(long_name) => {
self.long_filename_buf.next(long_name).map_err(|e| {
self.long_filename_buf.reset();
anyhow::anyhow!("invalid long filename entry: {e}")
})?;
return self.next_impl();
}
};
if dir_entry.is_sentinel() {
return Ok(None);
}
if dir_entry.is_empty() {
return self.next_impl();
}
match self
.long_filename_buf
.get_buf(dir_entry.checksum())
.map_err(|e| {
anyhow::anyhow!(
"failed to get long filename for {}: {}",
dir_entry.name_str().as_deref().unwrap_or("<invalid>"),
e
)
})? {
Some(iter) => {
// attach long filename to dir_entry
let long_filename: String =
char::decode_utf16(iter).filter_map(|x| x.ok()).collect();
dir_entry.set_long_name(long_filename);
}
None => {} // no long filename -> do nothing
}
self.long_filename_buf.reset();
Ok(Some(dir_entry))
}
}
impl Iterator for DirIter<'_> {
impl<R: Read> Iterator for DirIter<R> {
type Item = DirEntry;
fn next(&mut self) -> Option<Self::Item> {
fn next_impl(me: &mut DirIter<'_>) -> anyhow::Result<Option<DirEntry>> {
let offset = me.reader.current_offset();
let mut chunk = [0; 32];
if me.reader.read_exact(&mut chunk).is_err() {
// nothing we can do here since we might be in an invalid state after a partial read
anyhow::bail!("read failed");
}
let dir_entry = DirEntryWrapper::load(&chunk, offset)
.map_err(|e| anyhow::anyhow!("failed to load dir entry: {e}"))?;
let mut dir_entry = match dir_entry {
DirEntryWrapper::Regular(dir_entry) => dir_entry,
DirEntryWrapper::LongName(long_name) => {
me.long_filename_buf.next(long_name).map_err(|e| {
me.long_filename_buf.reset();
anyhow::anyhow!("invalid long filename entry: {e}")
})?;
return next_impl(me);
}
};
if dir_entry.is_sentinel() {
return Ok(None);
}
if dir_entry.is_empty() {
return next_impl(me);
}
if let Some((iter, n_slots)) = me
.long_filename_buf
.get_buf(dir_entry.checksum)
.map_err(|e| {
anyhow::anyhow!(
"failed to get long filename for {}: {}",
dir_entry.name_string(),
e
)
})?
{
// attach long filename to dir_entry
let long_filename: CompactString =
char::decode_utf16(iter).filter_map(|x| x.ok()).collect();
dir_entry.set_long_name(long_filename, n_slots);
}
me.long_filename_buf.reset();
Ok(Some(dir_entry))
}
match next_impl(self) {
match self.next_impl() {
Ok(x) => x,
Err(e) => {
// print error message, try next
debug!("{}", e);
eprintln!("{}", e);
self.next()
}

View file

@ -1,15 +1,10 @@
use std::fmt::Display;
use std::io::Write as _;
use std::mem::MaybeUninit;
use std::ops::RangeInclusive;
use enum_dispatch::enum_dispatch;
use log::debug;
use crate::FatType;
use crate::subslice::SubSliceMut;
const FREE_ENTRY: u32 = 0;
#[derive(Debug, thiserror::Error)]
pub enum FatError {
@ -24,24 +19,26 @@ pub enum FatError {
}
#[enum_dispatch]
trait FatOps {
pub trait Fatty {
// get the next cluster
// assumes the cluster is valid, i.e. allocated
fn get_entry(&self, cluster: u32) -> u32;
fn set_entry(&mut self, cluster: u32, entry: u32);
fn valid_entries(&self) -> RangeInclusive<u32>;
fn reserved_entries(&self) -> RangeInclusive<u32>;
fn defective_entry(&self) -> u32;
fn reserved_eof_entries(&self) -> RangeInclusive<u32>;
fn eof_entry(&self) -> 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 write_to_disk(&self, sub_slice: SubSliceMut) -> std::io::Result<()>;
fn count_free_clusters(&self) -> usize {
self.get_valid_clusters()
.map(|cluster| self.get_entry(cluster))
.filter(|&entry| entry == 0)
.count()
}
}
#[enum_dispatch(FatOps)]
// others should never touch the inner FatNs directly, but instead go through the Fat type
#[allow(private_interfaces)]
#[enum_dispatch(Fatty)]
pub enum Fat {
Fat12(Fat12),
Fat16(Fat16),
@ -67,32 +64,24 @@ impl Fat {
}
}
pub fn fat_type(&self) -> FatType {
match self {
Fat::Fat12(_) => FatType::Fat12,
Fat::Fat16(_) => FatType::Fat16,
Fat::Fat32(_) => FatType::Fat32,
}
}
pub fn get_next_cluster(&self, cluster: u32) -> Result<Option<u32>, FatError> {
if cluster == FREE_ENTRY {
if cluster == 0x000 {
// can't get next cluster for free cluster
return Err(FatError::FreeCluster);
}
if self.reserved_entries().contains(&cluster) {
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.defective_entry() {
if cluster == self.get_defective_cluster() {
// can't get next cluster for defective cluster
return Err(FatError::DefectiveCluster);
}
if self.reserved_eof_entries().contains(&cluster) {
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).
//
@ -104,117 +93,21 @@ impl Fat {
let entry = self.get_entry(cluster);
if entry == FREE_ENTRY {
Ok(None)
} else if entry == self.eof_entry() {
Ok(None)
} else if self.valid_entries().contains(&entry) {
Ok(Some(entry))
} else if self.reserved_entries().contains(&entry) || entry == 1 {
Err(FatError::ReservedCluster(entry))
} else if entry == self.defective_entry() {
Err(FatError::DefectiveCluster)
} else {
unreachable!()
}
}
/// set the next cluster of `cluster` to either some `next_cluster` or EOF
///
/// if `cluster` currently points to another cluster, that cluster MUST be the EOF and will be
/// freed
pub fn set_next_cluster(&mut self, cluster: u32, next_cluster: Option<u32>) {
assert!(self.valid_entries().contains(&cluster));
let cur_next_cluster = self.get_entry(cluster);
// can't be defective
assert_ne!(cur_next_cluster, self.defective_entry());
if self.valid_entries().contains(&cur_next_cluster) {
// cluster currently points to a valid cluster, so we free it
log::debug!("freeing chain beginning at {cluster}");
self.free_chain(cluster);
// interpret second reserved block as EOF here
if entry == self.get_eof_cluster() || self.get_reserved_eof_clusters().contains(&entry) {
return Ok(None);
}
if let Some(next_cluster) = next_cluster {
assert!(self.valid_entries().contains(&next_cluster));
// 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));
}
if let Some(next_cluster) = next_cluster {
log::debug!("setting {cluster} -> {next_cluster}");
} else {
log::debug!("setting {cluster} EOF");
}
self.set_entry(cluster, next_cluster.unwrap_or(self.eof_entry()));
}
/// free a cluster
///
/// must be EOF
pub fn free_cluster(&mut self, cluster: u32) {
debug!("freeing cluster {cluster}");
let entry = self.get_entry(cluster);
if entry == FREE_ENTRY {
// nothing to be done here
debug!("cluster was already free");
return;
}
// can't be reserved or defective
// can't be pointing to another cluster (we'd orphan that one)
// use free_chain to free a chain of clusters iteratively
assert!(self.is_eof(entry));
self.set_entry(cluster, FREE_ENTRY);
}
/// free `first_cluster` and all following clusters
pub fn free_chain(&mut self, mut first_cluster: u32) {
debug!("freeing chaing at {first_cluster}");
loop {
let entry = self.get_entry(first_cluster);
// assert cluster either points to another cluster of is the EOF
assert!(self.valid_entries().contains(&entry) || self.is_eof(entry));
self.set_entry(first_cluster, FREE_ENTRY);
if self.valid_entries().contains(&entry) {
first_cluster = entry;
} else {
break;
}
}
}
pub fn count_free_clusters(&self) -> u32 {
self.valid_entries()
.map(|cluster| self.get_entry(cluster))
.filter(|&entry| entry == FREE_ENTRY)
.count() as u32
}
pub fn first_free_cluster(&self) -> Option<u32> {
self.valid_entries()
.find(|&cluster| self.get_entry(cluster) == FREE_ENTRY)
}
fn is_eof(&self, entry: u32) -> bool {
entry == self.eof_entry() || self.reserved_eof_entries().contains(&entry)
}
pub fn write_back(&self, sub_slice: SubSliceMut) -> std::io::Result<()> {
self.write_to_disk(sub_slice)
Ok(Some(entry))
}
}
struct Fat12 {
pub struct Fat12 {
max: u32,
next_sectors: Box<[u16]>,
@ -225,7 +118,7 @@ impl Display for Fat12 {
writeln!(f, "Fat 12 {{")?;
for (i, &x) in self.next_sectors.iter().enumerate() {
if x != FREE_ENTRY as u16 {
if x != 0 {
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
}
}
@ -251,10 +144,7 @@ impl Fat12 {
// assume bytes.len() is multiple of 3
// TODO: fix later
// assert_eq!(bytes.len() % 3, 0);
assert_eq!(max % 2, 0);
let bytes = &bytes[..next_sectors.len() / 2 * 3];
assert_eq!(bytes.len() % 3, 0);
let (chunks, rem) = bytes.as_chunks::<3>();
@ -288,7 +178,7 @@ impl Fat12 {
}
}
impl FatOps for Fat12 {
impl Fatty for Fat12 {
fn get_entry(&self, cluster: u32) -> u32 {
let cluster = cluster as usize;
assert!(cluster < self.next_sectors.len());
@ -296,74 +186,28 @@ impl FatOps for Fat12 {
self.next_sectors[cluster] as u32
}
fn set_entry(&mut self, cluster: u32, entry: u32) {
self.next_sectors[cluster as usize] = entry as u16;
}
fn valid_entries(&self) -> RangeInclusive<u32> {
fn get_valid_clusters(&self) -> RangeInclusive<u32> {
2..=self.max
}
fn reserved_entries(&self) -> RangeInclusive<u32> {
fn get_reserved_clusters(&self) -> RangeInclusive<u32> {
(self.max as u32 + 1)..=0xFF6
}
fn defective_entry(&self) -> u32 {
fn get_defective_cluster(&self) -> u32 {
0xFF7
}
fn reserved_eof_entries(&self) -> RangeInclusive<u32> {
fn get_reserved_eof_clusters(&self) -> RangeInclusive<u32> {
0xFF8..=0xFFE
}
fn eof_entry(&self) -> u32 {
fn get_eof_cluster(&self) -> u32 {
0xFFF
}
fn write_to_disk(&self, mut sub_slice: SubSliceMut) -> std::io::Result<()> {
assert!(2 * sub_slice.len() > 3 * self.next_sectors.len());
let mut iter = self.next_sectors.chunks_exact(2);
let mut buf: [u8; 3];
for chunk in &mut iter {
// 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;
// sub_slice.write_all(&entry.to_le_bytes())?;
let first = chunk[0];
let second = chunk[1];
buf = [0; 3];
// buf[..2] |= &first.to_le_bytes();
buf[0] = first.to_le_bytes()[0];
buf[1] = first.to_le_bytes()[1] | (second << 4).to_le_bytes()[0];
buf[2] = (second << 4).to_le_bytes()[1];
sub_slice.write_all(&buf)?;
}
match iter.remainder() {
[] => {}
[x] => sub_slice.write_all(&x.to_le_bytes())?,
_ => unreachable!(),
}
Ok(())
}
}
struct Fat16 {
pub struct Fat16 {
max: u32,
next_sectors: Box<[u16]>,
@ -374,7 +218,7 @@ impl Display for Fat16 {
writeln!(f, "Fat 16 {{")?;
for (i, &x) in self.next_sectors.iter().enumerate() {
if x != FREE_ENTRY as u16 {
if x != 0 {
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
}
}
@ -418,7 +262,7 @@ impl Fat16 {
}
}
impl FatOps for Fat16 {
impl Fatty for Fat16 {
fn get_entry(&self, cluster: u32) -> u32 {
let cluster = cluster as usize;
assert!(cluster < self.next_sectors.len());
@ -426,42 +270,28 @@ impl FatOps for Fat16 {
self.next_sectors[cluster] as u32
}
fn set_entry(&mut self, cluster: u32, entry: u32) {
self.next_sectors[cluster as usize] = entry as u16;
}
fn valid_entries(&self) -> RangeInclusive<u32> {
fn get_valid_clusters(&self) -> RangeInclusive<u32> {
2..=self.max
}
fn reserved_entries(&self) -> RangeInclusive<u32> {
fn get_reserved_clusters(&self) -> RangeInclusive<u32> {
(self.max as u32 + 1)..=0xFFF6
}
fn defective_entry(&self) -> u32 {
fn get_defective_cluster(&self) -> u32 {
0xFFF7
}
fn reserved_eof_entries(&self) -> RangeInclusive<u32> {
fn get_reserved_eof_clusters(&self) -> RangeInclusive<u32> {
0xFFF8..=0xFFFE
}
fn eof_entry(&self) -> u32 {
fn get_eof_cluster(&self) -> u32 {
0xFFFF
}
fn write_to_disk(&self, mut sub_slice: SubSliceMut) -> std::io::Result<()> {
assert_eq!(2 * sub_slice.len(), self.next_sectors.len());
for &entry in self.next_sectors.iter() {
sub_slice.write_all(&entry.to_le_bytes())?;
}
Ok(())
}
}
struct Fat32 {
pub struct Fat32 {
max: u32,
next_sectors: Box<[u32]>,
@ -472,7 +302,7 @@ impl Display for Fat32 {
writeln!(f, "Fat 32 {{")?;
for (i, &x) in self.next_sectors.iter().enumerate() {
if x != FREE_ENTRY {
if x != 0 {
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
}
}
@ -516,7 +346,7 @@ impl Fat32 {
}
}
impl FatOps for Fat32 {
impl Fatty for Fat32 {
fn get_entry(&self, cluster: u32) -> u32 {
let cluster = cluster as usize;
assert!(cluster < self.next_sectors.len());
@ -524,37 +354,23 @@ impl FatOps for Fat32 {
self.next_sectors[cluster] as u32
}
fn set_entry(&mut self, cluster: u32, entry: u32) {
self.next_sectors[cluster as usize] = entry;
}
fn valid_entries(&self) -> RangeInclusive<u32> {
fn get_valid_clusters(&self) -> RangeInclusive<u32> {
2..=self.max
}
fn reserved_entries(&self) -> RangeInclusive<u32> {
fn get_reserved_clusters(&self) -> RangeInclusive<u32> {
(self.max + 1)..=0xFFFFFFF6
}
fn defective_entry(&self) -> u32 {
fn get_defective_cluster(&self) -> u32 {
0xFFFFFFF7
}
fn reserved_eof_entries(&self) -> RangeInclusive<u32> {
fn get_reserved_eof_clusters(&self) -> RangeInclusive<u32> {
0xFFFFFFF8..=0xFFFFFFFE
}
fn eof_entry(&self) -> u32 {
fn get_eof_cluster(&self) -> u32 {
0xFFFFFFFF
}
fn write_to_disk(&self, mut sub_slice: SubSliceMut) -> std::io::Result<()> {
assert_eq!(4 * sub_slice.len(), self.next_sectors.len());
for &entry in self.next_sectors.iter() {
sub_slice.write_all(&entry.to_le_bytes())?;
}
Ok(())
}
}

View file

@ -40,12 +40,4 @@ impl FsInfo {
next_free,
})
}
pub fn free_count(&self) -> u32 {
self.free_count
}
pub fn next_free(&self) -> Option<u32> {
Some(self.next_free)
}
}

View file

@ -1,94 +1,48 @@
use std::io::{Read, Write};
use std::io::Read;
use log::debug;
use crate::subslice::{SubSlice, SubSliceMut};
use crate::{FatFs, FatType};
use crate::FatFs;
use crate::subslice::SubSlice;
use crate::utils::replace;
pub struct ClusterChainReader<'a> {
fat_fs: &'a FatFs,
sub_slice: SubSlice,
sub_slice: SubSlice<'a>,
next_cluster: Option<u32>,
}
impl<'a> ClusterChainReader<'a> {
pub fn new(fat_fs: &'a FatFs, first_cluster: u32) -> Self {
pub fn new(fat_fs: &'a FatFs, first_cluster: u32) -> ClusterChainReader<'a> {
let next_cluster = fat_fs.next_cluster(first_cluster).unwrap_or(None);
let sub_slice = fat_fs.cluster_as_subslice(first_cluster);
ClusterChainReader {
fat_fs,
sub_slice,
next_cluster,
}
}
pub fn root_dir_reader(fat_fs: &'a FatFs) -> Self {
match fat_fs.fat_type() {
FatType::Fat12 | FatType::Fat16 => {
// fixed root dir, so no need to chain
// get a single SubSlice for it and next_cluster is None
let sub_slice = fat_fs.root_dir_as_subslice();
ClusterChainReader {
fat_fs,
sub_slice,
next_cluster: None,
}
}
FatType::Fat32 => {
// FAT is directory_like, so get a real chain reader
Self::new(fat_fs, fat_fs.bpb.root_cluster().unwrap())
}
}
}
fn move_to_next_cluster(&mut self) -> bool {
fn next_cluster(&mut self) -> bool {
let Some(next_cluster) = self.next_cluster else {
return false;
};
self.next_cluster = self.fat_fs.next_cluster(next_cluster).unwrap_or(None);
self.sub_slice = self.fat_fs.cluster_as_subslice(next_cluster);
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
}
pub fn skip(&mut self, n: u64) -> u64 {
let mut bytes_to_skip = n;
while bytes_to_skip > self.sub_slice.len() as u64 {
bytes_to_skip -= self.sub_slice.len() as u64;
if !self.move_to_next_cluster() {
// ran out of bytes to seek
return n - bytes_to_skip;
}
}
if bytes_to_skip != 0 {
bytes_to_skip -= self.sub_slice.skip(bytes_to_skip as usize) as u64;
}
// n should absolutely be zero here
assert_eq!(bytes_to_skip, 0);
n
}
pub fn current_offset(&self) -> u64 {
self.sub_slice.offset()
}
}
impl Read for ClusterChainReader<'_> {
impl<'a> Read for ClusterChainReader<'a> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.sub_slice.is_empty() {
if !self.move_to_next_cluster() {
if !self.next_cluster() {
return Ok(0);
}
}
@ -96,127 +50,3 @@ impl Read for ClusterChainReader<'_> {
self.sub_slice.read(buf)
}
}
pub struct ClusterChainWriter<'a> {
fat_fs: &'a mut FatFs,
sub_slice: SubSliceMut,
// next_cluster: Option<u32>,
cur_cluster: u32,
}
impl<'a> ClusterChainWriter<'a> {
pub fn new(fat_fs: &'a mut FatFs, first_cluster: u32) -> Self {
// let next_cluster = fat_fs.next_cluster(first_cluster).unwrap_or(None);
let sub_slice = fat_fs.cluster_as_subslice_mut(first_cluster);
ClusterChainWriter {
fat_fs,
sub_slice,
cur_cluster: first_cluster,
}
}
pub fn root_dir_writer(fat_fs: &'a mut FatFs) -> Self {
match fat_fs.fat_type() {
FatType::Fat12 | FatType::Fat16 => {
// fixed root dir, so no need to chain
// get a single SubSliceMut for it and next_cluster is None
let sub_slice = fat_fs.root_dir_as_subslice_mut();
ClusterChainWriter {
fat_fs,
sub_slice,
cur_cluster: 0,
}
}
FatType::Fat32 => {
// FAT is directory_like, so get a real chain writer
Self::new(fat_fs, fat_fs.bpb.root_cluster().unwrap())
}
}
}
fn move_to_next_cluster(&mut self) -> bool {
// TODO: should allocate a new cluster here!
// let Some(next_cluster) = self.next_cluster else {
// let Some(new_cluster) = self.fat_fs.alloc_cluster() else {
// // cluster allocation failed
// return false;
// };
// return false;
// };
let Some(next_cluster) = self
.fat_fs
.next_cluster(self.cur_cluster)
.map_err(|err| {
debug!("failed to get next cluster: {err}");
err
})
.unwrap_or(None)
.or_else(|| {
debug!("allocating new cluster");
self.fat_fs.alloc_cluster(Some(self.cur_cluster))
})
else {
debug!("failed to allocate next cluster");
return false;
};
debug!("next cluster: {next_cluster}");
self.fat_fs.cluster_as_subslice_mut(next_cluster);
self.cur_cluster = next_cluster;
true
}
pub fn skip(&mut self, n: u64) -> u64 {
let mut bytes_to_skip = n;
while bytes_to_skip > self.sub_slice.len() as u64 {
bytes_to_skip -= self.sub_slice.len() as u64;
if !self.move_to_next_cluster() {
// ran out of bytes to seek
return n - bytes_to_skip;
}
}
if bytes_to_skip != 0 {
bytes_to_skip -= self.sub_slice.skip(bytes_to_skip as usize) as u64;
}
// n should absolutely be zero here
assert_eq!(bytes_to_skip, 0);
n
}
pub fn current_offset(&self) -> u64 {
self.sub_slice.offset()
}
}
impl Write for ClusterChainWriter<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if self.sub_slice.is_empty() {
if !(self.move_to_next_cluster()) {
return Ok(0);
}
}
self.sub_slice.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}

View file

@ -1,13 +1,9 @@
use std::cell::RefCell;
use std::fmt::Display;
use std::io::{Read, Seek, SeekFrom, Write};
use std::rc::Rc;
use log::debug;
use crate::dir::DirIter;
use crate::fat::FatError;
use crate::iter::ClusterChainReader;
pub use crate::slice_like::SliceLike;
use crate::fat::{FatError, Fatty};
use crate::subslice::{SubSlice, SubSliceMut};
pub mod bpb;
@ -15,8 +11,7 @@ mod datetime;
pub mod dir;
pub mod fat;
pub mod fs_info;
pub mod iter;
mod slice_like;
mod iter;
mod subslice;
mod utils;
@ -27,59 +22,84 @@ pub enum FatType {
Fat32,
}
pub struct FatFs {
inner: Rc<RefCell<dyn SliceLike>>,
pub trait SliceLike {
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()>;
// 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,
next_free: Option<u32>,
free_count: u32,
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> std::io::Result<()>;
}
impl Display for FatFs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{}", self.bpb)?;
writeln!(f, "")?;
writeln!(f, "{}", self.fat)?;
impl SliceLike for &mut [u8] {
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()> {
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(())
}
}
unsafe impl Send for FatFs {}
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))?;
impl Drop for FatFs {
fn drop(&mut self) {
let fat_slice = SubSliceMut::new(
Rc::clone(&self.inner),
self.bpb.fat_offset(),
self.bpb.fat_len_bytes(),
);
self.read_exact(buf)?;
if let Err(err) = self.fat.write_back(fat_slice) {
debug!("writing FAT back to disk failed: {err}");
}
Ok(())
}
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> std::io::Result<()> {
self.seek(SeekFrom::Start(offset))?;
self.write_all(bytes)?;
Ok(())
}
}
impl FatFs {
pub fn load<S>(data: S) -> anyhow::Result<FatFs>
where
S: SliceLike + Send + 'static,
{
let data = Rc::new(RefCell::new(data));
#[allow(dead_code)]
pub struct FatFs {
inner: Rc<RefCell<dyn SliceLike>>,
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 FatFs {
pub fn load(data: Rc<RefCell<dyn SliceLike>>) -> anyhow::Result<FatFs> {
let mut bpb_bytes = [0; 512];
data.borrow_mut().read_at_offset(0, &mut bpb_bytes)?;
@ -108,177 +128,127 @@ impl FatFs {
// }
// }
// let fat_offset = bpb.fat_offset();
// let fat_size = bpb.fat_len_bytes();
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 data_size = bpb.data_len_bytes();
let bytes_per_cluster = bpb.bytes_per_cluster();
let next_free = fat.first_free_cluster();
let free_count = fat.count_free_clusters();
Ok(FatFs {
inner: data,
// fat_offset,
// fat_size,
fat_offset,
fat_size,
root_dir_offset,
root_dir_size,
data_offset,
// data_size,
data_size,
bytes_per_cluster,
bpb,
fat,
next_free,
free_count,
})
}
pub fn fat_type(&self) -> FatType {
self.fat.fat_type()
pub fn bpb(&self) -> &bpb::Bpb {
&self.bpb
}
pub fn fat(&self) -> &fat::Fat {
&self.fat
}
/// byte offset of data cluster
fn data_cluster_to_offset(&self, cluster: u32) -> u64 {
pub fn data_cluster_to_offset(&self, cluster: u32) -> u64 {
// assert!(cluster >= 2);
// assert!(self.fat.valid_entries().contains(&cluster));
assert!(self.fat().get_valid_clusters().contains(&cluster));
self.data_offset + (cluster - 2) as u64 * self.bytes_per_cluster as u64
}
pub fn free_clusters(&self) -> u32 {
// self.fat.count_free_clusters()
self.free_count
}
pub fn alloc_cluster(&mut self, prev_cluster: Option<u32>) -> Option<u32> {
let Some(new_cluster) = self.next_free else {
// no free cluster
return None;
};
debug!("next free cluster: {new_cluster}");
// set cluster as EOF
self.fat.set_next_cluster(new_cluster, None);
if let Some(prev_cluster) = prev_cluster {
self.fat.set_next_cluster(prev_cluster, Some(new_cluster));
}
// something went terribly wrong
assert_ne!(self.free_count, 0);
self.free_count -= 1;
// find next free cluster
self.next_free = self.fat.first_free_cluster();
Some(new_cluster)
}
pub fn dealloc_cluster(&mut self, cluster: u32) {
// assert cluster is actually valid
self.fat.free_cluster(cluster);
if self.next_free.is_none() || self.next_free.unwrap() > cluster {
self.next_free = Some(cluster);
}
self.free_count += 1;
}
pub fn bytes_per_sector(&self) -> u16 {
self.bpb.bytes_per_sector()
}
pub fn sectors_per_cluster(&self) -> u8 {
self.bpb.sectors_per_cluster()
}
pub fn root_cluster(&self) -> Option<u32> {
self.bpb.root_cluster()
}
/// 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)
self.fat().get_next_cluster(cluster)
}
pub fn cluster_as_subslice(&self, cluster: u32) -> SubSlice {
if cluster == 0 {
// for cluster 0 simply return empty subslice
// this makes things a bit easier, since cluster 0 is used as a marker that a file/dir
// is empty
return SubSlice::new(self.inner.clone(), 0, 0);
}
pub fn cluster_as_subslice_mut(&mut self, cluster: u32) -> SubSliceMut<'_> {
let offset = self.data_cluster_to_offset(cluster);
SubSlice::new(self.inner.clone(), offset, self.bytes_per_cluster)
SubSliceMut::new(self, offset, self.bytes_per_cluster)
}
pub fn cluster_as_subslice_mut(&self, cluster: u32) -> SubSliceMut {
if cluster == 0 {
// for cluster 0 simply return empty subslice
// this makes things a bit easier, since cluster 0 is used as a marker that a file/dir
// is empty
return SubSliceMut::new(self.inner.clone(), 0, 0);
}
pub fn cluster_as_subslice(&self, cluster: u32) -> SubSlice<'_> {
let offset = self.data_cluster_to_offset(cluster);
SubSliceMut::new(self.inner.clone(), offset, self.bytes_per_cluster)
SubSlice::new(self, offset, self.bytes_per_cluster)
}
fn root_dir_as_subslice(&self) -> SubSlice {
SubSlice::new(self.inner.clone(), self.root_dir_offset.unwrap(), self.root_dir_size)
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];
let mut inner = self.inner.borrow_mut();
inner.read_at_offset(self.data_cluster_to_offset(cluster), &mut data)?;
while let Ok(Some(next_cluster)) = self.next_cluster(cluster) {
cluster = next_cluster;
inner.read_at_offset(self.data_cluster_to_offset(cluster), &mut data)?;
}
Ok(data)
}
fn root_dir_as_subslice_mut(&self) -> SubSliceMut {
SubSliceMut::new(self.inner.clone(), self.root_dir_offset.unwrap(), self.root_dir_size)
}
fn chain_reader(&'_ self, first_cluster: u32) -> iter::ClusterChainReader<'_> {
fn chain_reader(&self, first_cluster: u32) -> impl Read {
iter::ClusterChainReader::new(self, first_cluster)
}
fn chain_writer(&'_ mut self, first_cluster: u32) -> iter::ClusterChainWriter<'_> {
iter::ClusterChainWriter::new(self, first_cluster)
pub fn root_dir_iter<'a>(&'a self) -> DirIter<Box<dyn Read + 'a>> {
// Box<dyn Iterator<Item = DirEntry> + '_>
// 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 DirIter::new(Box::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);
DirIter::new(Box::new(cluster_iter))
}
pub fn root_dir_iter<'a>(&self) -> DirIter<'_> {
let reader = ClusterChainReader::root_dir_reader(self);
pub fn dir_iter<'a>(&'a self, first_cluster: u32) -> DirIter<Box<dyn Read + 'a>> {
// TODO: return type must match root_dir_iter
// if the Box<dyn> is changed there, update here as well
DirIter::new(reader)
}
pub fn dir_iter<'a>(&self, first_cluster: u32) -> DirIter<'_> {
let cluster_iter = self.chain_reader(first_cluster);
DirIter::new(cluster_iter)
}
pub fn file_reader(&self, first_cluster: u32) -> iter::ClusterChainReader<'_> {
// TODO: needs to take file size into account
assert!(first_cluster >= 2);
self.chain_reader(first_cluster)
}
pub fn file_writer(&mut self, first_cluster: u32) -> iter::ClusterChainWriter<'_> {
// TODO: needs to take file size into account
assert!(first_cluster >= 2);
self.chain_writer(first_cluster)
DirIter::new(Box::new(cluster_iter))
}
}

View file

@ -1,58 +0,0 @@
use std::fs::File;
use std::io::{Read as _, Seek as _, SeekFrom, Write as _};
pub trait SliceLike {
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()>;
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> std::io::Result<()>;
}
impl SliceLike for &mut [u8] {
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()> {
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 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(())
}
}

View file

@ -1,18 +1,16 @@
use std::cell::RefCell;
use std::fmt::Debug;
use std::io::{Read, Write};
use std::rc::Rc;
use crate::SliceLike;
use crate::FatFs;
pub struct SubSlice {
data: Rc<RefCell<dyn SliceLike>>,
pub struct SubSliceMut<'a> {
fat_fs: &'a mut FatFs,
offset: u64,
len: usize,
}
impl Debug for SubSlice {
impl Debug for SubSliceMut<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SubSliceMut")
.field("offset", &self.offset)
@ -21,15 +19,17 @@ impl Debug for SubSlice {
}
}
impl<'a> SubSlice {
pub fn new(data: Rc<RefCell<dyn SliceLike>>, offset: u64, len: usize) -> SubSlice {
SubSlice { data, offset, len }
}
pub fn offset(&self) -> u64 {
self.offset
impl SubSliceMut<'_> {
pub fn new(fat_fs: &mut FatFs, offset: u64, len: usize) -> SubSliceMut<'_> {
SubSliceMut {
fat_fs,
offset,
len,
}
}
}
impl SubSliceMut<'_> {
pub fn len(&self) -> usize {
self.len
}
@ -37,22 +37,14 @@ impl<'a> SubSlice {
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn skip(&mut self, n: usize) -> usize {
let n = n.min(self.len());
self.offset += n as u64;
self.len -= n;
n
}
}
impl Read for SubSlice {
impl Read for SubSliceMut<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let bytes_to_read = self.len.min(buf.len());
self.data
self.fat_fs
.inner
.borrow_mut()
.read_at_offset(self.offset, &mut buf[..bytes_to_read])?;
@ -63,72 +55,12 @@ impl Read for SubSlice {
}
}
pub struct SubSliceMut {
// fat_fs: &'a FatFs,
data: Rc<RefCell<dyn SliceLike>>,
offset: u64,
len: usize,
}
impl Debug for SubSliceMut {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SubSliceMut")
.field("offset", &self.offset)
.field("len", &self.len)
.finish()
}
}
impl SubSliceMut {
pub fn new(data: Rc<RefCell<dyn SliceLike>>, offset: u64, len: usize) -> SubSliceMut {
SubSliceMut { data, offset, len }
}
}
impl<'a> SubSliceMut {
pub fn offset(&self) -> u64 {
self.offset
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn skip(&mut self, n: usize) -> usize {
let n = n.min(self.len());
self.offset += n as u64;
self.len -= n;
n
}
}
impl Read for SubSliceMut {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let bytes_to_read = self.len.min(buf.len());
self.data
.borrow_mut()
.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 Write for SubSliceMut {
impl Write for SubSliceMut<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let bytes_to_write = self.len.min(buf.len());
self.data
self.fat_fs
.inner
.borrow_mut()
.write_at_offset(self.offset, &buf[..bytes_to_write])?;
@ -142,3 +74,67 @@ impl Write for SubSliceMut {
Ok(())
}
}
pub struct SubSlice<'a> {
fat_fs: &'a FatFs,
offset: u64,
len: usize,
}
impl Debug for SubSlice<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SubSliceMut")
.field("offset", &self.offset)
.field("len", &self.len)
.finish()
}
}
impl SubSlice<'_> {
pub fn new(fat_fs: &FatFs, offset: u64, len: usize) -> SubSlice<'_> {
SubSlice {
fat_fs,
offset,
len,
}
}
pub fn fat_fs(&self) -> &FatFs {
self.fat_fs
}
pub fn fat_fs_mut(&self) -> &FatFs {
self.fat_fs
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> usize {
self.len
}
}
impl<'a> SubSlice<'a> {
/// releases the inner &FatFs, consuming self in the process
pub fn release(self) -> &'a FatFs {
self.fat_fs
}
}
impl Read for SubSlice<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let bytes_to_read = self.len.min(buf.len());
self.fat_fs
.inner
.borrow_mut()
.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)
}
}

View file

@ -3,9 +3,24 @@ pub fn load_u16_le(bytes: &[u8]) -> u16 {
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);
}
}

View file

@ -1,5 +1,9 @@
use std::cell::RefCell;
use std::rc::Rc;
use fat_bits::FatFs;
use fat_bits::dir::DirEntry;
use fat_bits::dir::{DirEntry, DirIter};
use fat_bits::fat::Fatty as _;
pub fn main() -> anyhow::Result<()> {
let args = std::env::args();
@ -18,20 +22,18 @@ pub fn main() -> anyhow::Result<()> {
// println!("{}", bpb);
let fat_fs = FatFs::load(file)?;
let fat_fs = FatFs::load(Rc::new(RefCell::new(file)))?;
// println!("{}", fat_fs.bpb());
// println!();
// println!("{}", fat_fs.fat());
println!("{}", fat_fs);
println!("{}", fat_fs.bpb());
println!();
println!("{}", fat_fs.fat());
println!();
println!(
"free clusters: {} ({} bytes)",
fat_fs.free_clusters(),
fat_fs.free_clusters() as usize
* fat_fs.bytes_per_sector() as usize
* fat_fs.sectors_per_cluster() as usize
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
);
println!();

View file

@ -5,20 +5,10 @@ edition = "2024"
[dependencies]
anyhow = "1.0.98"
bitflags = "2.9.1"
chrono = { version = "0.4.41", default-features = false, features = [
"alloc",
"clock",
"std",
] }
compact_string = "0.1.0"
chrono = { version = "0.4.41", default-features = false, features = ["alloc", "clock", "std"] }
fat-bits = { version = "0.1.0", path = "../fat-bits" }
fuser = "0.15.1"
fxhash = "0.2.1"
libc = "0.2.174"
log = "0.4.27"
rand = { version = "0.9.2", default-features = false, features = [
"os_rng",
"small_rng",
] }
rand = { version = "0.9.2", default-features = false, features = ["os_rng", "small_rng"] }
thiserror = "2.0.12"

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,10 @@
use std::cell::{LazyCell, RefCell};
use std::rc::Rc;
use std::time::SystemTime;
use chrono::{NaiveDateTime, NaiveTime};
use fat_bits::FatFs;
use fat_bits::dir::{DirEntry, DirIter};
use fat_bits::iter::{ClusterChainReader, ClusterChainWriter};
use fuser::FileAttr;
use libc::{EISDIR, ENOENT, ENOTDIR};
use log::debug;
use rand::{Rng, SeedableRng as _};
thread_local! {
@ -23,10 +19,13 @@ thread_local! {
static RNG: LazyCell<RefCell<rand::rngs::SmallRng>> = LazyCell::new(|| RefCell::new(rand::rngs::SmallRng::from_os_rng()));
}
fn get_random<T>() -> T
where
rand::distr::StandardUniform: rand::distr::Distribution<T>,
{
fn get_random_u32() -> u32 {
// RNG.with(|x| unsafe {
// let rng = &mut (*x.get());
// rng.random::<u32>()
// })
RNG.with(|rng| rng.borrow_mut().random())
}
@ -45,81 +44,38 @@ impl From<Kind> for fuser::FileType {
}
}
pub const ROOT_INO: u64 = 1;
pub type InodeRef = Rc<RefCell<Inode>>;
// pub type InodeWeak = Weak<RefCell<Inode>>;
const ROOT_INO: u64 = 1;
#[derive(Debug)]
#[allow(dead_code)]
pub struct Inode {
ino: u64,
// FUSE uses a u64 for generation, but the Linux kernel only handles u32s anyway, truncating
// the high bits, so using more is pretty pointless and possibly even detrimental
generation: u32,
ref_count: u64,
uid: u32,
gid: u32,
path: Rc<str>,
parent: Option<InodeRef>,
size: u64,
block_size: u32,
kind: Kind,
read_only: bool,
dirty: bool,
// these are the fields that have to get written back to the DirEntry on write_back
size: u64,
atime: SystemTime,
mtime: SystemTime,
// ctime: SystemTime,
crtime: SystemTime,
uid: u32,
gid: u32,
first_cluster: u32,
}
impl Drop for Inode {
fn drop(&mut self) {
// since we don't have a handle on the FatFs we can't do the write-back here
assert!(
!self.dirty,
"inode {} is dirty, but was not written back to FS before being destroyed",
self.ino()
);
}
}
#[allow(dead_code)]
impl Inode {
fn new_generation() -> u32 {
let rand: u16 = get_random();
let secs = SystemTime::UNIX_EPOCH
.elapsed()
.map(|dur| dur.as_secs() as u16)
.unwrap_or(0);
((secs as u32) << 16) | rand as u32
}
pub fn new(
fat_fs: &FatFs,
dir_entry: &DirEntry,
ino: u64,
uid: u32,
gid: u32,
path: impl Into<Rc<str>>,
parent: InodeRef,
) -> Inode {
pub fn new(fat_fs: &FatFs, dir_entry: DirEntry, uid: u32, gid: u32) -> Inode {
assert!(dir_entry.is_file() || dir_entry.is_dir());
let generation = Self::new_generation();
let generation = get_random_u32();
let kind = if dir_entry.is_dir() {
Kind::Dir
@ -139,138 +95,22 @@ impl Inode {
let mtime = datetime_to_system(dir_entry.write_time());
let crtime = datetime_to_system(dir_entry.create_time());
let path = path.into();
debug!(
"creating new inode: ino: {} name: {} path: {}",
ino,
dir_entry.name_string(),
path
);
Inode {
ino,
ino: dir_entry.first_cluster() as u64,
generation,
ref_count: 0,
parent: Some(parent),
size: dir_entry.file_size() as u64,
block_size: fat_fs.bytes_per_sector() as u32,
block_size: fat_fs.bpb().bytes_per_sector() as u32,
kind,
read_only: dir_entry.is_readonly(),
dirty: false,
atime,
mtime,
crtime,
uid,
gid,
first_cluster: dir_entry.first_cluster(),
path,
}
}
pub fn root_inode(fat_fs: &FatFs, uid: u32, gid: u32) -> Inode {
let root_cluster = fat_fs.root_cluster().unwrap_or(0);
Inode {
ino: ROOT_INO,
generation: 0, // root cluster always has constant generation of 0
ref_count: 0,
parent: None, // parent is self
size: 0,
block_size: fat_fs.bytes_per_sector() as u32,
kind: Kind::Dir,
read_only: false,
dirty: false,
atime: SystemTime::UNIX_EPOCH,
mtime: SystemTime::UNIX_EPOCH,
crtime: SystemTime::UNIX_EPOCH,
uid,
gid,
first_cluster: root_cluster,
path: "/".into(),
}
}
pub fn ino(&self) -> u64 {
self.ino
}
pub fn generation(&self) -> u32 {
self.generation
}
pub fn ref_count(&self) -> u64 {
self.ref_count
}
pub fn inc_ref_count(&mut self) {
debug!(
"increasing ref_count of ino {} by 1 (new ref_count: {})",
self.ino(),
self.ref_count() + 1
);
self.ref_count += 1;
}
pub fn dec_ref_count(&mut self, n: u64) -> u64 {
debug!(
"decreasing ref_count of ino {} by {} (new ref_count: {})",
self.ino(),
n,
self.ref_count().saturating_sub(n),
);
if self.ref_count < n {
debug!(
"inode {}: tried to decrement refcount by {}, but is only {}",
self.ino(),
n,
self.ref_count
);
}
self.ref_count = self.ref_count.saturating_sub(n);
self.ref_count
}
pub fn parent(&self) -> Option<&InodeRef> {
self.parent.as_ref()
}
pub fn size(&self) -> u64 {
self.size
}
pub fn kind(&self) -> Kind {
self.kind
}
pub fn is_file(&self) -> bool {
self.kind == Kind::File
}
pub fn is_dir(&self) -> bool {
self.kind == Kind::Dir
}
pub fn is_read_only(&self) -> bool {
self.read_only
}
pub fn first_cluster(&self) -> u32 {
self.first_cluster
}
pub fn path(&self) -> Rc<str> {
Rc::clone(&self.path)
}
pub fn is_root(&self) -> bool {
self.ino == ROOT_INO
}
pub fn file_attr(&self) -> FileAttr {
let perm = if self.read_only { 0o555 } else { 0o777 };
@ -293,123 +133,21 @@ impl Inode {
}
}
pub fn dir_iter<'a>(&'a self, fat_fs: &'a FatFs) -> Result<DirIter<'a>, i32> {
if self.kind != Kind::Dir {
return Err(ENOTDIR);
}
pub fn dir_iter(&self, fat_fs: &FatFs) -> anyhow::Result<impl Iterator<Item = DirEntry>> {
anyhow::ensure!(self.kind == Kind::Dir, "cannot dir_iter on a file");
if self.is_root() {
// TODO: the boxing here is not particularly pretty, but neccessary, since the DirIter for
// the root holds a
if self.ino == ROOT_INO {
// root dir
return Ok(fat_fs.root_dir_iter());
}
Ok(fat_fs.dir_iter(self.first_cluster))
}
let chain_reader = fat_fs.chain_reader(self.first_cluster);
pub fn find_child_by_name(&self, fat_fs: &FatFs, name: &str) -> Result<DirEntry, i32> {
self.dir_iter(fat_fs)
.and_then(|mut dir_iter| dir_iter.find_by_name(name).ok_or(ENOENT))
}
pub fn file_reader<'a>(&'a self, fat_fs: &'a FatFs) -> Result<ClusterChainReader<'a>, i32> {
if self.is_dir() {
return Err(EISDIR);
}
Ok(fat_fs.file_reader(self.first_cluster()))
}
pub fn file_writer<'a>(&'a self, fat_fs: &'a mut FatFs) -> Result<ClusterChainWriter<'a>, i32> {
if self.is_dir() {
return Err(EISDIR);
}
Ok(fat_fs.file_writer(self.first_cluster()))
}
pub fn update_size(&mut self, new_size: u64) {
debug!("updating size to {new_size}");
if new_size == self.size {
return;
}
self.size = new_size;
self.dirty = true;
}
pub fn update_atime(&mut self, atime: SystemTime) {
if self.atime == atime {
return;
}
self.atime = atime;
self.dirty = true;
}
pub fn update_mtime(&mut self, mtime: SystemTime) {
if self.mtime == mtime {
return;
}
self.mtime = mtime;
self.dirty = true;
}
pub fn write_back(&mut self, fat_fs: &FatFs) -> anyhow::Result<()> {
if !self.dirty {
return Ok(());
}
if self.is_root() {
// root dir has no attributes
self.dirty = false;
return Ok(());
}
let Some(parent_inode) = self.parent() else {
anyhow::bail!("parent inode of {} does not exist", self.ino);
};
let parent_inode = parent_inode.borrow();
// since we just wrote to the file with this inode, first cluster should not be zero
let Some(mut dir_entry) = parent_inode
.dir_iter(fat_fs)
.unwrap()
.find(|dir_entry| dir_entry.first_cluster() == self.first_cluster())
else {
anyhow::bail!("could not find dir_entry corresponding to self in parent inode");
};
drop(parent_inode);
// update stats on DirEntry
assert!(self.size <= u32::MAX as u64);
dir_entry.update_file_size(self.size as u32);
dir_entry
.update_last_access_date(self.atime)
.map_err(|err| {
anyhow::anyhow!("failed to update atime for inode {}: {err}", self.ino)
})?;
dir_entry.update_write_time(self.mtime).map_err(|err| {
anyhow::anyhow!("failed to update mtime for inode {}: {err}", self.ino)
})?;
// update cr_time as well?
// write DirEntry back to device
dir_entry.write_back(fat_fs).map_err(|err| {
anyhow::anyhow!("failed to write back dir_entry for inode {}: {err}", self.ino)
})?;
self.dirty = false;
Ok(())
// TODO: get rid of this Box if the boxing is removed from root_dir_iter
Ok(DirIter::new(Box::new(chain_reader)))
}
}

View file

@ -5,12 +5,9 @@ use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
use fat_bits::dir::DirEntry;
use fat_bits::{FatFs, SliceLike};
use fxhash::FxHashMap;
use log::{debug, error};
use crate::inode::{Inode, InodeRef};
use crate::inode::Inode;
#[allow(dead_code)]
pub struct FatFuse {
@ -19,279 +16,32 @@ pub struct FatFuse {
uid: u32,
gid: u32,
next_ino: u64,
next_fh: u64,
next_fd: u32,
inode_table: BTreeMap<u64, InodeRef>,
ino_by_first_cluster: BTreeMap<u32, u64>,
ino_by_fh: BTreeMap<u64, u64>,
ino_by_path: FxHashMap<Rc<str>, u64>,
inode_table: BTreeMap<u64, Inode>,
}
/// SAFETY
///
/// do NOT leak Rc<str> from this type
unsafe impl Send for FatFuse {}
impl FatFuse {
pub fn new<S>(data: S) -> anyhow::Result<FatFuse>
where
S: SliceLike + Send + 'static,
{
pub fn new(data: Rc<RefCell<dyn SliceLike>>) -> anyhow::Result<FatFuse> {
let uid = unsafe { libc::getuid() };
let gid = unsafe { libc::getgid() };
let fat_fs = FatFs::load(data)?;
let mut fat_fuse = FatFuse {
Ok(FatFuse {
fat_fs,
uid,
gid,
next_ino: 2, // 0 is reserved and 1 is root
next_fh: 0,
next_fd: 0,
inode_table: BTreeMap::new(),
ino_by_first_cluster: BTreeMap::new(),
ino_by_fh: BTreeMap::new(),
ino_by_path: FxHashMap::default(),
};
// TODO: build and insert root dir inode
let root_inode = Inode::root_inode(&fat_fuse.fat_fs, uid, gid);
fat_fuse.insert_inode(root_inode);
Ok(fat_fuse)
})
}
fn next_ino(&mut self) -> u64 {
let ino = self.next_ino;
assert!(!self.inode_table.contains_key(&ino));
self.next_ino += 1;
ino
}
fn next_fh(&mut self) -> u64 {
let fh = self.next_fh;
assert!(!self.ino_by_fh.contains_key(&fh));
self.next_fh += 1;
fh
}
fn insert_inode(&mut self, inode: Inode) -> InodeRef {
let ino = inode.ino();
let generation = inode.generation();
let first_cluster = inode.first_cluster();
// let old_inode = self.inode_table.insert(ino, inode);
let inode = Rc::new(RefCell::new(inode));
let entry = self.inode_table.entry(ino);
let (new_inode, old_inode) = match entry {
std::collections::btree_map::Entry::Vacant(vacant_entry) => {
let new_inode = vacant_entry.insert(inode);
(Rc::clone(new_inode), None)
}
std::collections::btree_map::Entry::Occupied(occupied_entry) => {
let entry_ref = occupied_entry.into_mut();
let old_inode = std::mem::replace(entry_ref, inode);
(Rc::clone(entry_ref), Some(old_inode))
}
};
debug!(
"inserted new inode with ino {} and generation {} (first cluster: {})",
ino, generation, first_cluster
);
if let Some(old_inode) = old_inode {
let old_inode = old_inode.borrow();
debug!("ejected inode {} {}", old_inode.ino(), old_inode.generation());
}
if first_cluster != 0 {
if let Some(old_ino) = self.ino_by_first_cluster.insert(first_cluster, ino) {
debug!("ejected old {} -> {} cluster to ino mapping", first_cluster, old_ino);
}
}
let path = new_inode.borrow().path();
if let Some(old_ino) = self.ino_by_path.insert(Rc::clone(&path), ino) {
debug!("ejected old {} -> {} path to ino mapping", path, old_ino);
}
new_inode
}
fn drop_inode(&mut self, inode: InodeRef) {
let inode = inode.borrow();
let ino = inode.ino();
debug!("dropping inode {}", ino);
if self.inode_table.remove(&ino).is_none() {
error!("tried to drop inode with ino {}, but was not in table", ino);
return;
};
let first_cluster = inode.first_cluster();
if first_cluster != 0 {
let entry = self.ino_by_first_cluster.entry(first_cluster);
match entry {
std::collections::btree_map::Entry::Vacant(_) => debug!(
"removed inode with ino {} from table, but it's first cluster did not point to any ino",
ino
),
std::collections::btree_map::Entry::Occupied(occupied_entry) => {
let found_ino = *occupied_entry.get();
if found_ino == ino {
// matches our inode, remove it
occupied_entry.remove();
} else {
// does not match removed inode, leave it as is
debug!(
"removed inode with ino {} from table, but its first cluster pointed to ino {} instead",
ino, found_ino
);
}
}
}
}
{
let entry = self.ino_by_path.entry(inode.path());
match entry {
std::collections::hash_map::Entry::Vacant(_) => debug!(
"removed inode with ino {} from table, but it's path did not point to any ino",
ino
),
std::collections::hash_map::Entry::Occupied(occupied_entry) => {
let found_ino = *occupied_entry.get();
if found_ino == ino {
// matches our inode, remove it
occupied_entry.remove();
} else {
// does not match removed inode, leave it as is
debug!(
"removed inode with ino {} from table, but its path pointed to ino {} instead",
ino, found_ino
);
}
}
}
}
}
fn get_inode(&self, ino: u64) -> Option<&InodeRef> {
fn get_inode(&self, ino: u64) -> Option<&Inode> {
self.inode_table.get(&ino)
}
fn get_or_make_inode(&mut self, dir_entry: &DirEntry, parent: &Inode) -> InodeRef {
// let parent = parent.borrow();
// try to find inode by first cluster first
if dir_entry.first_cluster() != 0
&& let Some(inode) = self.get_inode_by_first_cluster(dir_entry.first_cluster())
{
return inode;
}
// try to find inode by path
// mostly for empty files/directories which have a first cluster of 0
let path = {
let mut path = parent.path().as_ref().to_owned();
if parent.ino() != inode::ROOT_INO {
// root inode already has trailing slash
path.push('/');
}
path += &dir_entry.name_string();
path
};
if let Some(inode) = self.get_inode_by_path(&path) {
return inode;
}
// no inode found, make a new one
let ino = self.next_ino();
let Some(parent_inode) = self.get_inode(parent.ino()).cloned() else {
// TODO: what do we do here? should not happen
panic!("parent_ino {} does not lead to inode", parent.ino());
};
let inode =
Inode::new(&self.fat_fs, dir_entry, ino, self.uid, self.gid, path, parent_inode);
self.insert_inode(inode)
}
pub fn get_inode_by_first_cluster(&self, first_cluster: u32) -> Option<InodeRef> {
if first_cluster == 0 {
debug!("trying to get inode by first cluster 0");
return None;
}
let ino = self.ino_by_first_cluster.get(&first_cluster)?;
if let Some(inode) = self.inode_table.get(ino) {
Some(Rc::clone(inode))
} else {
debug!(
"first cluster {} is mapped to ino {}, but inode is not in table",
first_cluster, ino
);
None
}
}
pub fn get_inode_by_fh(&self, fh: u64) -> Option<&InodeRef> {
let ino = *self.ino_by_fh.get(&fh)?;
if let Some(inode) = self.get_inode(ino) {
Some(inode)
} else {
debug!("fh {} is mapped to ino {}, but inode is not in table", fh, ino);
None
}
}
pub fn get_inode_by_path(&self, path: &str) -> Option<InodeRef> {
let ino = *self.ino_by_path.get(path)?;
if let Some(inode) = self.get_inode(ino).cloned() {
Some(inode)
} else {
debug!("path {} is mapped to ino {}, but inode is not in table", path, ino);
None
}
fn get_inode_mut(&mut self, ino: u64) -> Option<&mut Inode> {
self.inode_table.get_mut(&ino)
}
}

View file

@ -1,11 +0,0 @@
[package]
name = "fat-mount"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0.98"
ctrlc = "3.4.7"
env_logger = "0.11.8"
fat-fuse = { version = "0.1.0", path = "../fat-fuse" }
fuser = "0.15.1"

View file

@ -1,41 +0,0 @@
use std::fs::OpenOptions;
use std::sync::mpsc::channel;
use fat_fuse::FatFuse;
use fuser::MountOption;
fn main() -> anyhow::Result<()> {
env_logger::init();
let mut args = std::env::args();
let _prog_name = args.next().unwrap();
let path = args.next().ok_or(anyhow::anyhow!("missing fs path"))?;
let mountpoint = args.next().ok_or(anyhow::anyhow!("missing mount point"))?;
// let file = File::open(path)?;
let file = OpenOptions::new().read(true).write(true).open(path)?;
let fat_fuse = FatFuse::new(file)?;
let options = vec![
// MountOption::RO,
MountOption::FSName("fat-fuse".to_owned()),
MountOption::AutoUnmount,
];
let (tx, rx) = channel();
ctrlc::set_handler(move || {
tx.send(()).unwrap();
})
.unwrap();
let handle = fuser::spawn_mount2(fat_fuse, mountpoint, &options)?;
rx.recv().unwrap();
handle.join();
Ok(())
}