Skip to content

Commit 073fa98

Browse files
host_env: renameat support for os.rename
Python's `os.rename` supports renaming a path relative to a directory descriptor - essentially, `renameat`. The syscall and its Rustix wrapper does all of the work, so this patch mainly updates the Python signature to forward to the implementation. As of this patch, `os.rename` is still incorrect for Windows because it always replaces the destination file. I added a small note for future contributors (or myself, since I might tackle it) as a reminder of what's broken.
1 parent dbfd69e commit 073fa98

2 files changed

Lines changed: 53 additions & 7 deletions

File tree

crates/host_env/src/os.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use core::ffi::CStr;
1010
use core::str::Utf8Error;
1111
#[cfg(windows)]
1212
use core::time::Duration;
13+
#[cfg(unix)]
14+
use rustix::fd::AsFd;
1315
use std::{
1416
env,
1517
ffi::{OsStr, OsString},
@@ -266,11 +268,36 @@ pub fn copy_file_range(
266268
rustix::fs::copy_file_range(src, offset_src, dst, offset_dst, count)
267269
}
268270

271+
#[cfg(not(unix))]
272+
pub fn rename(
273+
from: impl AsRef<std::path::Path>,
274+
from_fd: Option<crt_fd::Borrowed<'_>>,
275+
to: impl AsRef<std::path::Path>,
276+
to_fd: Option<crt_fd::Borrowed<'_>>,
277+
) -> io::Result<()> {
278+
if from_fd.is_none() && to_fd.is_none() {
279+
// TODO: Rust's implementation always overwrites the file so ensure consistency between
280+
// operating systems. We need to use windows-sys directly to distinguish between
281+
// os.rename and os.replace.
282+
std::fs::rename(from, to)
283+
} else {
284+
core::hint::cold_path();
285+
Err(io::Error::other("renameat is not available on this platform"))
286+
}
287+
}
288+
289+
#[cfg(unix)]
269290
pub fn rename(
270291
from: impl AsRef<std::path::Path>,
292+
from_fd: Option<crt_fd::Borrowed<'_>>,
271293
to: impl AsRef<std::path::Path>,
294+
to_fd: Option<crt_fd::Borrowed<'_>>,
272295
) -> io::Result<()> {
273-
std::fs::rename(from, to)
296+
let from = from.as_ref();
297+
let from_fd = from_fd.as_ref().map_or(rustix::fs::CWD, AsFd::as_fd);
298+
let to = to.as_ref();
299+
let to_fd = to_fd.as_ref().map_or(rustix::fs::CWD, AsFd::as_fd);
300+
rustix::fs::renameat(from_fd, from, to_fd, to).map_err(Into::into)
274301
}
275302

276303
#[cfg(windows)]

crates/vm/src/stdlib/os.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ pub(super) mod _os {
189189
const UTIME_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox")));
190190
pub(crate) const SYMLINK_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox")));
191191
pub(crate) const UNLINK_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox")));
192+
const RENAME_DIR_FD: bool = cfg!(not(windows));
192193
const RMDIR_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox")));
193194
const SCANDIR_FD: bool = cfg!(all(unix, not(target_os = "redox")));
194195

@@ -1377,19 +1378,37 @@ pub(super) mod _os {
13771378
FsPath::try_from_path_like(path, false, vm)
13781379
}
13791380

1381+
#[derive(FromArgs)]
1382+
struct RenameArgs<'fd> {
1383+
#[pyarg(positional)]
1384+
src: PyObjectRef,
1385+
#[pyarg(positional)]
1386+
dst: PyObjectRef,
1387+
#[pyarg(any, default)]
1388+
src_dir_fd: OptionalArg<crt_fd::Borrowed<'fd>>,
1389+
#[pyarg(any, default)]
1390+
dst_dir_fd: OptionalArg<crt_fd::Borrowed<'fd>>,
1391+
}
1392+
13801393
#[pyfunction]
13811394
#[pyfunction(name = "replace")]
1382-
fn rename(src: PyObjectRef, dst: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
1395+
fn rename(args: RenameArgs<'_>, vm: &VirtualMachine) -> PyResult<()> {
13831396
let src = PathConverter::new()
13841397
.function("rename")
13851398
.argument("src")
1386-
.try_path(src, vm)?;
1399+
.try_path(args.src, vm)?;
13871400
let dst = PathConverter::new()
13881401
.function("rename")
13891402
.argument("dst")
1390-
.try_path(dst, vm)?;
1403+
.try_path(args.dst, vm)?;
13911404

1392-
crate::host_env::os::rename(&src.path, &dst.path).map_err(|err| {
1405+
crate::host_env::os::rename(
1406+
&src,
1407+
args.src_dir_fd.into_option(),
1408+
&dst,
1409+
args.dst_dir_fd.into_option(),
1410+
)
1411+
.map_err(|err| {
13931412
let builder = err.to_os_error_builder(vm);
13941413
let builder = builder.filename(src.filename(vm));
13951414
let builder = builder.filename2(dst.filename(vm));
@@ -1932,8 +1951,8 @@ pub(super) mod _os {
19321951
SupportFunc::new("readlink", Some(false), None, Some(false)),
19331952
SupportFunc::new("remove", Some(false), Some(UNLINK_DIR_FD), Some(false)),
19341953
SupportFunc::new("unlink", Some(false), Some(UNLINK_DIR_FD), Some(false)),
1935-
SupportFunc::new("rename", Some(false), None, Some(false)),
1936-
SupportFunc::new("replace", Some(false), None, Some(false)), // TODO: Fix replace
1954+
SupportFunc::new("rename", Some(false), Some(RENAME_DIR_FD), Some(false)),
1955+
SupportFunc::new("replace", Some(false), Some(RENAME_DIR_FD), Some(false)), // TODO: Fix replace
19371956
SupportFunc::new("rmdir", Some(false), Some(RMDIR_DIR_FD), Some(false)),
19381957
SupportFunc::new("scandir", Some(SCANDIR_FD), Some(false), Some(false)),
19391958
SupportFunc::new("stat", Some(true), Some(STAT_DIR_FD), Some(true)),

0 commit comments

Comments
 (0)