Skip to content

Commit

Permalink
feat: support opcode::renameat
Browse files Browse the repository at this point in the history
Signed-off-by: lzzzt <[email protected]>
  • Loading branch information
Lzzzzzt committed Aug 14, 2024
1 parent 504a424 commit 3d03846
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 0 deletions.
2 changes: 2 additions & 0 deletions monoio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions monoio/src/driver/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
55 changes: 55 additions & 0 deletions monoio/src/driver/op/rename.rs
Original file line number Diff line number Diff line change
@@ -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<Rename> {
pub(crate) fn rename(from: &Path, to: &Path) -> std::io::Result<Self> {
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<u32> {
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<u32> {
unimplemented!()
}
}
33 changes: 33 additions & 0 deletions monoio/src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,36 @@ pub async fn remove_dir<P: AsRef<Path>>(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<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
Op::rename(from.as_ref(), to.as_ref())?.await.meta.result?;
Ok(())
}
78 changes: 78 additions & 0 deletions monoio/tests/fs_rename.rs
Original file line number Diff line number Diff line change
@@ -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());
}

0 comments on commit 3d03846

Please sign in to comment.