implemented rmdir

This commit is contained in:
Moritz Gmeiner 2025-08-02 00:36:35 +02:00
commit c9e8833ac6
4 changed files with 131 additions and 99 deletions

View file

@ -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<Option<DirEntry>> {
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<DirEntry> {
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<Self::Item> {
match self.next_impl() {
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) = 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

View file

@ -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<DirEntry> = 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();

View file

@ -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<impl Iterator<Item = DirEntry>, i32> {
pub fn dir_iter<'a>(&'a self, fat_fs: &'a FatFs) -> Result<DirIter<'a>, 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<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);

View file

@ -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<str>,
) -> 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 =