Skip to content

Commit

Permalink
fs: implement file & directory removal (unlink)
Browse files Browse the repository at this point in the history
  • Loading branch information
equation314 committed Apr 6, 2023
1 parent 664f98b commit f2dbc95
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 5 deletions.
30 changes: 28 additions & 2 deletions apps/fs/shell/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,34 @@ fn do_mkdir(args: &str) {
}
}

fn do_rm(_args: &str) {
print_err!("rm", Unsupported);
fn do_rm(args: &str) {
if args.is_empty() {
print_err!("rm", "missing operand");
return;
}
let mut rm_dir = false;
for arg in args.split_whitespace() {
if arg == "-d" {
rm_dir = true;
}
}

fn rm_one(path: &str, rm_dir: bool) -> io::Result<()> {
if rm_dir && fs::metadata(path)?.is_dir() {
fs::remove_dir(path)
} else {
fs::remove_file(path)
}
}

for path in args.split_whitespace() {
if path == "-d" {
continue;
}
if let Err(e) = rm_one(path, rm_dir) {
print_err!("rm", format_args!("cannot remove '{path}'"), e.as_str());
}
}
}

fn do_cd(mut args: &str) {
Expand Down
10 changes: 10 additions & 0 deletions modules/axfs/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,13 @@ pub fn create_dir(path: &str) -> io::Result<()> {
pub fn create_dir_all(path: &str) -> io::Result<()> {
DirBuilder::new().recursive(true).create(path)
}

/// Removes an empty directory.
pub fn remove_dir(path: &str) -> io::Result<()> {
crate::root::remove_dir(path)
}

/// Removes a file from the filesystem.
pub fn remove_file(path: &str) -> io::Result<()> {
crate::root::remove_file(path)
}
10 changes: 10 additions & 0 deletions modules/axfs/src/fs/fatfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,16 @@ impl VfsNodeOps for DirWrapper<'static> {
}
}

fn remove(&self, path: &str) -> VfsResult {
debug!("remove at fatfs: {}", path);
let path = path.trim_matches('/');
assert!(!path.is_empty()); // already check at `root.rs`
if let Some(rest) = path.strip_prefix("./") {
return self.remove(rest);
}
self.0.remove(path).map_err(as_vfs_err)
}

fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult<usize> {
let mut iter = self.0.iter().skip(start_idx);
for (i, out_entry) in dirents.iter_mut().enumerate() {
Expand Down
49 changes: 49 additions & 0 deletions modules/axfs/src/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ impl VfsNodeOps for RootDirectory {
}
})
}

fn remove(&self, path: &str) -> VfsResult {
self.lookup_mounted_fs(path, |fs, rest_path| {
if rest_path.is_empty() {
ax_err!(PermissionDenied) // cannot remove mount points
} else {
fs.root_dir().remove(rest_path)
}
})
}
}

pub(crate) fn init_rootfs(disk: crate::dev::Disk) {
Expand Down Expand Up @@ -193,6 +203,45 @@ pub(crate) fn create_dir(path: &str) -> AxResult {
}
}

pub(crate) fn remove_file(path: &str) -> AxResult {
let node = lookup(path)?;
let attr = node.get_attr()?;
if attr.is_dir() {
ax_err!(IsADirectory)
} else if !attr.perm().owner_writable() {
ax_err!(PermissionDenied)
} else {
parent_node_of(path).remove(path)
}
}

pub(crate) fn remove_dir(path: &str) -> AxResult {
// TODO: canonicalize path to avoid bypassing checks for removeing mount points
if path.is_empty() {
return ax_err!(NotFound);
}
let path_check = path.trim_matches('/');
if path_check.is_empty() {
return ax_err!(DirectoryNotEmpty); // rm -d '/'
} else if path_check == "."
|| path_check == ".."
|| path_check.ends_with("/.")
|| path_check.ends_with("/..")
{
return ax_err!(InvalidInput);
}

let node = lookup(path)?;
let attr = node.get_attr()?;
if !attr.is_dir() {
ax_err!(NotADirectory)
} else if !attr.perm().owner_writable() {
ax_err!(PermissionDenied)
} else {
parent_node_of(path).remove(path)
}
}

pub(crate) fn current_dir() -> AxResult<String> {
Ok(CURRENT_DIR_PATH.lock().clone())
}
Expand Down
49 changes: 48 additions & 1 deletion modules/axfs/tests/test_axfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,39 @@ fn test_create_file_dir() -> Result<()> {
Ok(())
}

fn test_remove_file_dir() -> Result<()> {
// remove a file and test existence
let fname = "//very-long-dir-name/..///new-file.txt";
println!("test remove file {:?}:", fname);
assert_err!(fs::remove_dir(fname), NotADirectory);
assert!(fs::remove_file(fname).is_ok());
assert_err!(fs::metadata(fname), NotFound);
assert_err!(fs::remove_file(fname), NotFound);

// remove a directory and test existence
let dirname = "very//.//long/../long/.//./new-dir////";
println!("test remove dir {:?}:", dirname);
assert_err!(fs::remove_file(dirname), IsADirectory);
assert!(fs::remove_dir(dirname).is_ok());
assert_err!(fs::metadata(dirname), NotFound);
assert_err!(fs::remove_dir(fname), NotFound);

// error cases
assert_err!(fs::remove_file(""), NotFound);
assert_err!(fs::remove_dir("/"), DirectoryNotEmpty);
assert_err!(fs::remove_dir("."), InvalidInput);
assert_err!(fs::remove_dir("../"), InvalidInput);
assert_err!(fs::remove_dir("./././/"), InvalidInput);
assert_err!(fs::remove_file("///very/./"), IsADirectory);
assert_err!(fs::remove_file("short.txt/"), NotADirectory);
assert_err!(fs::remove_dir(".///"), InvalidInput);
assert_err!(fs::remove_dir("/./very///"), DirectoryNotEmpty);
assert_err!(fs::remove_dir("very/long/.."), InvalidInput);

println!("test_remove_file_dir() OK!");
Ok(())
}

fn test_devfs() -> Result<()> {
const N: usize = 32;
let mut buf = [1; N];
Expand Down Expand Up @@ -188,13 +221,26 @@ fn test_devfs() -> Result<()> {
assert!(md.is_dir());

// stat /dev/foo/bar
let fname = "/dev/.//./foo//./././bar";
let fname = ".//.///././/./dev///.///./foo//././bar";
let file = File::open(fname)?;
let md = file.metadata()?;
println!("metadata of {:?}: {:?}", fname, md);
assert_eq!(md.file_type(), FileType::CharDevice);
assert!(!md.is_dir());

// error cases
assert_err!(fs::metadata("/dev/null/"), NotADirectory);
assert_err!(fs::create_dir("dev"), AlreadyExists);
assert_err!(File::create_new("/dev/"), AlreadyExists);
assert_err!(fs::create_dir("/dev/zero"), AlreadyExists);
assert_err!(fs::write("/dev/stdout", "test"), PermissionDenied);
assert_err!(fs::create_dir("/dev/test"), PermissionDenied);
assert_err!(fs::remove_file("/dev/null"), PermissionDenied);
assert_err!(fs::remove_dir("./dev"), PermissionDenied);
assert_err!(fs::remove_dir("./dev/."), InvalidInput);
assert_err!(fs::remove_dir("///dev//..//"), InvalidInput);
// assert_err!(fs::remove_dir("very/../dev//"), PermissionDenied); // TODO

println!("test_devfs() OK!");
Ok(())
}
Expand All @@ -210,5 +256,6 @@ fn test_axfs() {
test_read_dir().expect("test_read_dir() failed");
test_file_permission().expect("test_file_permission() failed");
test_create_file_dir().expect("test_create_file_dir() failed");
test_remove_file_dir().expect("test_remove_file_dir() failed");
test_devfs().expect("test_devfs() failed");
}
4 changes: 2 additions & 2 deletions ulib/libax/src/fs.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pub use axfs::api::{canonicalize, metadata, read, read_to_string, write};
pub use axfs::api::{create_dir, create_dir_all, read_dir};
pub use axfs::api::{canonicalize, metadata, read, read_to_string, remove_file, write};
pub use axfs::api::{create_dir, create_dir_all, read_dir, remove_dir};
pub use axfs::api::{DirEntry, File, FileType, Metadata, OpenOptions, Permissions, ReadDir};

0 comments on commit f2dbc95

Please sign in to comment.