From 9e40cbe495a9024529d58e5d69ebf2e63db32e15 Mon Sep 17 00:00:00 2001 From: Joe Hillenbrand Date: Wed, 19 Jul 2023 10:03:38 -0700 Subject: [PATCH] fix(fs): support rename across filesystems Rather than expect the user to know that they need to handle a special case when renaming across filesystems, we can just handle it for them. Fixes #3092 --- ext/fs/std_fs.rs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/ext/fs/std_fs.rs b/ext/fs/std_fs.rs index b2923abb15..41a04c2970 100644 --- a/ext/fs/std_fs.rs +++ b/ext/fs/std_fs.rs @@ -179,16 +179,14 @@ impl FileSystem for RealFs { } fn rename_sync(&self, oldpath: &Path, newpath: &Path) -> FsResult<()> { - fs::rename(oldpath, newpath).map_err(Into::into) + rename(oldpath, newpath) } async fn rename_async( &self, oldpath: PathBuf, newpath: PathBuf, ) -> FsResult<()> { - spawn_blocking(move || fs::rename(oldpath, newpath)) - .await? - .map_err(Into::into) + spawn_blocking(move || rename(&oldpath, &newpath)).await? } fn link_sync(&self, oldpath: &Path, newpath: &Path) -> FsResult<()> { @@ -598,6 +596,29 @@ fn read_dir(path: &Path) -> FsResult> { Ok(entries) } +fn rename(oldpath: &Path, newpath: &Path) -> FsResult<()> { + match fs::rename(oldpath, newpath) { + Ok(_) => Ok(()), + Err(err) => { + if err.raw_os_error() == Some(libc::EXDEV) { + // EXDEV: rename fails because oldpath and newpath are not on the same + // mounted filesystem. We need to do a copy and remove. + // + // This check can be replaced with the following once + // https://github.com/rust-lang/rust/issues/86442 stabilizes: + // + // if err.kind() == io::ErrorKind::CrossDeviceLink + // + copy_file(oldpath, newpath)?; + fs::remove_file(oldpath)?; + Ok(()) + } else { + Err(err.into()) + } + } + } +} + #[cfg(not(windows))] fn symlink( oldpath: &Path,