From 3d03846b3cca809889629dd6c659ce0dd07963a5 Mon Sep 17 00:00:00 2001 From: lzzzt Date: Wed, 14 Aug 2024 07:34:05 +0000 Subject: [PATCH] feat: support `opcode::renameat` Signed-off-by: lzzzt --- monoio/Cargo.toml | 2 + monoio/src/driver/op.rs | 3 ++ monoio/src/driver/op/rename.rs | 55 ++++++++++++++++++++++++ monoio/src/fs/mod.rs | 33 ++++++++++++++ monoio/tests/fs_rename.rs | 78 ++++++++++++++++++++++++++++++++++ 5 files changed, 171 insertions(+) create mode 100644 monoio/src/driver/op/rename.rs create mode 100644 monoio/tests/fs_rename.rs diff --git a/monoio/Cargo.toml b/monoio/Cargo.toml index ab023036..37b7b5f3 100644 --- a/monoio/Cargo.toml +++ b/monoio/Cargo.toml @@ -73,6 +73,8 @@ splice = [] mkdirat = [] # unlinkat op(requires kernel 5.11+) unlinkat = [] +# renameat op(requires kernel 5.11+) +renameat = [] # 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 100e0c0c..d2daafff 100644 --- a/monoio/src/driver/op.rs +++ b/monoio/src/driver/op.rs @@ -28,6 +28,9 @@ mod mkdir; #[cfg(all(unix, feature = "unlinkat"))] mod unlink; +#[cfg(all(unix, feature = "renameat"))] +mod rename; + #[cfg(all(target_os = "linux", feature = "splice"))] mod splice; diff --git a/monoio/src/driver/op/rename.rs b/monoio/src/driver/op/rename.rs new file mode 100644 index 00000000..df9719a0 --- /dev/null +++ b/monoio/src/driver/op/rename.rs @@ -0,0 +1,55 @@ +use std::{ffi::CString, path::Path}; + +use super::{Op, OpAble}; +use crate::driver::util::cstr; + +pub(crate) struct Rename { + from: CString, + to: CString, +} + +impl Op { + pub(crate) fn rename(from: &Path, to: &Path) -> std::io::Result { + let from = cstr(from)?; + let to = cstr(to)?; + + Op::submit_with(Rename { from, to }) + } +} + +impl OpAble for Rename { + #[cfg(all(target_os = "linux", feature = "iouring"))] + fn uring_op(&mut self) -> io_uring::squeue::Entry { + use io_uring::{opcode::RenameAt, types}; + use libc::AT_FDCWD; + + RenameAt::new( + types::Fd(AT_FDCWD), + self.from.as_ptr(), + types::Fd(AT_FDCWD), + self.to.as_ptr(), + ) + .build() + } + + 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!(renameat( + libc::AT_FDCWD, + self.from.as_ptr(), + libc::AT_FDCWD, + self.to.as_ptr() + )) + } + + #[cfg(all(any(feature = "legacy", feature = "poll-io"), windows))] + fn legacy_call(&mut self) -> io::Result { + unimplemented!() + } +} diff --git a/monoio/src/fs/mod.rs b/monoio/src/fs/mod.rs index 442c0c4a..665bc2dc 100644 --- a/monoio/src/fs/mod.rs +++ b/monoio/src/fs/mod.rs @@ -133,3 +133,36 @@ pub async fn remove_dir>(path: P) -> io::Result<()> { Op::rmdir(path)?.await.meta.result?; Ok(()) } + +/// Rename a file or directory to a new name, replacing the original file if +/// `to` already exists. +/// +/// This will not work if the new name is on a different mount point. +/// +/// This is async version of [std::fs::rename]. +/// +/// # Errors +/// +/// This function will return an error in the following situations, but is not +/// limited to just these cases: +/// +/// * `from` does not exist. +/// * The user lacks permissions to view contents. +/// * `from` and `to` are on separate filesystems. +/// +/// # Examples +/// +/// ```no_run +/// use monoio::fs; +/// +/// #[monoio::main] +/// async fn main() -> std::io::Result<()> { +/// fs::rename("a.txt", "b.txt")?; // Rename a.txt to b.txt +/// Ok(()) +/// } +/// ``` +#[cfg(all(unix, feature = "renameat"))] +pub async fn rename, Q: AsRef>(from: P, to: Q) -> io::Result<()> { + Op::rename(from.as_ref(), to.as_ref())?.await.meta.result?; + Ok(()) +} diff --git a/monoio/tests/fs_rename.rs b/monoio/tests/fs_rename.rs new file mode 100644 index 00000000..fb98837d --- /dev/null +++ b/monoio/tests/fs_rename.rs @@ -0,0 +1,78 @@ +#![cfg(all(unix, feature = "renameat"))] + +use std::{fs::Permissions, os::unix::fs::PermissionsExt}; + +#[monoio::test_all] +async fn rename_file_in_the_same_directory() { + let temp_dir = tempfile::tempdir().unwrap(); + let file = tempfile::NamedTempFile::new_in(temp_dir.path()).unwrap(); + + let old_file_path = file.path(); + let new_file_path = temp_dir.path().join("test-file"); + + let result = monoio::fs::rename(old_file_path, &new_file_path).await; + assert!(result.is_ok()); + + assert!(new_file_path.exists()); + assert!(!old_file_path.exists()); +} + +#[monoio::test_all] +async fn rename_file_in_different_directory() { + let temp_dir1 = tempfile::tempdir().unwrap(); + let temp_dir2 = tempfile::tempdir().unwrap(); + let file = tempfile::NamedTempFile::new_in(temp_dir1.path()).unwrap(); + + let old_file_path = file.path(); + let new_file_path = temp_dir2.path().join("test-file"); + + let result = monoio::fs::rename(old_file_path, &new_file_path).await; + assert!(result.is_ok()); + + assert!(new_file_path.exists()); + assert!(!old_file_path.exists()); +} + +#[monoio::test_all] +async fn mv_file_in_different_directory() { + let temp_dir1 = tempfile::tempdir().unwrap(); + let temp_dir2 = tempfile::tempdir().unwrap(); + let file = tempfile::NamedTempFile::new_in(temp_dir1.path()).unwrap(); + + let old_file_path = file.path(); + let old_file_name = old_file_path.file_name().unwrap(); + let new_file_path = temp_dir2.path().join(old_file_name); + + let result = monoio::fs::rename(old_file_path, &new_file_path).await; + assert!(result.is_ok()); + + assert!(new_file_path.exists()); + assert!(!old_file_path.exists()); +} + +#[monoio::test_all] +async fn rename_inexist_file() { + let temp_dir = tempfile::tempdir().unwrap(); + + let old_file_path = temp_dir.path().join("inexist.txt"); + let new_file_path = temp_dir.path().join("renamed.txt"); + + let result = monoio::fs::rename(old_file_path, new_file_path).await; + + assert!(result.is_err()); +} + +#[monoio::test_all] +async fn rename_file_without_permission() { + let temp_dir = tempfile::tempdir().unwrap(); + let temp_file = tempfile::NamedTempFile::new_in(&temp_dir).unwrap(); + + std::fs::set_permissions(temp_dir.path(), Permissions::from_mode(0o0)).unwrap(); + + let old_file_path = temp_file.path(); + let new_file_path = temp_dir.path().join("test-file"); + + let result = monoio::fs::rename(old_file_path, &new_file_path).await; + + assert!(result.is_err()); +}