diff --git a/crates/host_env/src/os.rs b/crates/host_env/src/os.rs index b1d398faad..8d561e391b 100644 --- a/crates/host_env/src/os.rs +++ b/crates/host_env/src/os.rs @@ -10,6 +10,8 @@ use core::ffi::CStr; use core::str::Utf8Error; #[cfg(windows)] use core::time::Duration; +#[cfg(unix)] +use rustix::fd::AsFd; use std::{ env, ffi::{OsStr, OsString}, @@ -266,11 +268,36 @@ pub fn copy_file_range( rustix::fs::copy_file_range(src, offset_src, dst, offset_dst, count) } +#[cfg(not(unix))] +pub fn rename( + from: impl AsRef, + from_fd: Option>, + to: impl AsRef, + to_fd: Option>, +) -> io::Result<()> { + if from_fd.is_none() && to_fd.is_none() { + // TODO: Rust's implementation always overwrites the file so ensure consistency between + // operating systems. We need to use windows-sys directly to distinguish between + // os.rename and os.replace. + std::fs::rename(from, to) + } else { + core::hint::cold_path(); + Err(io::Error::other("renameat is not available on this platform")) + } +} + +#[cfg(unix)] pub fn rename( from: impl AsRef, + from_fd: Option>, to: impl AsRef, + to_fd: Option>, ) -> io::Result<()> { - std::fs::rename(from, to) + let from = from.as_ref(); + let from_fd = from_fd.as_ref().map_or(rustix::fs::CWD, AsFd::as_fd); + let to = to.as_ref(); + let to_fd = to_fd.as_ref().map_or(rustix::fs::CWD, AsFd::as_fd); + rustix::fs::renameat(from_fd, from, to_fd, to).map_err(Into::into) } #[cfg(windows)] diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index d6f6a181a9..20cfc535f3 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -189,6 +189,7 @@ pub(super) mod _os { const UTIME_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); pub(crate) const SYMLINK_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); pub(crate) const UNLINK_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); + const RENAME_DIR_FD: bool = cfg!(not(windows)); const RMDIR_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); const SCANDIR_FD: bool = cfg!(all(unix, not(target_os = "redox"))); @@ -1377,19 +1378,37 @@ pub(super) mod _os { FsPath::try_from_path_like(path, false, vm) } + #[derive(FromArgs)] + struct RenameArgs<'fd> { + #[pyarg(positional)] + src: PyObjectRef, + #[pyarg(positional)] + dst: PyObjectRef, + #[pyarg(any, default)] + src_dir_fd: OptionalArg>, + #[pyarg(any, default)] + dst_dir_fd: OptionalArg>, + } + #[pyfunction] #[pyfunction(name = "replace")] - fn rename(src: PyObjectRef, dst: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + fn rename(args: RenameArgs<'_>, vm: &VirtualMachine) -> PyResult<()> { let src = PathConverter::new() .function("rename") .argument("src") - .try_path(src, vm)?; + .try_path(args.src, vm)?; let dst = PathConverter::new() .function("rename") .argument("dst") - .try_path(dst, vm)?; + .try_path(args.dst, vm)?; - crate::host_env::os::rename(&src.path, &dst.path).map_err(|err| { + crate::host_env::os::rename( + &src, + args.src_dir_fd.into_option(), + &dst, + args.dst_dir_fd.into_option(), + ) + .map_err(|err| { let builder = err.to_os_error_builder(vm); let builder = builder.filename(src.filename(vm)); let builder = builder.filename2(dst.filename(vm)); @@ -1932,8 +1951,8 @@ pub(super) mod _os { SupportFunc::new("readlink", Some(false), None, Some(false)), SupportFunc::new("remove", Some(false), Some(UNLINK_DIR_FD), Some(false)), SupportFunc::new("unlink", Some(false), Some(UNLINK_DIR_FD), Some(false)), - SupportFunc::new("rename", Some(false), None, Some(false)), - SupportFunc::new("replace", Some(false), None, Some(false)), // TODO: Fix replace + SupportFunc::new("rename", Some(false), Some(RENAME_DIR_FD), Some(false)), + SupportFunc::new("replace", Some(false), Some(RENAME_DIR_FD), Some(false)), // TODO: Fix replace SupportFunc::new("rmdir", Some(false), Some(RMDIR_DIR_FD), Some(false)), SupportFunc::new("scandir", Some(SCANDIR_FD), Some(false), Some(false)), SupportFunc::new("stat", Some(true), Some(STAT_DIR_FD), Some(true)),