From 9cb6ee64461cf6e1d889f041a077465204adc01d Mon Sep 17 00:00:00 2001 From: Moritz Gmeiner Date: Sun, 3 Aug 2025 01:32:48 +0200 Subject: [PATCH] tried to implement file cluster extension (bug it's still bugged) --- fat-bits/Cargo.toml | 1 + fat-bits/src/dir.rs | 3 +- fat-bits/src/fat.rs | 170 +++++++++++++++++++++++++++++++++--------- fat-bits/src/iter.rs | 47 +++++++++--- fat-bits/src/lib.rs | 54 ++++++++------ fat-fuse/Cargo.toml | 11 ++- fat-fuse/src/fuse.rs | 21 +++--- fat-fuse/src/inode.rs | 23 +++++- 8 files changed, 253 insertions(+), 77 deletions(-) diff --git a/fat-bits/Cargo.toml b/fat-bits/Cargo.toml index 6c53e1f..632d910 100644 --- a/fat-bits/Cargo.toml +++ b/fat-bits/Cargo.toml @@ -8,6 +8,7 @@ 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" diff --git a/fat-bits/src/dir.rs b/fat-bits/src/dir.rs index e7d5965..8b8ab6c 100644 --- a/fat-bits/src/dir.rs +++ b/fat-bits/src/dir.rs @@ -213,7 +213,8 @@ impl DirEntry { buf[28..].copy_from_slice(&self.file_size.to_le_bytes()); - eprintln!("writing new dir entry: {:?}", buf); + debug!("self: {self:?}"); + debug!("writing new dir entry: {:?}", buf); writer.write_all(&buf)?; diff --git a/fat-bits/src/fat.rs b/fat-bits/src/fat.rs index 9391363..2a33588 100644 --- a/fat-bits/src/fat.rs +++ b/fat-bits/src/fat.rs @@ -4,10 +4,13 @@ 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 { #[error("can't get next cluster of free cluster")] @@ -21,7 +24,7 @@ pub enum FatError { } #[enum_dispatch] -pub trait FatOps { +trait FatOps { // get the next cluster // assumes the cluster is valid, i.e. allocated fn get_entry(&self, cluster: u32) -> u32; @@ -33,23 +36,12 @@ pub trait FatOps { fn reserved_eof_entries(&self) -> RangeInclusive; fn eof_entry(&self) -> u32; - fn count_free_clusters(&self) -> u32 { - self.valid_entries() - .map(|cluster| self.get_entry(cluster)) - .filter(|&entry| entry == 0) - .count() as u32 - } - - fn first_free_cluster(&self) -> Option { - self.valid_entries() - .map(|cluster| self.get_entry(cluster)) - .find(|&entry| entry == 0) - } - fn write_to_disk(&self, sub_slice: SubSliceMut) -> std::io::Result<()>; } #[enum_dispatch(FatOps)] +// others should never touch the inner FatNs directly, but instead go through the Fat type +#[allow(private_interfaces)] pub enum Fat { Fat12(Fat12), Fat16(Fat16), @@ -75,8 +67,16 @@ 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, FatError> { - if cluster == 0x000 { + if cluster == FREE_ENTRY { // can't get next cluster for free cluster return Err(FatError::FreeCluster); } @@ -104,21 +104,117 @@ impl Fat { let entry = self.get_entry(cluster); - // interpret second reserved block as EOF here - if entry == self.eof_entry() || self.reserved_eof_entries().contains(&entry) { - return Ok(None); + 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) { + 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); } - // entry should be in the valid cluster range here; otherwise something went wrong - if !self.valid_entries().contains(&entry) { - return Err(FatError::InvalidEntry(entry)); + if let Some(next_cluster) = next_cluster { + assert!(self.valid_entries().contains(&next_cluster)); } - Ok(Some(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 { + 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) } } -pub struct Fat12 { +struct Fat12 { max: u32, next_sectors: Box<[u16]>, @@ -129,7 +225,7 @@ impl Display for Fat12 { writeln!(f, "Fat 12 {{")?; for (i, &x) in self.next_sectors.iter().enumerate() { - if x != 0 { + if x != FREE_ENTRY as u16 { writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?; } } @@ -155,7 +251,10 @@ impl Fat12 { // assume bytes.len() is multiple of 3 // TODO: fix later - assert_eq!(bytes.len() % 3, 0); + // assert_eq!(bytes.len() % 3, 0); + assert_eq!(max % 2, 0); + + let bytes = &bytes[..next_sectors.len() / 2 * 3]; let (chunks, rem) = bytes.as_chunks::<3>(); @@ -222,11 +321,9 @@ impl FatOps for Fat12 { } fn write_to_disk(&self, mut sub_slice: SubSliceMut) -> std::io::Result<()> { - // TODO: currently assumed FAT has even number of entries + assert!(2 * sub_slice.len() > 3 * self.next_sectors.len()); - assert_eq!(3 * sub_slice.len(), self.next_sectors.len()); - - let mut iter = self.next_sectors.chunks_exact(3); + let mut iter = self.next_sectors.chunks_exact(2); let mut buf: [u8; 3]; @@ -255,13 +352,18 @@ impl FatOps for Fat12 { sub_slice.write_all(&buf)?; } - assert_eq!(iter.remainder().len(), 0); + match iter.remainder() { + [] => {} + [x] => sub_slice.write_all(&x.to_le_bytes())?, + + _ => unreachable!(), + } Ok(()) } } -pub struct Fat16 { +struct Fat16 { max: u32, next_sectors: Box<[u16]>, @@ -272,7 +374,7 @@ impl Display for Fat16 { writeln!(f, "Fat 16 {{")?; for (i, &x) in self.next_sectors.iter().enumerate() { - if x != 0 { + if x != FREE_ENTRY as u16 { writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?; } } @@ -359,7 +461,7 @@ impl FatOps for Fat16 { } } -pub struct Fat32 { +struct Fat32 { max: u32, next_sectors: Box<[u32]>, @@ -370,7 +472,7 @@ impl Display for Fat32 { writeln!(f, "Fat 32 {{")?; for (i, &x) in self.next_sectors.iter().enumerate() { - if x != 0 { + if x != FREE_ENTRY { writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?; } } diff --git a/fat-bits/src/iter.rs b/fat-bits/src/iter.rs index aaf7eaf..6c2a9d9 100644 --- a/fat-bits/src/iter.rs +++ b/fat-bits/src/iter.rs @@ -1,5 +1,7 @@ use std::io::{Read, Write}; +use log::debug; + use crate::subslice::{SubSlice, SubSliceMut}; use crate::{FatFs, FatType}; @@ -96,27 +98,28 @@ impl Read for ClusterChainReader<'_> { } pub struct ClusterChainWriter<'a> { - fat_fs: &'a FatFs, + fat_fs: &'a mut FatFs, sub_slice: SubSliceMut, - next_cluster: Option, + // next_cluster: Option, + cur_cluster: u32, } impl<'a> ClusterChainWriter<'a> { - pub fn new(fat_fs: &'a FatFs, first_cluster: u32) -> Self { - let next_cluster = fat_fs.next_cluster(first_cluster).unwrap_or(None); + 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, - next_cluster, + cur_cluster: first_cluster, } } - pub fn root_dir_writer(fat_fs: &'a FatFs) -> Self { + 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 @@ -127,7 +130,7 @@ impl<'a> ClusterChainWriter<'a> { ClusterChainWriter { fat_fs, sub_slice, - next_cluster: None, + cur_cluster: 0, } } FatType::Fat32 => { @@ -140,12 +143,38 @@ impl<'a> ClusterChainWriter<'a> { 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(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; }; - self.next_cluster = self.fat_fs.next_cluster(next_cluster).unwrap_or(None); + debug!("next cluster: {next_cluster}"); + self.fat_fs.cluster_as_subslice_mut(next_cluster); + self.cur_cluster = next_cluster; true } diff --git a/fat-bits/src/lib.rs b/fat-bits/src/lib.rs index 139bb23..88092f7 100644 --- a/fat-bits/src/lib.rs +++ b/fat-bits/src/lib.rs @@ -2,8 +2,10 @@ use std::cell::RefCell; use std::fmt::Display; use std::rc::Rc; +use log::debug; + use crate::dir::DirIter; -use crate::fat::{FatError, FatOps}; +use crate::fat::FatError; use crate::iter::ClusterChainReader; pub use crate::slice_like::SliceLike; use crate::subslice::{SubSlice, SubSliceMut}; @@ -57,6 +59,20 @@ impl Display for FatFs { unsafe impl Send for FatFs {} +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(), + ); + + if let Err(err) = self.fat.write_back(fat_slice) { + debug!("writing FAT back to disk failed: {err}"); + } + } +} + impl FatFs { pub fn load(data: S) -> anyhow::Result where @@ -123,18 +139,14 @@ impl FatFs { } pub fn fat_type(&self) -> FatType { - match &self.fat { - fat::Fat::Fat12(_) => FatType::Fat12, - fat::Fat::Fat16(_) => FatType::Fat16, - fat::Fat::Fat32(_) => FatType::Fat32, - } + self.fat.fat_type() } /// byte offset of data cluster fn data_cluster_to_offset(&self, cluster: u32) -> u64 { // assert!(cluster >= 2); - assert!(self.fat.valid_entries().contains(&cluster)); + // assert!(self.fat.valid_entries().contains(&cluster)); self.data_offset + (cluster - 2) as u64 * self.bytes_per_cluster as u64 } @@ -144,14 +156,20 @@ impl FatFs { self.free_count } - pub fn alloc_cluster(&mut self) -> Option { - let Some(cluster) = self.next_free else { + pub fn alloc_cluster(&mut self, prev_cluster: Option) -> Option { + let Some(new_cluster) = self.next_free else { // no free cluster return None; }; - // set cluster as taken - self.fat.set_entry(cluster, self.fat.eof_entry()); + 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); @@ -161,18 +179,12 @@ impl FatFs { // find next free cluster self.next_free = self.fat.first_free_cluster(); - Some(cluster) + Some(new_cluster) } pub fn dealloc_cluster(&mut self, cluster: u32) { // assert cluster is actually valid - assert!( - self.fat - .valid_entries() - .contains(&self.fat.get_entry(cluster)) - ); - - self.fat.set_entry(cluster, 0); + self.fat.free_cluster(cluster); if self.next_free.is_none() || self.next_free.unwrap() > cluster { self.next_free = Some(cluster); @@ -240,7 +252,7 @@ impl FatFs { iter::ClusterChainReader::new(self, first_cluster) } - fn chain_writer(&'_ self, first_cluster: u32) -> iter::ClusterChainWriter<'_> { + fn chain_writer(&'_ mut self, first_cluster: u32) -> iter::ClusterChainWriter<'_> { iter::ClusterChainWriter::new(self, first_cluster) } @@ -263,7 +275,7 @@ impl FatFs { self.chain_reader(first_cluster) } - pub fn file_writer(&self, first_cluster: u32) -> iter::ClusterChainWriter<'_> { + pub fn file_writer(&mut self, first_cluster: u32) -> iter::ClusterChainWriter<'_> { // TODO: needs to take file size into account assert!(first_cluster >= 2); diff --git a/fat-fuse/Cargo.toml b/fat-fuse/Cargo.toml index ad86f43..9cf4698 100644 --- a/fat-fuse/Cargo.toml +++ b/fat-fuse/Cargo.toml @@ -6,12 +6,19 @@ edition = "2024" [dependencies] anyhow = "1.0.98" bitflags = "2.9.1" -chrono = { version = "0.4.41", default-features = false, features = ["alloc", "clock", "std"] } +chrono = { version = "0.4.41", default-features = false, features = [ + "alloc", + "clock", + "std", +] } compact_string = "0.1.0" 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" diff --git a/fat-fuse/src/fuse.rs b/fat-fuse/src/fuse.rs index feb4dad..1df0e03 100644 --- a/fat-fuse/src/fuse.rs +++ b/fat-fuse/src/fuse.rs @@ -117,11 +117,11 @@ impl Filesystem for FatFuse { let attr = inode.file_attr(); let generation = inode.generation(); + debug!("attr: {attr:?}"); + reply.entry(&TTL, &attr, generation as u64); inode.inc_ref_count(); - - // TODO: update access time } fn forget(&mut self, _req: &fuser::Request<'_>, ino: u64, nlookup: u64) { @@ -165,6 +165,14 @@ impl Filesystem for FatFuse { let attr = inode.file_attr(); inode.update_atime(SystemTime::now()); + if let Err(err) = inode.write_back(&self.fat_fs) { + debug!("error while writing back inode: {err}"); + + reply.error(EIO); + return; + } + + debug!("attr: {attr:?}"); reply.attr(&TTL, &attr); } @@ -515,7 +523,7 @@ impl Filesystem for FatFuse { let offset = offset as u64; - let Some(inode) = self.get_inode_by_fh(fh) else { + let Some(inode) = self.get_inode_by_fh(fh).cloned() else { debug!("no inode associated with fh {fh} (given ino: {ino}"); reply.error(EBADF); @@ -544,7 +552,7 @@ impl Filesystem for FatFuse { return; } - let mut writer = match inode.file_writer(&self.fat_fs) { + let mut writer = match inode.file_writer(&mut self.fat_fs) { Ok(writer) => writer, Err(err) => { reply.error(err); @@ -552,11 +560,6 @@ impl Filesystem for FatFuse { } }; - // if writer.skip(offset) != offset { - // // writer is at EOF, bail - - // } - let cur_offset = writer.skip(offset); // can't seek more than we requested diff --git a/fat-fuse/src/inode.rs b/fat-fuse/src/inode.rs index fa059d8..bff9ef1 100644 --- a/fat-fuse/src/inode.rs +++ b/fat-fuse/src/inode.rs @@ -320,7 +320,7 @@ impl Inode { Ok(fat_fs.file_reader(self.first_cluster())) } - pub fn file_writer<'a>(&'a self, fat_fs: &'a FatFs) -> Result, i32> { + pub fn file_writer<'a>(&'a self, fat_fs: &'a mut FatFs) -> Result, i32> { if self.is_dir() { return Err(EISDIR); } @@ -329,16 +329,30 @@ impl Inode { } 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; } @@ -348,6 +362,13 @@ impl Inode { 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); };