diff --git a/monoio/Cargo.toml b/monoio/Cargo.toml index 59b4043e..ab023036 100644 --- a/monoio/Cargo.toml +++ b/monoio/Cargo.toml @@ -71,6 +71,8 @@ zero-copy = [] splice = [] # mkdirat2 op(requires kernel 5.15+) mkdirat = [] +# unlinkat op(requires kernel 5.11+) +unlinkat = [] # enable `async main` macros support macros = ["monoio-macros"] # allow waker to be sent across threads diff --git a/monoio/src/driver/op.rs b/monoio/src/driver/op.rs index b646225d..100e0c0c 100644 --- a/monoio/src/driver/op.rs +++ b/monoio/src/driver/op.rs @@ -25,6 +25,9 @@ mod statx; #[cfg(all(unix, feature = "mkdirat"))] mod mkdir; +#[cfg(all(unix, feature = "unlinkat"))] +mod unlink; + #[cfg(all(target_os = "linux", feature = "splice"))] mod splice; diff --git a/monoio/src/driver/op/unlink.rs b/monoio/src/driver/op/unlink.rs new file mode 100644 index 00000000..3871d29c --- /dev/null +++ b/monoio/src/driver/op/unlink.rs @@ -0,0 +1,57 @@ +use std::{ffi::CString, io, path::Path}; + +#[cfg(all(target_os = "linux", feature = "iouring"))] +use io_uring::{opcode, squeue::Entry, types::Fd}; +#[cfg(all(target_os = "linux", feature = "iouring"))] +use libc::{AT_FDCWD, AT_REMOVEDIR}; + +use super::{Op, OpAble}; +use crate::driver::util::cstr; +#[cfg(any(feature = "legacy", feature = "poll-io"))] +use crate::{driver::ready::Direction, syscall_u32}; + +pub(crate) struct Unlink { + path: CString, + remove_dir: bool, +} + +impl Op { + pub(crate) fn unlink>(path: P) -> io::Result> { + let path = cstr(path.as_ref())?; + Op::submit_with(Unlink { + path, + remove_dir: false, + }) + } + + pub(crate) fn rmdir>(path: P) -> io::Result> { + let path = cstr(path.as_ref())?; + Op::submit_with(Unlink { + path, + remove_dir: true, + }) + } +} + +impl OpAble for Unlink { + #[cfg(all(target_os = "linux", feature = "iouring"))] + fn uring_op(&mut self) -> Entry { + opcode::UnlinkAt::new(Fd(AT_FDCWD), self.path.as_c_str().as_ptr()) + .flags(if self.remove_dir { AT_REMOVEDIR } else { 0 }) + .build() + } + + #[cfg(any(feature = "legacy", feature = "poll-io"))] + fn legacy_interest(&self) -> Option<(Direction, usize)> { + None + } + + #[cfg(any(feature = "legacy", feature = "poll-io"))] + fn legacy_call(&mut self) -> io::Result { + if self.remove_dir { + syscall_u32!(rmdir(self.path.as_c_str().as_ptr())) + } else { + syscall_u32!(unlink(self.path.as_c_str().as_ptr())) + } + } +} diff --git a/monoio/src/fs/mod.rs b/monoio/src/fs/mod.rs index b8a47009..442c0c4a 100644 --- a/monoio/src/fs/mod.rs +++ b/monoio/src/fs/mod.rs @@ -34,6 +34,8 @@ mod permissions; pub use permissions::Permissions; use crate::buf::IoBuf; +#[cfg(all(unix, feature = "unlinkat"))] +use crate::driver::op::Op; /// Read the entire contents of a file into a bytes vector. #[cfg(unix)] @@ -62,3 +64,72 @@ pub async fn write, C: IoBuf>(path: P, contents: C) -> (io::Resul }; file.write_all_at(contents, 0).await } + +/// Removes a file from the filesystem. +/// +/// Note that there is no +/// guarantee that the file is immediately deleted (e.g., depending on +/// platform, other open file descriptors may prevent immediate removal). +/// +/// # Platform-specific behavior +/// +/// This function is currently only implemented for Unix. +/// +/// # Errors +/// +/// This function will return an error in the following situations, but is not +/// limited to just these cases: +/// +/// * `path` points to a directory. +/// * The file doesn't exist. +/// * The user lacks permissions to remove the file. +/// +/// # Examples +/// +/// ```no_run +/// use monoio::fs::File; +/// +/// #[monoio::main] +/// async fn main() -> std::io::Result<()> { +/// fs::remove_file("a.txt").await?; +/// Ok(()) +/// } +/// ``` +#[cfg(all(unix, feature = "unlinkat"))] +pub async fn remove_file>(path: P) -> io::Result<()> { + Op::unlink(path)?.await.meta.result?; + Ok(()) +} + +/// Removes an empty directory. +/// +/// # Platform-specific behavior +/// +/// This function is currently only implemented for Unix. +/// +/// # Errors +/// +/// This function will return an error in the following situations, but is not +/// limited to just these cases: +/// +/// * `path` doesn't exist. +/// * `path` isn't a directory. +/// * The user lacks permissions to remove the directory at the provided `path`. +/// * The directory isn't empty. +/// +/// # Examples +/// +/// ```no_run +/// use monoio::fs::File; +/// +/// #[monoio::main] +/// async fn main() -> std::io::Result<()> { +/// fs::remove_dir("/some/dir").await?; +/// Ok(()) +/// } +/// ``` +#[cfg(all(unix, feature = "unlinkat"))] +pub async fn remove_dir>(path: P) -> io::Result<()> { + Op::rmdir(path)?.await.meta.result?; + Ok(()) +} diff --git a/monoio/tests/fs_unlink.rs b/monoio/tests/fs_unlink.rs new file mode 100644 index 00000000..4a3b2514 --- /dev/null +++ b/monoio/tests/fs_unlink.rs @@ -0,0 +1,38 @@ +#![cfg(all(unix, feature = "unlinkat", feature = "mkdirat"))] + +use std::{io, path::PathBuf}; + +use monoio::fs::{self, File}; +use tempfile::tempdir; + +async fn create_file(path: &PathBuf) -> io::Result<()> { + let file = File::create(path).await?; + file.close().await?; + Ok(()) +} + +#[monoio::test_all] +async fn remove_file() { + let dir = tempdir().unwrap(); + let target = dir.path().join("test"); + + create_file(&target).await.unwrap(); + fs::remove_file(&target).await.unwrap(); + assert!(File::open(&target).await.is_err()); + assert!(fs::remove_file(&target).await.is_err()); +} + +#[monoio::test_all] +async fn remove_dir() { + let dir = tempdir().unwrap(); + let target = dir.path().join("test"); + + fs::create_dir(&target).await.unwrap(); + let path = target.join("file"); + create_file(&path).await.unwrap(); + assert!(fs::remove_dir(&target).await.is_err()); // dir is not empty + fs::remove_file(&path).await.unwrap(); + fs::remove_dir(&target).await.unwrap(); + assert!(create_file(&path).await.is_err()); // dir has been removed + assert!(fs::remove_dir(&target).await.is_err()); +}