From 3c977d2d4217b8ff588c5a01c5a1d95ee5b32e6f Mon Sep 17 00:00:00 2001 From: Moritz Gmeiner Date: Tue, 29 Jul 2025 20:32:25 +0200 Subject: [PATCH 1/3] DirIter: now exposed by FatFs --- fat-bits/src/lib.rs | 22 ++++++++++++++++------ fat-dump/src/main.rs | 4 +--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/fat-bits/src/lib.rs b/fat-bits/src/lib.rs index 0709cce..f17275c 100644 --- a/fat-bits/src/lib.rs +++ b/fat-bits/src/lib.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::io::{Read, Seek, SeekFrom, Write}; use std::rc::Rc; -use crate::dir::{DirEntry, DirIter}; +use crate::dir::DirIter; use crate::fat::{FatError, Fatty}; use crate::subslice::{SubSlice, SubSliceMut}; @@ -217,7 +217,12 @@ impl FatFs { Ok(data) } - pub fn root_dir_iter(&self) -> Box + '_> { + fn chain_reader(&self, first_cluster: u32) -> impl Read { + iter::ClusterChainReader::new(self, first_cluster) + } + + pub fn root_dir_iter<'a>(&'a self) -> DirIter> { + // Box + '_> // TODO: maybe wrap this in another RootDirIter enum, so we don't have to Box if let Some(root_dir_offset) = self.root_dir_offset { @@ -225,7 +230,7 @@ impl FatFs { let sub_slice = SubSlice::new(self, root_dir_offset, self.root_dir_size); - return Box::new(DirIter::new(sub_slice)); + return DirIter::new(Box::new(sub_slice)); } // FAT32 @@ -235,10 +240,15 @@ impl FatFs { let cluster_iter = iter::ClusterChainReader::new(self, root_cluster); - Box::new(DirIter::new(cluster_iter)) + DirIter::new(Box::new(cluster_iter)) } - pub fn chain_reader(&self, first_cluster: u32) -> impl Read { - iter::ClusterChainReader::new(self, first_cluster) + pub fn dir_iter<'a>(&'a self, first_cluster: u32) -> DirIter> { + // TODO: return type must match root_dir_iter + // if the Box is changed there, update here as well + + let cluster_iter = self.chain_reader(first_cluster); + + DirIter::new(Box::new(cluster_iter)) } } diff --git a/fat-dump/src/main.rs b/fat-dump/src/main.rs index 3cb84eb..b2fe7c3 100644 --- a/fat-dump/src/main.rs +++ b/fat-dump/src/main.rs @@ -68,9 +68,7 @@ fn tree(fat_fs: &FatFs, show_hidden: bool) { } if dir_entry.is_dir() { - let reader = fat_fs.chain_reader(dir_entry.first_cluster()); - - let iter = DirIter::new(reader); + let iter = fat_fs.dir_iter(dir_entry.first_cluster()); tree_impl(fat_fs, iter, show_hidden, indent + 1); } From 372aa34022cc4d29178c6e3dfba965bbd7c1aae4 Mon Sep 17 00:00:00 2001 From: Moritz Gmeiner Date: Tue, 29 Jul 2025 20:33:36 +0200 Subject: [PATCH 2/3] moved FUSE impl into separate file, more work on inodes --- Cargo.lock | 55 ++++ fat-fuse/Cargo.toml | 1 + fat-fuse/src/fuse.rs | 572 ++++++++++++++++++++++++++++++++++++++++++ fat-fuse/src/inode.rs | 53 +++- fat-fuse/src/lib.rs | 545 +--------------------------------------- 5 files changed, 686 insertions(+), 540 deletions(-) create mode 100644 fat-fuse/src/fuse.rs diff --git a/Cargo.lock b/Cargo.lock index bc2c84e..3caec75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -122,6 +122,7 @@ dependencies = [ "fuser", "libc", "log", + "rand", "thiserror", ] @@ -141,6 +142,18 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi", +] + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -254,6 +267,30 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + [[package]] name = "rustversion" version = "1.0.21" @@ -315,6 +352,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -454,6 +500,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + [[package]] name = "zerocopy" version = "0.8.26" diff --git a/fat-fuse/Cargo.toml b/fat-fuse/Cargo.toml index 5e60917..ceebf15 100644 --- a/fat-fuse/Cargo.toml +++ b/fat-fuse/Cargo.toml @@ -10,4 +10,5 @@ fat-bits = { version = "0.1.0", path = "../fat-bits" } fuser = "0.15.1" libc = "0.2.174" log = "0.4.27" +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 new file mode 100644 index 0000000..6168950 --- /dev/null +++ b/fat-fuse/src/fuse.rs @@ -0,0 +1,572 @@ +use std::ffi::c_int; +use std::time::Duration; + +use fuser::Filesystem; +use libc::{EIO, ENOENT, ENOSYS, ENOTDIR, EPERM}; +use log::{debug, warn}; + +use crate::FatFuse; + +impl Filesystem for FatFuse { + fn init( + &mut self, + _req: &fuser::Request<'_>, + _config: &mut fuser::KernelConfig, + ) -> Result<(), c_int> { + Ok(()) + } + + fn destroy(&mut self) {} + + fn lookup( + &mut self, + _req: &fuser::Request<'_>, + parent: u64, + name: &std::ffi::OsStr, + reply: fuser::ReplyEntry, + ) { + // warn!("[Not Implemented] lookup(parent: {:#x?}, name {:?})", parent, name); + // reply.error(ENOSYS); + + let parent_inode = if let Some(inode) = self.get_inode(parent) { + inode + } else { + // parent inode does not exist + // TODO: how can we make sure this does not exist? + // panic? + reply.error(EIO); + + return; + }; + + let Ok(dir_iter) = parent_inode.dir_iter(&self.fat_fs) else { + reply.error(ENOTDIR); + return; + }; + + let Some(dir_entry) = + dir_iter.find(|dir_entry| dir_entry.name_str().as_deref() == Some("")) + else { + reply.error(ENOENT); + return; + }; + + reply.entry(&Duration::from_secs(1), attr, generation); + + todo!(); + } + + fn forget(&mut self, _req: &fuser::Request<'_>, _ino: u64, _nlookup: u64) {} + + fn getattr( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + fh: Option, + reply: fuser::ReplyAttr, + ) { + warn!("[Not Implemented] getattr(ino: {:#x?}, fh: {:#x?})", ino, fh); + reply.error(ENOSYS); + } + + fn setattr( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + mode: Option, + uid: Option, + gid: Option, + size: Option, + _atime: Option, + _mtime: Option, + _ctime: Option, + fh: Option, + _crtime: Option, + _chgtime: Option, + _bkuptime: Option, + flags: Option, + reply: fuser::ReplyAttr, + ) { + debug!( + "[Not Implemented] setattr(ino: {:#x?}, mode: {:?}, uid: {:?}, \ + gid: {:?}, size: {:?}, fh: {:?}, flags: {:?})", + ino, mode, uid, gid, size, fh, flags + ); + reply.error(ENOSYS); + } + + fn readlink(&mut self, _req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyData) { + debug!("[Not Implemented] readlink(ino: {:#x?})", ino); + reply.error(ENOSYS); + } + + fn mknod( + &mut self, + _req: &fuser::Request<'_>, + parent: u64, + name: &std::ffi::OsStr, + mode: u32, + umask: u32, + rdev: u32, + reply: fuser::ReplyEntry, + ) { + debug!( + "[Not Implemented] mknod(parent: {:#x?}, name: {:?}, mode: {}, \ + umask: {:#x?}, rdev: {})", + parent, name, mode, umask, rdev + ); + reply.error(ENOSYS); + } + + fn mkdir( + &mut self, + _req: &fuser::Request<'_>, + parent: u64, + name: &std::ffi::OsStr, + mode: u32, + umask: u32, + reply: fuser::ReplyEntry, + ) { + debug!( + "[Not Implemented] mkdir(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?})", + parent, name, mode, umask + ); + reply.error(ENOSYS); + } + + fn unlink( + &mut self, + _req: &fuser::Request<'_>, + parent: u64, + name: &std::ffi::OsStr, + reply: fuser::ReplyEmpty, + ) { + debug!("[Not Implemented] unlink(parent: {:#x?}, name: {:?})", parent, name,); + reply.error(ENOSYS); + } + + fn rmdir( + &mut self, + _req: &fuser::Request<'_>, + parent: u64, + name: &std::ffi::OsStr, + reply: fuser::ReplyEmpty, + ) { + debug!("[Not Implemented] rmdir(parent: {:#x?}, name: {:?})", parent, name,); + reply.error(ENOSYS); + } + + fn symlink( + &mut self, + _req: &fuser::Request<'_>, + parent: u64, + link_name: &std::ffi::OsStr, + target: &std::path::Path, + reply: fuser::ReplyEntry, + ) { + debug!( + "[Not Implemented] symlink(parent: {:#x?}, link_name: {:?}, target: {:?})", + parent, link_name, target, + ); + reply.error(EPERM); + } + + fn rename( + &mut self, + _req: &fuser::Request<'_>, + parent: u64, + name: &std::ffi::OsStr, + newparent: u64, + newname: &std::ffi::OsStr, + flags: u32, + reply: fuser::ReplyEmpty, + ) { + debug!( + "[Not Implemented] rename(parent: {:#x?}, name: {:?}, newparent: {:#x?}, \ + newname: {:?}, flags: {})", + parent, name, newparent, newname, flags, + ); + reply.error(ENOSYS); + } + + fn link( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + newparent: u64, + newname: &std::ffi::OsStr, + reply: fuser::ReplyEntry, + ) { + debug!( + "[Not Implemented] link(ino: {:#x?}, newparent: {:#x?}, newname: {:?})", + ino, newparent, newname + ); + reply.error(EPERM); + } + + fn open(&mut self, _req: &fuser::Request<'_>, _ino: u64, _flags: i32, reply: fuser::ReplyOpen) { + reply.opened(0, 0); + } + + fn read( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + fh: u64, + offset: i64, + size: u32, + flags: i32, + lock_owner: Option, + reply: fuser::ReplyData, + ) { + warn!( + "[Not Implemented] read(ino: {:#x?}, fh: {}, offset: {}, size: {}, \ + flags: {:#x?}, lock_owner: {:?})", + ino, fh, offset, size, flags, lock_owner + ); + reply.error(ENOSYS); + } + + fn write( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + fh: u64, + offset: i64, + data: &[u8], + write_flags: u32, + flags: i32, + lock_owner: Option, + reply: fuser::ReplyWrite, + ) { + debug!( + "[Not Implemented] write(ino: {:#x?}, fh: {}, offset: {}, data.len(): {}, \ + write_flags: {:#x?}, flags: {:#x?}, lock_owner: {:?})", + ino, + fh, + offset, + data.len(), + write_flags, + flags, + lock_owner + ); + reply.error(ENOSYS); + } + + fn flush( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + fh: u64, + lock_owner: u64, + reply: fuser::ReplyEmpty, + ) { + debug!( + "[Not Implemented] flush(ino: {:#x?}, fh: {}, lock_owner: {:?})", + ino, fh, lock_owner + ); + reply.error(ENOSYS); + } + + fn release( + &mut self, + _req: &fuser::Request<'_>, + _ino: u64, + _fh: u64, + _flags: i32, + _lock_owner: Option, + _flush: bool, + reply: fuser::ReplyEmpty, + ) { + reply.ok(); + } + + fn fsync( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + fh: u64, + datasync: bool, + reply: fuser::ReplyEmpty, + ) { + debug!("[Not Implemented] fsync(ino: {:#x?}, fh: {}, datasync: {})", ino, fh, datasync); + reply.error(ENOSYS); + } + + fn opendir( + &mut self, + _req: &fuser::Request<'_>, + _ino: u64, + _flags: i32, + reply: fuser::ReplyOpen, + ) { + reply.opened(0, 0); + } + + fn readdir( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + fh: u64, + offset: i64, + reply: fuser::ReplyDirectory, + ) { + warn!("[Not Implemented] readdir(ino: {:#x?}, fh: {}, offset: {})", ino, fh, offset); + reply.error(ENOSYS); + } + + fn readdirplus( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + fh: u64, + offset: i64, + reply: fuser::ReplyDirectoryPlus, + ) { + debug!( + "[Not Implemented] readdirplus(ino: {:#x?}, fh: {}, offset: {})", + ino, fh, offset + ); + reply.error(ENOSYS); + } + + fn releasedir( + &mut self, + _req: &fuser::Request<'_>, + _ino: u64, + _fh: u64, + _flags: i32, + reply: fuser::ReplyEmpty, + ) { + reply.ok(); + } + + fn fsyncdir( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + fh: u64, + datasync: bool, + reply: fuser::ReplyEmpty, + ) { + debug!( + "[Not Implemented] fsyncdir(ino: {:#x?}, fh: {}, datasync: {})", + ino, fh, datasync + ); + reply.error(ENOSYS); + } + + fn statfs(&mut self, _req: &fuser::Request<'_>, _ino: u64, reply: fuser::ReplyStatfs) { + reply.statfs(0, 0, 0, 0, 0, 512, 255, 0); + } + + fn setxattr( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + name: &std::ffi::OsStr, + _value: &[u8], + flags: i32, + position: u32, + reply: fuser::ReplyEmpty, + ) { + debug!( + "[Not Implemented] setxattr(ino: {:#x?}, name: {:?}, flags: {:#x?}, position: {})", + ino, name, flags, position + ); + reply.error(ENOSYS); + } + + fn getxattr( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + name: &std::ffi::OsStr, + size: u32, + reply: fuser::ReplyXattr, + ) { + debug!("[Not Implemented] getxattr(ino: {:#x?}, name: {:?}, size: {})", ino, name, size); + reply.error(ENOSYS); + } + + fn listxattr( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + size: u32, + reply: fuser::ReplyXattr, + ) { + debug!("[Not Implemented] listxattr(ino: {:#x?}, size: {})", ino, size); + reply.error(ENOSYS); + } + + fn removexattr( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + name: &std::ffi::OsStr, + reply: fuser::ReplyEmpty, + ) { + debug!("[Not Implemented] removexattr(ino: {:#x?}, name: {:?})", ino, name); + reply.error(ENOSYS); + } + + fn access(&mut self, _req: &fuser::Request<'_>, ino: u64, mask: i32, reply: fuser::ReplyEmpty) { + debug!("[Not Implemented] access(ino: {:#x?}, mask: {})", ino, mask); + reply.error(ENOSYS); + } + + fn create( + &mut self, + _req: &fuser::Request<'_>, + parent: u64, + name: &std::ffi::OsStr, + mode: u32, + umask: u32, + flags: i32, + reply: fuser::ReplyCreate, + ) { + debug!( + "[Not Implemented] create(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?}, \ + flags: {:#x?})", + parent, name, mode, umask, flags + ); + reply.error(ENOSYS); + } + + fn getlk( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + fh: u64, + lock_owner: u64, + start: u64, + end: u64, + typ: i32, + pid: u32, + reply: fuser::ReplyLock, + ) { + debug!( + "[Not Implemented] getlk(ino: {:#x?}, fh: {}, lock_owner: {}, start: {}, \ + end: {}, typ: {}, pid: {})", + ino, fh, lock_owner, start, end, typ, pid + ); + reply.error(ENOSYS); + } + + fn setlk( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + fh: u64, + lock_owner: u64, + start: u64, + end: u64, + typ: i32, + pid: u32, + sleep: bool, + reply: fuser::ReplyEmpty, + ) { + debug!( + "[Not Implemented] setlk(ino: {:#x?}, fh: {}, lock_owner: {}, start: {}, \ + end: {}, typ: {}, pid: {}, sleep: {})", + ino, fh, lock_owner, start, end, typ, pid, sleep + ); + reply.error(ENOSYS); + } + + fn bmap( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + blocksize: u32, + idx: u64, + reply: fuser::ReplyBmap, + ) { + debug!( + "[Not Implemented] bmap(ino: {:#x?}, blocksize: {}, idx: {})", + ino, blocksize, idx, + ); + reply.error(ENOSYS); + } + + fn ioctl( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + fh: u64, + flags: u32, + cmd: u32, + in_data: &[u8], + out_size: u32, + reply: fuser::ReplyIoctl, + ) { + debug!( + "[Not Implemented] ioctl(ino: {:#x?}, fh: {}, flags: {}, cmd: {}, \ + in_data.len(): {}, out_size: {})", + ino, + fh, + flags, + cmd, + in_data.len(), + out_size, + ); + reply.error(ENOSYS); + } + + fn fallocate( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + fh: u64, + offset: i64, + length: i64, + mode: i32, + reply: fuser::ReplyEmpty, + ) { + debug!( + "[Not Implemented] fallocate(ino: {:#x?}, fh: {}, offset: {}, \ + length: {}, mode: {})", + ino, fh, offset, length, mode + ); + reply.error(ENOSYS); + } + + fn lseek( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + fh: u64, + offset: i64, + whence: i32, + reply: fuser::ReplyLseek, + ) { + debug!( + "[Not Implemented] lseek(ino: {:#x?}, fh: {}, offset: {}, whence: {})", + ino, fh, offset, whence + ); + reply.error(ENOSYS); + } + + fn copy_file_range( + &mut self, + _req: &fuser::Request<'_>, + ino_in: u64, + fh_in: u64, + offset_in: i64, + ino_out: u64, + fh_out: u64, + offset_out: i64, + len: u64, + flags: u32, + reply: fuser::ReplyWrite, + ) { + debug!( + "[Not Implemented] copy_file_range(ino_in: {:#x?}, fh_in: {}, \ + offset_in: {}, ino_out: {:#x?}, fh_out: {}, offset_out: {}, \ + len: {}, flags: {})", + ino_in, fh_in, offset_in, ino_out, fh_out, offset_out, len, flags + ); + reply.error(ENOSYS); + } +} diff --git a/fat-fuse/src/inode.rs b/fat-fuse/src/inode.rs index 39fe2aa..d46d143 100644 --- a/fat-fuse/src/inode.rs +++ b/fat-fuse/src/inode.rs @@ -1,11 +1,35 @@ +use std::cell::{LazyCell, RefCell}; use std::time::SystemTime; use chrono::{NaiveDateTime, NaiveTime}; use fat_bits::FatFs; -use fat_bits::dir::DirEntry; +use fat_bits::dir::{DirEntry, DirIter}; use fuser::FileAttr; +use rand::{Rng, SeedableRng as _}; -#[derive(Debug, Clone, Copy)] +thread_local! { +/// SAFETY +/// +/// do not access this directly, only invoke the get_random_u32 function +// static RNG: LazyCell> = LazyCell::new(|| UnsafeCell::new(rand::rngs::SmallRng::from_os_rng())); + +/// performance should not be a bottleneck here, since we only need to occasionally generate u32s to +/// be used as generations in inodes +/// if at some point (contrary to expectations) it should become, can switch it to an UnsafeCell +static RNG: LazyCell> = LazyCell::new(|| RefCell::new(rand::rngs::SmallRng::from_os_rng())); +} + +fn get_random_u32() -> u32 { + // RNG.with(|x| unsafe { + // let rng = &mut (*x.get()); + + // rng.random::() + // }) + + RNG.with(|rng| rng.borrow_mut().random()) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Kind { File, Dir, @@ -20,13 +44,15 @@ impl From for fuser::FileType { } } +const ROOT_INO: u64 = 1; + #[derive(Debug)] #[allow(dead_code)] pub struct Inode { ino: u64, + generation: u32, size: u64, - // blocks: u64, block_size: u32, kind: Kind, @@ -49,6 +75,8 @@ impl 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 = get_random_u32(); + let kind = if dir_entry.is_dir() { Kind::Dir } else { @@ -69,6 +97,7 @@ impl Inode { Inode { ino: dir_entry.first_cluster() as u64, + generation, size: dir_entry.file_size() as u64, block_size: fat_fs.bpb().bytes_per_sector() as u32, kind, @@ -103,4 +132,22 @@ impl Inode { flags: 0, } } + + pub fn dir_iter(&self, fat_fs: &FatFs) -> anyhow::Result> { + anyhow::ensure!(self.kind == Kind::Dir, "cannot dir_iter on a file"); + + // 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()); + } + + let chain_reader = fat_fs.chain_reader(self.first_cluster); + + // TODO: get rid of this Box if the boxing is removed from root_dir_iter + Ok(DirIter::new(Box::new(chain_reader))) + } } diff --git a/fat-fuse/src/lib.rs b/fat-fuse/src/lib.rs index 83b2a34..b8499d8 100644 --- a/fat-fuse/src/lib.rs +++ b/fat-fuse/src/lib.rs @@ -1,14 +1,11 @@ +mod fuse; mod inode; use std::cell::RefCell; use std::collections::BTreeMap; -use std::ffi::c_int; use std::rc::Rc; use fat_bits::{FatFs, SliceLike}; -use fuser::Filesystem; -use libc::{ENOSYS, EPERM}; -use log::{debug, warn}; use crate::inode::Inode; @@ -19,6 +16,8 @@ pub struct FatFuse { uid: u32, gid: u32, + next_fd: u32, + inode_table: BTreeMap, } @@ -33,544 +32,16 @@ impl FatFuse { fat_fs, uid, gid, + next_fd: 0, inode_table: BTreeMap::new(), }) } -} -impl Filesystem for FatFuse { - fn init( - &mut self, - _req: &fuser::Request<'_>, - _config: &mut fuser::KernelConfig, - ) -> Result<(), c_int> { - Ok(()) + fn get_inode(&self, ino: u64) -> Option<&Inode> { + self.inode_table.get(&ino) } - fn destroy(&mut self) {} - - fn lookup( - &mut self, - _req: &fuser::Request<'_>, - parent: u64, - name: &std::ffi::OsStr, - reply: fuser::ReplyEntry, - ) { - warn!("[Not Implemented] lookup(parent: {:#x?}, name {:?})", parent, name); - reply.error(ENOSYS); - } - - fn forget(&mut self, _req: &fuser::Request<'_>, _ino: u64, _nlookup: u64) {} - - fn getattr( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - fh: Option, - reply: fuser::ReplyAttr, - ) { - warn!("[Not Implemented] getattr(ino: {:#x?}, fh: {:#x?})", ino, fh); - reply.error(ENOSYS); - } - - fn setattr( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - mode: Option, - uid: Option, - gid: Option, - size: Option, - _atime: Option, - _mtime: Option, - _ctime: Option, - fh: Option, - _crtime: Option, - _chgtime: Option, - _bkuptime: Option, - flags: Option, - reply: fuser::ReplyAttr, - ) { - debug!( - "[Not Implemented] setattr(ino: {:#x?}, mode: {:?}, uid: {:?}, \ - gid: {:?}, size: {:?}, fh: {:?}, flags: {:?})", - ino, mode, uid, gid, size, fh, flags - ); - reply.error(ENOSYS); - } - - fn readlink(&mut self, _req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyData) { - debug!("[Not Implemented] readlink(ino: {:#x?})", ino); - reply.error(ENOSYS); - } - - fn mknod( - &mut self, - _req: &fuser::Request<'_>, - parent: u64, - name: &std::ffi::OsStr, - mode: u32, - umask: u32, - rdev: u32, - reply: fuser::ReplyEntry, - ) { - debug!( - "[Not Implemented] mknod(parent: {:#x?}, name: {:?}, mode: {}, \ - umask: {:#x?}, rdev: {})", - parent, name, mode, umask, rdev - ); - reply.error(ENOSYS); - } - - fn mkdir( - &mut self, - _req: &fuser::Request<'_>, - parent: u64, - name: &std::ffi::OsStr, - mode: u32, - umask: u32, - reply: fuser::ReplyEntry, - ) { - debug!( - "[Not Implemented] mkdir(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?})", - parent, name, mode, umask - ); - reply.error(ENOSYS); - } - - fn unlink( - &mut self, - _req: &fuser::Request<'_>, - parent: u64, - name: &std::ffi::OsStr, - reply: fuser::ReplyEmpty, - ) { - debug!("[Not Implemented] unlink(parent: {:#x?}, name: {:?})", parent, name,); - reply.error(ENOSYS); - } - - fn rmdir( - &mut self, - _req: &fuser::Request<'_>, - parent: u64, - name: &std::ffi::OsStr, - reply: fuser::ReplyEmpty, - ) { - debug!("[Not Implemented] rmdir(parent: {:#x?}, name: {:?})", parent, name,); - reply.error(ENOSYS); - } - - fn symlink( - &mut self, - _req: &fuser::Request<'_>, - parent: u64, - link_name: &std::ffi::OsStr, - target: &std::path::Path, - reply: fuser::ReplyEntry, - ) { - debug!( - "[Not Implemented] symlink(parent: {:#x?}, link_name: {:?}, target: {:?})", - parent, link_name, target, - ); - reply.error(EPERM); - } - - fn rename( - &mut self, - _req: &fuser::Request<'_>, - parent: u64, - name: &std::ffi::OsStr, - newparent: u64, - newname: &std::ffi::OsStr, - flags: u32, - reply: fuser::ReplyEmpty, - ) { - debug!( - "[Not Implemented] rename(parent: {:#x?}, name: {:?}, newparent: {:#x?}, \ - newname: {:?}, flags: {})", - parent, name, newparent, newname, flags, - ); - reply.error(ENOSYS); - } - - fn link( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - newparent: u64, - newname: &std::ffi::OsStr, - reply: fuser::ReplyEntry, - ) { - debug!( - "[Not Implemented] link(ino: {:#x?}, newparent: {:#x?}, newname: {:?})", - ino, newparent, newname - ); - reply.error(EPERM); - } - - fn open(&mut self, _req: &fuser::Request<'_>, _ino: u64, _flags: i32, reply: fuser::ReplyOpen) { - reply.opened(0, 0); - } - - fn read( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - fh: u64, - offset: i64, - size: u32, - flags: i32, - lock_owner: Option, - reply: fuser::ReplyData, - ) { - warn!( - "[Not Implemented] read(ino: {:#x?}, fh: {}, offset: {}, size: {}, \ - flags: {:#x?}, lock_owner: {:?})", - ino, fh, offset, size, flags, lock_owner - ); - reply.error(ENOSYS); - } - - fn write( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - fh: u64, - offset: i64, - data: &[u8], - write_flags: u32, - flags: i32, - lock_owner: Option, - reply: fuser::ReplyWrite, - ) { - debug!( - "[Not Implemented] write(ino: {:#x?}, fh: {}, offset: {}, data.len(): {}, \ - write_flags: {:#x?}, flags: {:#x?}, lock_owner: {:?})", - ino, - fh, - offset, - data.len(), - write_flags, - flags, - lock_owner - ); - reply.error(ENOSYS); - } - - fn flush( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - fh: u64, - lock_owner: u64, - reply: fuser::ReplyEmpty, - ) { - debug!( - "[Not Implemented] flush(ino: {:#x?}, fh: {}, lock_owner: {:?})", - ino, fh, lock_owner - ); - reply.error(ENOSYS); - } - - fn release( - &mut self, - _req: &fuser::Request<'_>, - _ino: u64, - _fh: u64, - _flags: i32, - _lock_owner: Option, - _flush: bool, - reply: fuser::ReplyEmpty, - ) { - reply.ok(); - } - - fn fsync( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - fh: u64, - datasync: bool, - reply: fuser::ReplyEmpty, - ) { - debug!("[Not Implemented] fsync(ino: {:#x?}, fh: {}, datasync: {})", ino, fh, datasync); - reply.error(ENOSYS); - } - - fn opendir( - &mut self, - _req: &fuser::Request<'_>, - _ino: u64, - _flags: i32, - reply: fuser::ReplyOpen, - ) { - reply.opened(0, 0); - } - - fn readdir( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - fh: u64, - offset: i64, - reply: fuser::ReplyDirectory, - ) { - warn!("[Not Implemented] readdir(ino: {:#x?}, fh: {}, offset: {})", ino, fh, offset); - reply.error(ENOSYS); - } - - fn readdirplus( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - fh: u64, - offset: i64, - reply: fuser::ReplyDirectoryPlus, - ) { - debug!( - "[Not Implemented] readdirplus(ino: {:#x?}, fh: {}, offset: {})", - ino, fh, offset - ); - reply.error(ENOSYS); - } - - fn releasedir( - &mut self, - _req: &fuser::Request<'_>, - _ino: u64, - _fh: u64, - _flags: i32, - reply: fuser::ReplyEmpty, - ) { - reply.ok(); - } - - fn fsyncdir( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - fh: u64, - datasync: bool, - reply: fuser::ReplyEmpty, - ) { - debug!( - "[Not Implemented] fsyncdir(ino: {:#x?}, fh: {}, datasync: {})", - ino, fh, datasync - ); - reply.error(ENOSYS); - } - - fn statfs(&mut self, _req: &fuser::Request<'_>, _ino: u64, reply: fuser::ReplyStatfs) { - reply.statfs(0, 0, 0, 0, 0, 512, 255, 0); - } - - fn setxattr( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - name: &std::ffi::OsStr, - _value: &[u8], - flags: i32, - position: u32, - reply: fuser::ReplyEmpty, - ) { - debug!( - "[Not Implemented] setxattr(ino: {:#x?}, name: {:?}, flags: {:#x?}, position: {})", - ino, name, flags, position - ); - reply.error(ENOSYS); - } - - fn getxattr( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - name: &std::ffi::OsStr, - size: u32, - reply: fuser::ReplyXattr, - ) { - debug!("[Not Implemented] getxattr(ino: {:#x?}, name: {:?}, size: {})", ino, name, size); - reply.error(ENOSYS); - } - - fn listxattr( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - size: u32, - reply: fuser::ReplyXattr, - ) { - debug!("[Not Implemented] listxattr(ino: {:#x?}, size: {})", ino, size); - reply.error(ENOSYS); - } - - fn removexattr( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - name: &std::ffi::OsStr, - reply: fuser::ReplyEmpty, - ) { - debug!("[Not Implemented] removexattr(ino: {:#x?}, name: {:?})", ino, name); - reply.error(ENOSYS); - } - - fn access(&mut self, _req: &fuser::Request<'_>, ino: u64, mask: i32, reply: fuser::ReplyEmpty) { - debug!("[Not Implemented] access(ino: {:#x?}, mask: {})", ino, mask); - reply.error(ENOSYS); - } - - fn create( - &mut self, - _req: &fuser::Request<'_>, - parent: u64, - name: &std::ffi::OsStr, - mode: u32, - umask: u32, - flags: i32, - reply: fuser::ReplyCreate, - ) { - debug!( - "[Not Implemented] create(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?}, \ - flags: {:#x?})", - parent, name, mode, umask, flags - ); - reply.error(ENOSYS); - } - - fn getlk( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - fh: u64, - lock_owner: u64, - start: u64, - end: u64, - typ: i32, - pid: u32, - reply: fuser::ReplyLock, - ) { - debug!( - "[Not Implemented] getlk(ino: {:#x?}, fh: {}, lock_owner: {}, start: {}, \ - end: {}, typ: {}, pid: {})", - ino, fh, lock_owner, start, end, typ, pid - ); - reply.error(ENOSYS); - } - - fn setlk( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - fh: u64, - lock_owner: u64, - start: u64, - end: u64, - typ: i32, - pid: u32, - sleep: bool, - reply: fuser::ReplyEmpty, - ) { - debug!( - "[Not Implemented] setlk(ino: {:#x?}, fh: {}, lock_owner: {}, start: {}, \ - end: {}, typ: {}, pid: {}, sleep: {})", - ino, fh, lock_owner, start, end, typ, pid, sleep - ); - reply.error(ENOSYS); - } - - fn bmap( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - blocksize: u32, - idx: u64, - reply: fuser::ReplyBmap, - ) { - debug!( - "[Not Implemented] bmap(ino: {:#x?}, blocksize: {}, idx: {})", - ino, blocksize, idx, - ); - reply.error(ENOSYS); - } - - fn ioctl( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - fh: u64, - flags: u32, - cmd: u32, - in_data: &[u8], - out_size: u32, - reply: fuser::ReplyIoctl, - ) { - debug!( - "[Not Implemented] ioctl(ino: {:#x?}, fh: {}, flags: {}, cmd: {}, \ - in_data.len(): {}, out_size: {})", - ino, - fh, - flags, - cmd, - in_data.len(), - out_size, - ); - reply.error(ENOSYS); - } - - fn fallocate( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - fh: u64, - offset: i64, - length: i64, - mode: i32, - reply: fuser::ReplyEmpty, - ) { - debug!( - "[Not Implemented] fallocate(ino: {:#x?}, fh: {}, offset: {}, \ - length: {}, mode: {})", - ino, fh, offset, length, mode - ); - reply.error(ENOSYS); - } - - fn lseek( - &mut self, - _req: &fuser::Request<'_>, - ino: u64, - fh: u64, - offset: i64, - whence: i32, - reply: fuser::ReplyLseek, - ) { - debug!( - "[Not Implemented] lseek(ino: {:#x?}, fh: {}, offset: {}, whence: {})", - ino, fh, offset, whence - ); - reply.error(ENOSYS); - } - - fn copy_file_range( - &mut self, - _req: &fuser::Request<'_>, - ino_in: u64, - fh_in: u64, - offset_in: i64, - ino_out: u64, - fh_out: u64, - offset_out: i64, - len: u64, - flags: u32, - reply: fuser::ReplyWrite, - ) { - debug!( - "[Not Implemented] copy_file_range(ino_in: {:#x?}, fh_in: {}, \ - offset_in: {}, ino_out: {:#x?}, fh_out: {}, offset_out: {}, \ - len: {}, flags: {})", - ino_in, fh_in, offset_in, ino_out, fh_out, offset_out, len, flags - ); - reply.error(ENOSYS); + fn get_inode_mut(&mut self, ino: u64) -> Option<&mut Inode> { + self.inode_table.get_mut(&ino) } } From 7921064ae2156293bb5d5e1a32bc4b5fabc45926 Mon Sep 17 00:00:00 2001 From: Moritz Gmeiner Date: Tue, 29 Jul 2025 20:34:35 +0200 Subject: [PATCH 3/3] moved short name str into separate buffer --- fat-bits/src/dir.rs | 106 ++++++++++++++++++++++++++++++++------------ 1 file changed, 78 insertions(+), 28 deletions(-) diff --git a/fat-bits/src/dir.rs b/fat-bits/src/dir.rs index bc8e7a4..be69a54 100644 --- a/fat-bits/src/dir.rs +++ b/fat-bits/src/dir.rs @@ -64,24 +64,30 @@ pub struct DirEntry { file_size: u32, + // 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, } impl Display for DirEntry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut name = self.name_string().unwrap_or_else(|| "".to_owned()); + let name = self.name_str().unwrap_or(""); - 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(()) @@ -92,7 +98,7 @@ impl DirEntry { pub fn load(bytes: &[u8]) -> anyhow::Result { assert_eq!(bytes.len(), 32); - let name = bytes[..11].try_into().unwrap(); + let name: [u8; 11] = bytes[..11].try_into().unwrap(); let attr = Attr::from_bits_truncate(bytes[11]); let create_time_tenths = bytes[13]; @@ -130,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, @@ -141,6 +173,7 @@ impl DirEntry { write_time, write_date, file_size, + short_name, long_name: None, }) } @@ -216,29 +249,19 @@ impl DirEntry { std::str::from_utf8(self.extension()).ok() } - pub fn name_string(&self) -> Option { - if let Some(long_filename) = self.long_name() { - return Some(long_filename.to_owned()); + fn short_name(&self) -> &[u8] { + &self.short_name + } + + fn short_name_str(&self) -> Option<&str> { + let mut short_name = self.short_name(); + + // remove trailing zeros + while short_name.last() == Some(&0) { + short_name = &short_name[..short_name.len() - 1]; } - 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) + std::str::from_utf8(short_name).ok() } pub fn long_name(&self) -> Option<&str> { @@ -249,6 +272,33 @@ impl DirEntry { self.long_name = Some(long_name); } + 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 { self.attr } @@ -540,7 +590,7 @@ impl DirIter { .map_err(|e| { anyhow::anyhow!( "failed to get long filename for {}: {}", - dir_entry.name_string().as_deref().unwrap_or(""), + dir_entry.name_str().as_deref().unwrap_or(""), e ) })? {