Skip to content

Commit

Permalink
feat: Add the ability to remove empty directories
Browse files Browse the repository at this point in the history
  • Loading branch information
Oakchris1955 committed Sep 14, 2024
1 parent 7a2bc0c commit 63b0f9b
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ where
NotADirectory,
/// Found a directory when we expected a file
IsADirectory,
/// Expected an empty directory
DirectoryNotEmpty,
/// This file cannot be modified, as it is read-only
ReadOnlyFile,
/// A file or directory wasn't found
Expand Down
5 changes: 5 additions & 0 deletions src/fat/direntry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ impl TryFrom<EntryModificationTime> for PrimitiveDateTime {
// a directory entry occupies 32 bytes
pub(crate) const DIRENTRY_SIZE: usize = 32;

// each directory other than the root directory must have
// at least the `.` and `..` entries
// TODO: actually check this on runtime
pub(crate) const NONROOT_MIN_DIRENTRIES: usize = 2;

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub(crate) struct FATDirEntry {
pub(crate) sfn: SFN,
Expand Down
46 changes: 46 additions & 0 deletions src/fat/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,52 @@ where
Ok(())
}

/// Remove an empty directory from the filesystem
///
/// Errors if the path provided points to the root directory
pub fn remove_empty_dir(&mut self, path: PathBuf) -> FSResult<(), S::Error> {
if path.is_malformed() {
return Err(FSError::MalformedPath);
}

if !path.is_dir() {
log::error!("Not a directory");
return Err(FSError::NotADirectory);
}

if path == PathBuf::new() {
// we are in the root directory, we can't remove it
return Err(S::Error::new(
<S::Error as IOError>::Kind::new_unsupported(),
"We can't remove the root directory",
)
.into());
}

let dir_entries = self.read_dir(path.clone())?;

if dir_entries.len() > NONROOT_MIN_DIRENTRIES {
return Err(FSError::DirectoryNotEmpty);
}

let parent_path = path.parent();

let parent_dir_entries = self.read_dir(parent_path)?;

let entry = parent_dir_entries
.iter()
.find(|entry| entry.path == path)
.ok_or(FSError::NotFound)?;

// we first clear the corresponding entry chain in the parent directory
self.remove_entry_chain(&entry.chain)?;

// then we remove the allocated cluster chain
self.free_cluster_chain(entry.data_cluster)?;

Ok(())
}

/// Get a corresponding [`RWFile`] object from a [`PathBuf`]
///
/// Borrows `&mut self` until that [`RWFile`] object is dropped, effectively locking `self` until that file closed
Expand Down
44 changes: 44 additions & 0 deletions src/fat/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,28 @@ fn remove_data_region_file() {
}
}

#[test]
fn remove_empty_dir() {
use std::io::Cursor;

let mut storage = Cursor::new(FAT16.to_owned());
let mut fs = FileSystem::from_storage(&mut storage).unwrap();

let dir_path = PathBuf::from("/another root directory/");

fs.remove_empty_dir(dir_path.clone()).unwrap();

// the directory should now be gone
let dir_result = fs.read_dir(dir_path);
match dir_result {
Err(err) => match err {
FSError::NotFound => (),
_ => panic!("unexpected IOError: {:?}", err),
},
_ => panic!("the directory should have been deleted by now"),
}
}

#[test]
#[allow(non_snake_case)]
fn FAT_tables_after_write_are_identical() {
Expand Down Expand Up @@ -466,6 +488,28 @@ fn remove_fat32_file() {
}
}

#[test]
fn remove_fat32_dir() {
use std::io::Cursor;

let mut storage = Cursor::new(FAT32.to_owned());
let mut fs = FileSystem::from_storage(&mut storage).unwrap();

let dir_path = PathBuf::from("/emptydir/");

fs.remove_empty_dir(dir_path.clone()).unwrap();

// the directory should now be gone
let dir_result = fs.read_dir(dir_path);
match dir_result {
Err(err) => match err {
FSError::NotFound => (),
_ => panic!("unexpected IOError: {:?}", err),
},
_ => panic!("the directory should have been deleted by now"),
}
}

#[test]
#[allow(non_snake_case)]
fn FAT_tables_after_fat32_write_are_identical() {
Expand Down
2 changes: 1 addition & 1 deletion src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ fn is_forbidden(pathbuf: &PathBuf) -> bool {
// TODO: pushing an absolute path should replace a pathbuf

/// Represents an owned, mutable path
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PathBuf {
inner: VecDeque<String>,
}
Expand Down

0 comments on commit 63b0f9b

Please sign in to comment.