From c9e8833ac681369ea16b328959f7e0a90a8cf71f Mon Sep 17 00:00:00 2001 From: Moritz Gmeiner Date: Sat, 2 Aug 2025 00:36:35 +0200 Subject: [PATCH] implemented rmdir --- fat-bits/src/dir.rs | 128 +++++++++++++++++++++++------------------- fat-fuse/src/fuse.rs | 74 ++++++++++++++---------- fat-fuse/src/inode.rs | 11 +++- fat-fuse/src/lib.rs | 17 +++--- 4 files changed, 131 insertions(+), 99 deletions(-) diff --git a/fat-bits/src/dir.rs b/fat-bits/src/dir.rs index cf4b240..4f7e40d 100644 --- a/fat-bits/src/dir.rs +++ b/fat-bits/src/dir.rs @@ -270,6 +270,17 @@ impl DirEntry { 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]) + } + /// indicates this DirEntry is empty /// /// can be either simply empty (0xe5) or the sentinel (0x00) that indicates that all following @@ -660,62 +671,8 @@ impl<'a> DirIter<'a> { } } - /// inner function for iterator - fn next_impl(&mut self) -> anyhow::Result> { - let offset = self.reader.current_offset(); - - let mut chunk = [0; 32]; - - if self.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) => { - 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(); - } - - if let Some(iter) = self - .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); - } - - self.long_filename_buf.reset(); - - Ok(Some(dir_entry)) + pub fn find_by_name(&mut self, name: &str) -> Option { + self.find(|dir_entry| &dir_entry.name_string() == name) } } @@ -723,7 +680,64 @@ impl Iterator for DirIter<'_> { type Item = DirEntry; fn next(&mut self) -> Option { - match self.next_impl() { + fn next_impl(me: &mut DirIter<'_>) -> anyhow::Result> { + 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) = 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); + } + + me.long_filename_buf.reset(); + + Ok(Some(dir_entry)) + } + + match next_impl(self) { Ok(x) => x, Err(e) => { // print error message, try next diff --git a/fat-fuse/src/fuse.rs b/fat-fuse/src/fuse.rs index b54b4f9..52cfdd4 100644 --- a/fat-fuse/src/fuse.rs +++ b/fat-fuse/src/fuse.rs @@ -1,6 +1,5 @@ use std::ffi::c_int; use std::io::{Read, Write}; -use std::rc::Rc; use std::time::Duration; use fat_bits::dir::DirEntry; @@ -44,7 +43,8 @@ impl Filesystem for FatFuse { let Some(name) = name.to_str() else { // TODO: add proper handling of non-utf8 strings debug!("cannot convert OsStr {:?} to str", name); - reply.error(ENOSYS); + + reply.error(EINVAL); return; }; @@ -62,28 +62,17 @@ impl Filesystem for FatFuse { let parent_inode = parent_inode.borrow(); - let dir_entry: DirEntry = - match parent_inode - .dir_iter(&self.fat_fs) - .and_then(|mut dir_iter| { - dir_iter - .find(|dir_entry| &dir_entry.name_string() == name) - .ok_or(ENOENT) - }) { - Ok(dir_entry) => dir_entry, - Err(err) => { - debug!("error: {}", err); - reply.error(err); + let dir_entry: DirEntry = match parent_inode.find_child_by_name(&self.fat_fs, name) { + Ok(dir_entry) => dir_entry, + Err(err) => { + debug!("error: {}", err); + reply.error(err); - return; - } - }; + return; + } + }; - let inode = self.get_or_make_inode_by_dir_entry( - &dir_entry, - parent_inode.ino(), - parent_inode.path(), - ); + let inode = self.get_or_make_inode(&dir_entry, &parent_inode); let mut inode = inode.borrow_mut(); @@ -239,8 +228,39 @@ impl Filesystem for FatFuse { name: &std::ffi::OsStr, reply: fuser::ReplyEmpty, ) { - debug!("[Not Implemented] rmdir(parent: {:#x?}, name: {:?})", parent, name,); - reply.error(ENOSYS); + let Some(name) = name.to_str() else { + // TODO: add proper handling of non-utf8 strings + debug!("cannot convert OsStr {:?} to str", name); + + reply.error(EINVAL); + return; + }; + + let Some(parent_inode) = self.get_inode(parent) else { + debug!("parent inode {parent} does not exist"); + + reply.error(ENOENT); + return; + }; + + let dir_entry = match parent_inode.borrow().find_child_by_name(&self.fat_fs, name) { + Ok(dir_entry) => dir_entry, + Err(err) => { + debug!("parent inode {parent} has no child {name}"); + + reply.error(err); + return; + } + }; + + if let Err(err) = dir_entry.erase(&self.fat_fs) { + debug!("error while erasing DirEntry: {err}"); + + reply.error(EIO); + return; + } + + reply.ok(); } fn rename( @@ -651,14 +671,10 @@ impl Filesystem for FatFuse { // also skip over `offset` entries let dirs: Vec = dir_iter.skip(offset).collect(); - let dir_ino = dir_inode.ino(); - let dir_path = dir_inode.path(); - for dir_entry in dirs { let name = dir_entry.name_string(); - let inode = - self.get_or_make_inode_by_dir_entry(&dir_entry, dir_ino, Rc::clone(&dir_path)); + let inode = self.get_or_make_inode(&dir_entry, &dir_inode); let inode = inode.borrow(); diff --git a/fat-fuse/src/inode.rs b/fat-fuse/src/inode.rs index 35ed6e2..59352a4 100644 --- a/fat-fuse/src/inode.rs +++ b/fat-fuse/src/inode.rs @@ -4,10 +4,10 @@ use std::time::SystemTime; use chrono::{NaiveDateTime, NaiveTime}; use fat_bits::FatFs; -use fat_bits::dir::DirEntry; +use fat_bits::dir::{DirEntry, DirIter}; use fat_bits::iter::{ClusterChainReader, ClusterChainWriter}; use fuser::FileAttr; -use libc::{EISDIR, ENOTDIR}; +use libc::{EISDIR, ENOENT, ENOTDIR}; use log::debug; use rand::{Rng, SeedableRng as _}; @@ -311,7 +311,7 @@ impl Inode { } } - pub fn dir_iter(&self, fat_fs: &FatFs) -> Result, i32> { + pub fn dir_iter<'a>(&'a self, fat_fs: &'a FatFs) -> Result, i32> { if self.kind != Kind::Dir { return Err(ENOTDIR); } @@ -325,6 +325,11 @@ impl Inode { Ok(fat_fs.dir_iter(self.first_cluster)) } + pub fn find_child_by_name(&self, fat_fs: &FatFs, name: &str) -> Result { + 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, i32> { if self.is_dir() { return Err(EISDIR); diff --git a/fat-fuse/src/lib.rs b/fat-fuse/src/lib.rs index 7a13c46..4ef5dae 100644 --- a/fat-fuse/src/lib.rs +++ b/fat-fuse/src/lib.rs @@ -206,12 +206,9 @@ impl FatFuse { self.inode_table.get(&ino) } - fn get_or_make_inode_by_dir_entry( - &mut self, - dir_entry: &DirEntry, - parent_ino: u64, - parent_path: Rc, - ) -> InodeRef { + 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()) @@ -223,9 +220,9 @@ impl FatFuse { // mostly for empty files/directories which have a first cluster of 0 let path = { - let mut path = parent_path.as_ref().to_owned(); + let mut path = parent.path().as_ref().to_owned(); - if parent_ino != inode::ROOT_INO { + if parent.ino() != inode::ROOT_INO { // root inode already has trailing slash path.push('/'); } @@ -242,9 +239,9 @@ impl FatFuse { // no inode found, make a new one let ino = self.next_ino(); - let Some(parent_inode) = self.get_inode(parent_ino).cloned() else { + 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); + panic!("parent_ino {} does not lead to inode", parent.ino()); }; let inode =