From 2d347959eed69c99c55039812e696bc2ab88b08d Mon Sep 17 00:00:00 2001 From: Lzzzt <101313294+Lzzzzzt@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:24:24 +0800 Subject: [PATCH] feat(op): :sparkles: add `MkDirAt` opcode in io-uring (#270) --- monoio/Cargo.toml | 2 + monoio/src/driver/op.rs | 3 + monoio/src/driver/op/mkdir.rs | 47 +++++++++ monoio/src/fs/create_dir.rs | 62 ++++++++++++ monoio/src/fs/dir_builder/mod.rs | 152 ++++++++++++++++++++++++++++++ monoio/src/fs/dir_builder/unix.rs | 23 +++++ monoio/src/fs/mod.rs | 10 ++ monoio/tests/fs_create_dir.rs | 137 +++++++++++++++++++++++++++ 8 files changed, 436 insertions(+) create mode 100644 monoio/src/driver/op/mkdir.rs create mode 100644 monoio/src/fs/create_dir.rs create mode 100644 monoio/src/fs/dir_builder/mod.rs create mode 100644 monoio/src/fs/dir_builder/unix.rs create mode 100644 monoio/tests/fs_create_dir.rs diff --git a/monoio/Cargo.toml b/monoio/Cargo.toml index 51437299..59b4043e 100644 --- a/monoio/Cargo.toml +++ b/monoio/Cargo.toml @@ -69,6 +69,8 @@ async-cancel = [] zero-copy = [] # splice op(requires kernel 5.7+) splice = [] +# mkdirat2 op(requires kernel 5.15+) +mkdirat = [] # 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 6576933f..b646225d 100644 --- a/monoio/src/driver/op.rs +++ b/monoio/src/driver/op.rs @@ -22,6 +22,9 @@ mod write; #[cfg(unix)] mod statx; +#[cfg(all(unix, feature = "mkdirat"))] +mod mkdir; + #[cfg(all(target_os = "linux", feature = "splice"))] mod splice; diff --git a/monoio/src/driver/op/mkdir.rs b/monoio/src/driver/op/mkdir.rs new file mode 100644 index 00000000..be418c51 --- /dev/null +++ b/monoio/src/driver/op/mkdir.rs @@ -0,0 +1,47 @@ +use std::{ffi::CString, path::Path}; + +use libc::mode_t; + +use super::{Op, OpAble}; +use crate::driver::util::cstr; + +pub(crate) struct MkDir { + path: CString, + mode: mode_t, +} + +impl Op { + pub(crate) fn mkdir>(path: P, mode: mode_t) -> std::io::Result> { + let path = cstr(path.as_ref())?; + Op::submit_with(MkDir { path, mode }) + } +} + +impl OpAble for MkDir { + #[cfg(all(target_os = "linux", feature = "iouring"))] + fn uring_op(&mut self) -> io_uring::squeue::Entry { + use io_uring::{opcode, types}; + + opcode::MkDirAt::new(types::Fd(libc::AT_FDCWD), self.path.as_ptr()) + .mode(self.mode) + .build() + } + + #[cfg(any(feature = "legacy", feature = "poll-io"))] + #[inline] + fn legacy_interest(&self) -> Option<(crate::driver::ready::Direction, usize)> { + None + } + + #[cfg(all(any(feature = "legacy", feature = "poll-io"), unix))] + fn legacy_call(&mut self) -> std::io::Result { + use crate::syscall_u32; + + syscall_u32!(mkdirat(libc::AT_FDCWD, self.path.as_ptr(), self.mode)) + } + + #[cfg(all(any(feature = "legacy", feature = "poll-io"), windows))] + fn legacy_call(&mut self) -> io::Result { + unimplemented!() + } +} diff --git a/monoio/src/fs/create_dir.rs b/monoio/src/fs/create_dir.rs new file mode 100644 index 00000000..9fc59494 --- /dev/null +++ b/monoio/src/fs/create_dir.rs @@ -0,0 +1,62 @@ +use std::{io, path::Path}; + +use super::DirBuilder; + +/// Create a new directory at the target path +/// +/// # Note +/// +/// - This function require the provided path's parent are all existing. +/// - To create a directory and all its missing parents at the same time, use the +/// [`create_dir_all`] function. +/// - Currently this function is supported on unix, windows is unimplement. +/// +/// # Errors +/// +/// This function will return an error in the following situations, but is not +/// limited to just these cases: +/// +/// * User lacks permissions to create directory at `path`. +/// * A parent of the given path doesn't exist. (To create a directory and all its missing parents +/// at the same time, use the [`create_dir_all`] function.) +/// * `path` already exists. +/// +/// # Examples +/// +/// ```no_run +/// use monoio::fs; +/// +/// #[monoio::main] +/// async fn main() -> std::io::Result<()> { +/// fs::create_dir("/some/dir").await?; +/// Ok(()) +/// } +/// ``` +pub async fn create_dir>(path: P) -> io::Result<()> { + DirBuilder::new().create(path).await +} + +/// Recursively create a directory and all of its missing components +/// +/// # Note +/// +/// - Currently this function is supported on unix, windows is unimplement. +/// +/// # Errors +/// +/// Same with [`create_dir`] +/// +/// # Examples +/// +/// ```no_run +/// use monoio::fs; +/// +/// #[monoio::main] +/// async fn main() -> std::io::Result<()> { +/// fs::create_dir_all("/some/dir").await?; +/// Ok(()) +/// } +/// ``` +pub async fn create_dir_all>(path: P) -> io::Result<()> { + DirBuilder::new().recursive(true).create(path).await +} diff --git a/monoio/src/fs/dir_builder/mod.rs b/monoio/src/fs/dir_builder/mod.rs new file mode 100644 index 00000000..b9efd11b --- /dev/null +++ b/monoio/src/fs/dir_builder/mod.rs @@ -0,0 +1,152 @@ +mod unix; + +use std::{io, os::unix::fs::DirBuilderExt, path::Path}; + +#[cfg(unix)] +use unix as sys; + +/// A builder used to create directories in various manners. +/// +/// This builder also supports platform-specific options. +pub struct DirBuilder { + recursive: bool, + inner: sys::BuilderInner, +} + +impl DirBuilder { + /// Creates a new set of options with default mode/security settings for all + /// platforms and also non-recursive. + /// + /// This an async version of [`std::fs::DirBuilder::new`] + /// + /// # Examples + /// + /// ```no_run + /// use monoio::fs::DirBuilder; + /// + /// let builder = DirBuilder::new(); + /// ``` + pub fn new() -> Self { + Self { + recursive: false, + inner: sys::BuilderInner::new(), + } + } + + /// Indicates that directories should be created recursively, creating all + /// parent directories. Parents that do not exist are created with the same + /// security and permissions settings. + /// + /// This option defaults to `false`. + /// + /// This an async version of [`std::fs::DirBuilder::recursive`] + /// + /// # Examples + /// + /// ```no_run + /// use monoio::fs::DirBuilder; + /// + /// let mut builder = DirBuilder::new(); + /// builder.recursive(true); + /// ``` + pub fn recursive(&mut self, recursive: bool) -> &mut Self { + self.recursive = recursive; + self + } + + /// Creates the specified directory with the options configured in this + /// builder. + /// + /// It is considered an error if the directory already exists unless + /// recursive mode is enabled. + /// + /// This is async version of [`std::fs::DirBuilder::create`] and use io-uring + /// in support platform. + /// + /// # Errors + /// + /// An error will be returned under the following circumstances: + /// + /// * Path already points to an existing file. + /// * Path already points to an existing directory and the mode is non-recursive. + /// * The calling process doesn't have permissions to create the directory or its missing + /// parents. + /// * Other I/O error occurred. + /// + /// # Examples + /// + /// ```no_run + /// use monoio::fs::DirBuilder; + /// + /// #[monoio::main] + /// async fn main() -> std::io::Result<()> { + /// DirBuilder::new() + /// .recursive(true) + /// .create("/some/dir") + /// .await?; + /// + /// Ok(()) + /// } + /// ``` + pub async fn create>(&self, path: P) -> io::Result<()> { + if self.recursive { + self.create_dir_all(path.as_ref()).await + } else { + self.inner.mkdir(path.as_ref()).await + } + } + + async fn create_dir_all(&self, path: &Path) -> io::Result<()> { + if path == Path::new("") { + return Ok(()); + } + + let mut inexist_path = path; + let mut need_create = vec![]; + + while match self.inner.mkdir(inexist_path).await { + Ok(()) => false, + Err(ref e) if e.kind() == io::ErrorKind::NotFound => true, + Err(_) if is_dir(inexist_path).await => false, + Err(e) => return Err(e), + } { + match inexist_path.parent() { + Some(p) => { + need_create.push(inexist_path); + inexist_path = p; + } + None => { + return Err(io::Error::new( + io::ErrorKind::Other, + "failed to create whole tree", + )) + } + } + } + + for p in need_create.into_iter().rev() { + self.inner.mkdir(p).await?; + } + + Ok(()) + } +} + +impl Default for DirBuilder { + fn default() -> Self { + Self::new() + } +} + +impl DirBuilderExt for DirBuilder { + fn mode(&mut self, mode: u32) -> &mut Self { + self.inner.set_mode(mode); + self + } +} + +// currently, will use the std version of metadata, will change to use the io-uring version +// when the statx is merge +async fn is_dir(path: &Path) -> bool { + std::fs::metadata(path).is_ok_and(|metadata| metadata.is_dir()) +} diff --git a/monoio/src/fs/dir_builder/unix.rs b/monoio/src/fs/dir_builder/unix.rs new file mode 100644 index 00000000..ec5745a3 --- /dev/null +++ b/monoio/src/fs/dir_builder/unix.rs @@ -0,0 +1,23 @@ +use std::path::Path; + +use libc::mode_t; + +use crate::driver::op::Op; + +pub(super) struct BuilderInner { + mode: libc::mode_t, +} + +impl BuilderInner { + pub(super) fn new() -> Self { + Self { mode: 0o777 } + } + + pub(super) async fn mkdir(&self, path: &Path) -> std::io::Result<()> { + Op::mkdir(path, self.mode)?.await.meta.result.map(|_| ()) + } + + pub(super) fn set_mode(&mut self, mode: u32) { + self.mode = mode as mode_t; + } +} diff --git a/monoio/src/fs/mod.rs b/monoio/src/fs/mod.rs index 0147bdc0..b8a47009 100644 --- a/monoio/src/fs/mod.rs +++ b/monoio/src/fs/mod.rs @@ -5,6 +5,16 @@ use std::{io, path::Path}; pub use file::File; +#[cfg(all(unix, feature = "mkdirat"))] +mod dir_builder; +#[cfg(all(unix, feature = "mkdirat"))] +pub use dir_builder::DirBuilder; + +#[cfg(all(unix, feature = "mkdirat"))] +mod create_dir; +#[cfg(all(unix, feature = "mkdirat"))] +pub use create_dir::*; + mod open_options; pub use open_options::OpenOptions; diff --git a/monoio/tests/fs_create_dir.rs b/monoio/tests/fs_create_dir.rs new file mode 100644 index 00000000..9b543e13 --- /dev/null +++ b/monoio/tests/fs_create_dir.rs @@ -0,0 +1,137 @@ +#![cfg(all(unix, feature = "mkdirat"))] + +use monoio::fs; +use tempfile::tempdir; + +#[monoio::test_all] +async fn create_single_dirctory() { + let temp_dir = tempdir().unwrap(); + let path = temp_dir.path().join("test"); + + fs::create_dir(&path).await.unwrap(); + + assert!(path.exists()); + + std::fs::remove_dir(&path).unwrap(); + + assert!(!path.exists()); + + fs::create_dir_all(&path).await.unwrap(); + + assert!(path.exists()); +} + +#[monoio::test_all] +async fn create_nested_directories() { + let temp_dir = tempdir().unwrap(); + let path = temp_dir.path().join("test/foo/bar"); + + fs::create_dir_all(&path).await.unwrap(); + + assert!(path.exists()); +} + +#[monoio::test_all] +async fn create_existing_directory() { + let temp_dir = tempdir().unwrap(); + + fs::create_dir_all(temp_dir.path()).await.unwrap(); +} + +#[monoio::test_all] +async fn create_invalid_path() { + let temp_dir = tempdir().unwrap(); + + let mut path = temp_dir.path().display().to_string(); + path += "invalid_dir/\0"; + + let res = fs::create_dir_all(path).await; + + assert!(res.is_err()); +} + +#[monoio::test_all] +async fn create_directory_with_special_characters() { + let temp_dir = tempdir().unwrap(); + + let path = temp_dir.path().join("foo/😀"); + + fs::create_dir_all(&path).await.unwrap(); + + assert!(path.exists()); +} + +#[monoio::test_all] +async fn create_directory_where_file_exists() { + let temp_file = tempfile::NamedTempFile::new().unwrap(); + fs::write(temp_file.path(), "foo bar").await.0.unwrap(); + + let res = fs::create_dir(temp_file.path()).await; + + assert!(res.is_err()); + + let res = fs::create_dir_all(temp_file.path()).await; + + assert!(res.is_err()); +} + +#[monoio::test_all] +async fn create_directory_with_symlink() { + let temp_dir = tempdir().unwrap(); + + let target = temp_dir.path().join("foo"); + + fs::create_dir_all(&target).await.unwrap(); + + let link = temp_dir.path().join("bar"); + let to_create = link.join("nested"); + + std::os::unix::fs::symlink(&target, &link).unwrap(); + + fs::create_dir_all(&to_create).await.unwrap(); + + assert!(to_create.exists()); + assert!(target.join("nested").exists()); +} + +#[monoio::test_all] +async fn create_very_long_path() { + let temp_dir = tempdir().unwrap(); + + let mut path = temp_dir.path().to_path_buf(); + for _ in 0..255 { + path.push("a/"); + } + + fs::create_dir_all(&path).await.unwrap(); + + assert!(path.exists()); +} + +#[monoio::test_all] +async fn create_directory_with_permission_issue() { + use std::os::unix::fs::PermissionsExt; + + let temp_dir = tempdir().unwrap(); + + let target = temp_dir.path().join("foo"); + + fs::create_dir_all(&target).await.unwrap(); + + // use `std`'s due to the `monoio`'s `set_permissions` is not implement. + let mut perm = std::fs::metadata(&target).unwrap().permissions(); + perm.set_mode(0o400); + + std::fs::set_permissions(&target, perm.clone()).unwrap(); + + let path = target.join("bar"); + let res = fs::create_dir_all(&path).await; + assert!(res.is_err()); + + perm.set_mode(0o700); + std::fs::set_permissions(&target, perm).unwrap(); + + fs::create_dir_all(&path).await.unwrap(); + + assert!(path.exists()); +}