tried to implement file cluster extension (bug it's still bugged)

This commit is contained in:
Moritz Gmeiner 2025-08-03 01:32:48 +02:00
commit 9cb6ee6446
8 changed files with 253 additions and 77 deletions

View file

@ -8,6 +8,7 @@ anyhow = "1.0.98"
bitflags = "2.9.1" bitflags = "2.9.1"
chrono = { version = "0.4.41", default-features = false, features = [ chrono = { version = "0.4.41", default-features = false, features = [
"alloc", "alloc",
"clock",
"std", "std",
] } ] }
compact_str = "0.9.0" compact_str = "0.9.0"

View file

@ -213,7 +213,8 @@ impl DirEntry {
buf[28..].copy_from_slice(&self.file_size.to_le_bytes()); 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)?; writer.write_all(&buf)?;

View file

@ -4,10 +4,13 @@ use std::mem::MaybeUninit;
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use log::debug;
use crate::FatType; use crate::FatType;
use crate::subslice::SubSliceMut; use crate::subslice::SubSliceMut;
const FREE_ENTRY: u32 = 0;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum FatError { pub enum FatError {
#[error("can't get next cluster of free cluster")] #[error("can't get next cluster of free cluster")]
@ -21,7 +24,7 @@ pub enum FatError {
} }
#[enum_dispatch] #[enum_dispatch]
pub trait FatOps { trait FatOps {
// get the next cluster // get the next cluster
// assumes the cluster is valid, i.e. allocated // assumes the cluster is valid, i.e. allocated
fn get_entry(&self, cluster: u32) -> u32; fn get_entry(&self, cluster: u32) -> u32;
@ -33,23 +36,12 @@ pub trait FatOps {
fn reserved_eof_entries(&self) -> RangeInclusive<u32>; fn reserved_eof_entries(&self) -> RangeInclusive<u32>;
fn eof_entry(&self) -> u32; 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<u32> {
self.valid_entries()
.map(|cluster| self.get_entry(cluster))
.find(|&entry| entry == 0)
}
fn write_to_disk(&self, sub_slice: SubSliceMut) -> std::io::Result<()>; fn write_to_disk(&self, sub_slice: SubSliceMut) -> std::io::Result<()>;
} }
#[enum_dispatch(FatOps)] #[enum_dispatch(FatOps)]
// others should never touch the inner FatNs directly, but instead go through the Fat type
#[allow(private_interfaces)]
pub enum Fat { pub enum Fat {
Fat12(Fat12), Fat12(Fat12),
Fat16(Fat16), 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<Option<u32>, FatError> { pub fn get_next_cluster(&self, cluster: u32) -> Result<Option<u32>, FatError> {
if cluster == 0x000 { if cluster == FREE_ENTRY {
// can't get next cluster for free cluster // can't get next cluster for free cluster
return Err(FatError::FreeCluster); return Err(FatError::FreeCluster);
} }
@ -104,21 +104,117 @@ impl Fat {
let entry = self.get_entry(cluster); let entry = self.get_entry(cluster);
// interpret second reserved block as EOF here if entry == FREE_ENTRY {
if entry == self.eof_entry() || self.reserved_eof_entries().contains(&entry) { Ok(None)
return 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);
} }
// entry should be in the valid cluster range here; otherwise something went wrong if let Some(next_cluster) = next_cluster {
if !self.valid_entries().contains(&entry) { assert!(self.valid_entries().contains(&next_cluster));
return Err(FatError::InvalidEntry(entry));
} }
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<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)
} }
} }
pub struct Fat12 { struct Fat12 {
max: u32, max: u32,
next_sectors: Box<[u16]>, next_sectors: Box<[u16]>,
@ -129,7 +225,7 @@ impl Display for Fat12 {
writeln!(f, "Fat 12 {{")?; writeln!(f, "Fat 12 {{")?;
for (i, &x) in self.next_sectors.iter().enumerate() { 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)?; writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
} }
} }
@ -155,7 +251,10 @@ impl Fat12 {
// assume bytes.len() is multiple of 3 // assume bytes.len() is multiple of 3
// TODO: fix later // 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>(); 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<()> { 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(2);
let mut iter = self.next_sectors.chunks_exact(3);
let mut buf: [u8; 3]; let mut buf: [u8; 3];
@ -255,13 +352,18 @@ impl FatOps for Fat12 {
sub_slice.write_all(&buf)?; 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(()) Ok(())
} }
} }
pub struct Fat16 { struct Fat16 {
max: u32, max: u32,
next_sectors: Box<[u16]>, next_sectors: Box<[u16]>,
@ -272,7 +374,7 @@ impl Display for Fat16 {
writeln!(f, "Fat 16 {{")?; writeln!(f, "Fat 16 {{")?;
for (i, &x) in self.next_sectors.iter().enumerate() { 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)?; writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
} }
} }
@ -359,7 +461,7 @@ impl FatOps for Fat16 {
} }
} }
pub struct Fat32 { struct Fat32 {
max: u32, max: u32,
next_sectors: Box<[u32]>, next_sectors: Box<[u32]>,
@ -370,7 +472,7 @@ impl Display for Fat32 {
writeln!(f, "Fat 32 {{")?; writeln!(f, "Fat 32 {{")?;
for (i, &x) in self.next_sectors.iter().enumerate() { for (i, &x) in self.next_sectors.iter().enumerate() {
if x != 0 { if x != FREE_ENTRY {
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?; writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
} }
} }

View file

@ -1,5 +1,7 @@
use std::io::{Read, Write}; use std::io::{Read, Write};
use log::debug;
use crate::subslice::{SubSlice, SubSliceMut}; use crate::subslice::{SubSlice, SubSliceMut};
use crate::{FatFs, FatType}; use crate::{FatFs, FatType};
@ -96,27 +98,28 @@ impl Read for ClusterChainReader<'_> {
} }
pub struct ClusterChainWriter<'a> { pub struct ClusterChainWriter<'a> {
fat_fs: &'a FatFs, fat_fs: &'a mut FatFs,
sub_slice: SubSliceMut, sub_slice: SubSliceMut,
next_cluster: Option<u32>, // next_cluster: Option<u32>,
cur_cluster: u32,
} }
impl<'a> ClusterChainWriter<'a> { impl<'a> ClusterChainWriter<'a> {
pub fn new(fat_fs: &'a FatFs, first_cluster: u32) -> Self { 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 next_cluster = fat_fs.next_cluster(first_cluster).unwrap_or(None);
let sub_slice = fat_fs.cluster_as_subslice_mut(first_cluster); let sub_slice = fat_fs.cluster_as_subslice_mut(first_cluster);
ClusterChainWriter { ClusterChainWriter {
fat_fs, fat_fs,
sub_slice, 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() { match fat_fs.fat_type() {
FatType::Fat12 | FatType::Fat16 => { FatType::Fat12 | FatType::Fat16 => {
// fixed root dir, so no need to chain // fixed root dir, so no need to chain
@ -127,7 +130,7 @@ impl<'a> ClusterChainWriter<'a> {
ClusterChainWriter { ClusterChainWriter {
fat_fs, fat_fs,
sub_slice, sub_slice,
next_cluster: None, cur_cluster: 0,
} }
} }
FatType::Fat32 => { FatType::Fat32 => {
@ -140,12 +143,38 @@ impl<'a> ClusterChainWriter<'a> {
fn move_to_next_cluster(&mut self) -> bool { fn move_to_next_cluster(&mut self) -> bool {
// TODO: should allocate a new cluster here! // 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; 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.fat_fs.cluster_as_subslice_mut(next_cluster);
self.cur_cluster = next_cluster;
true true
} }

View file

@ -2,8 +2,10 @@ use std::cell::RefCell;
use std::fmt::Display; use std::fmt::Display;
use std::rc::Rc; use std::rc::Rc;
use log::debug;
use crate::dir::DirIter; use crate::dir::DirIter;
use crate::fat::{FatError, FatOps}; use crate::fat::FatError;
use crate::iter::ClusterChainReader; use crate::iter::ClusterChainReader;
pub use crate::slice_like::SliceLike; pub use crate::slice_like::SliceLike;
use crate::subslice::{SubSlice, SubSliceMut}; use crate::subslice::{SubSlice, SubSliceMut};
@ -57,6 +59,20 @@ impl Display for FatFs {
unsafe impl Send 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 { impl FatFs {
pub fn load<S>(data: S) -> anyhow::Result<FatFs> pub fn load<S>(data: S) -> anyhow::Result<FatFs>
where where
@ -123,18 +139,14 @@ impl FatFs {
} }
pub fn fat_type(&self) -> FatType { pub fn fat_type(&self) -> FatType {
match &self.fat { self.fat.fat_type()
fat::Fat::Fat12(_) => FatType::Fat12,
fat::Fat::Fat16(_) => FatType::Fat16,
fat::Fat::Fat32(_) => FatType::Fat32,
}
} }
/// byte offset of data cluster /// byte offset of data cluster
fn data_cluster_to_offset(&self, cluster: u32) -> u64 { fn data_cluster_to_offset(&self, cluster: u32) -> u64 {
// assert!(cluster >= 2); // 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 self.data_offset + (cluster - 2) as u64 * self.bytes_per_cluster as u64
} }
@ -144,14 +156,20 @@ impl FatFs {
self.free_count self.free_count
} }
pub fn alloc_cluster(&mut self) -> Option<u32> { pub fn alloc_cluster(&mut self, prev_cluster: Option<u32>) -> Option<u32> {
let Some(cluster) = self.next_free else { let Some(new_cluster) = self.next_free else {
// no free cluster // no free cluster
return None; return None;
}; };
// set cluster as taken debug!("next free cluster: {new_cluster}");
self.fat.set_entry(cluster, self.fat.eof_entry());
// 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 // something went terribly wrong
assert_ne!(self.free_count, 0); assert_ne!(self.free_count, 0);
@ -161,18 +179,12 @@ impl FatFs {
// find next free cluster // find next free cluster
self.next_free = self.fat.first_free_cluster(); self.next_free = self.fat.first_free_cluster();
Some(cluster) Some(new_cluster)
} }
pub fn dealloc_cluster(&mut self, cluster: u32) { pub fn dealloc_cluster(&mut self, cluster: u32) {
// assert cluster is actually valid // assert cluster is actually valid
assert!( self.fat.free_cluster(cluster);
self.fat
.valid_entries()
.contains(&self.fat.get_entry(cluster))
);
self.fat.set_entry(cluster, 0);
if self.next_free.is_none() || self.next_free.unwrap() > cluster { if self.next_free.is_none() || self.next_free.unwrap() > cluster {
self.next_free = Some(cluster); self.next_free = Some(cluster);
@ -240,7 +252,7 @@ impl FatFs {
iter::ClusterChainReader::new(self, first_cluster) 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) iter::ClusterChainWriter::new(self, first_cluster)
} }
@ -263,7 +275,7 @@ impl FatFs {
self.chain_reader(first_cluster) 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 // TODO: needs to take file size into account
assert!(first_cluster >= 2); assert!(first_cluster >= 2);

View file

@ -6,12 +6,19 @@ edition = "2024"
[dependencies] [dependencies]
anyhow = "1.0.98" anyhow = "1.0.98"
bitflags = "2.9.1" 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" compact_string = "0.1.0"
fat-bits = { version = "0.1.0", path = "../fat-bits" } fat-bits = { version = "0.1.0", path = "../fat-bits" }
fuser = "0.15.1" fuser = "0.15.1"
fxhash = "0.2.1" fxhash = "0.2.1"
libc = "0.2.174" libc = "0.2.174"
log = "0.4.27" 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" thiserror = "2.0.12"

View file

@ -117,11 +117,11 @@ impl Filesystem for FatFuse {
let attr = inode.file_attr(); let attr = inode.file_attr();
let generation = inode.generation(); let generation = inode.generation();
debug!("attr: {attr:?}");
reply.entry(&TTL, &attr, generation as u64); reply.entry(&TTL, &attr, generation as u64);
inode.inc_ref_count(); inode.inc_ref_count();
// TODO: update access time
} }
fn forget(&mut self, _req: &fuser::Request<'_>, ino: u64, nlookup: u64) { fn forget(&mut self, _req: &fuser::Request<'_>, ino: u64, nlookup: u64) {
@ -165,6 +165,14 @@ impl Filesystem for FatFuse {
let attr = inode.file_attr(); let attr = inode.file_attr();
inode.update_atime(SystemTime::now()); 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); reply.attr(&TTL, &attr);
} }
@ -515,7 +523,7 @@ impl Filesystem for FatFuse {
let offset = offset as u64; 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}"); debug!("no inode associated with fh {fh} (given ino: {ino}");
reply.error(EBADF); reply.error(EBADF);
@ -544,7 +552,7 @@ impl Filesystem for FatFuse {
return; 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, Ok(writer) => writer,
Err(err) => { Err(err) => {
reply.error(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); let cur_offset = writer.skip(offset);
// can't seek more than we requested // can't seek more than we requested

View file

@ -320,7 +320,7 @@ impl Inode {
Ok(fat_fs.file_reader(self.first_cluster())) Ok(fat_fs.file_reader(self.first_cluster()))
} }
pub fn file_writer<'a>(&'a self, fat_fs: &'a FatFs) -> Result<ClusterChainWriter<'a>, i32> { pub fn file_writer<'a>(&'a self, fat_fs: &'a mut FatFs) -> Result<ClusterChainWriter<'a>, i32> {
if self.is_dir() { if self.is_dir() {
return Err(EISDIR); return Err(EISDIR);
} }
@ -329,16 +329,30 @@ impl Inode {
} }
pub fn update_size(&mut self, new_size: u64) { 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.size = new_size;
self.dirty = true; self.dirty = true;
} }
pub fn update_atime(&mut self, atime: SystemTime) { pub fn update_atime(&mut self, atime: SystemTime) {
if self.atime == atime {
return;
}
self.atime = atime; self.atime = atime;
self.dirty = true; self.dirty = true;
} }
pub fn update_mtime(&mut self, mtime: SystemTime) { pub fn update_mtime(&mut self, mtime: SystemTime) {
if self.mtime == mtime {
return;
}
self.mtime = mtime; self.mtime = mtime;
self.dirty = true; self.dirty = true;
} }
@ -348,6 +362,13 @@ impl Inode {
return Ok(()); return Ok(());
} }
if self.is_root() {
// root dir has no attributes
self.dirty = false;
return Ok(());
}
let Some(parent_inode) = self.parent() else { let Some(parent_inode) = self.parent() else {
anyhow::bail!("parent inode of {} does not exist", self.ino); anyhow::bail!("parent inode of {} does not exist", self.ino);
}; };