Skip to content

Commit df033a1

Browse files
committed
Win: Fix std::fs::rename failing on Windows Server by attempting the non-atomic rename first
We previously attempted the atomic rename first, and fell back to trying a non-atomic rename if it wasn't supported. However, this does not work reliably on Windows Server - NTFS partitions created in Windows Server 2016 apparently need to be upgraded, and it outright fails with `ERROR_NOT_SUPPORTED` on ReFS on Windows Server 2022. This commit switches the two calls so that FileRenameInfo is tried first, and an atomic rename via FileRenameInfoEx is only attempted if the non-atomic rename fails.
1 parent f9e0239 commit df033a1

File tree

1 file changed

+28
-16
lines changed
  • library/std/src/sys/pal/windows

1 file changed

+28
-16
lines changed

library/std/src/sys/pal/windows/fs.rs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,9 +1338,8 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
13381338

13391339
// SAFETY: We have allocated enough memory for a full FILE_RENAME_INFO struct and a filename.
13401340
unsafe {
1341-
(&raw mut (*file_rename_info).Anonymous).write(c::FILE_RENAME_INFO_0 {
1342-
Flags: c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS | c::FILE_RENAME_FLAG_POSIX_SEMANTICS,
1343-
});
1341+
(&raw mut (*file_rename_info).Anonymous)
1342+
.write(c::FILE_RENAME_INFO_0 { ReplaceIfExists: true });
13441343

13451344
(&raw mut (*file_rename_info).RootDirectory).write(ptr::null_mut());
13461345
(&raw mut (*file_rename_info).FileNameLength).write(new_len_without_nul_in_bytes);
@@ -1353,31 +1352,44 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
13531352
let result = unsafe {
13541353
cvt(c::SetFileInformationByHandle(
13551354
handle.as_raw_handle(),
1356-
c::FileRenameInfoEx,
1355+
c::FileRenameInfo,
13571356
(&raw const *file_rename_info).cast::<c_void>(),
13581357
struct_size,
13591358
))
13601359
};
13611360

1362-
if let Err(err) = result {
1363-
if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) {
1364-
// FileRenameInfoEx and FILE_RENAME_FLAG_POSIX_SEMANTICS were added in Windows 10 1607; retry with FileRenameInfo.
1365-
file_rename_info.Anonymous.ReplaceIfExists = true;
1366-
1367-
cvt(unsafe {
1361+
match result {
1362+
Ok(_) => Ok(()),
1363+
Err(err) if err.raw_os_error() == Some(c::ERROR_ACCESS_DENIED as _) => {
1364+
// If a process already has an open file handle to the target file, we need POSIX rename semantics to succeed
1365+
// (open file handles still refer to the old file, but the path will refer to the new file).
1366+
// As this is only supported starting with Windows 10 1607 and does not work out of the box on some versions
1367+
// of Windows Server, we only try this if the initial rename failed.
1368+
file_rename_info.Anonymous.Flags =
1369+
c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS | c::FILE_RENAME_FLAG_POSIX_SEMANTICS;
1370+
1371+
let result = cvt(unsafe {
13681372
c::SetFileInformationByHandle(
13691373
handle.as_raw_handle(),
1370-
c::FileRenameInfo,
1374+
c::FileRenameInfoEx,
13711375
(&raw const *file_rename_info).cast::<c_void>(),
13721376
struct_size,
13731377
)
1374-
})?;
1375-
} else {
1376-
return Err(err);
1378+
});
1379+
1380+
match result {
1381+
Ok(_) => Ok(()),
1382+
Err(err) if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) => {
1383+
// FileRenameInfoEx was only tried because the initial rename failed with ERROR_ACCESS_DENIED.
1384+
// If FileRenameInfoEx isn't supported, return the original error as ERROR_INVALID_PARAMETER
1385+
// doesn't convey useful error information.
1386+
Err(io::Error::from_raw_os_error(c::ERROR_ACCESS_DENIED as _))
1387+
}
1388+
Err(err) => Err(err),
1389+
}
13771390
}
1391+
Err(err) => Err(err),
13781392
}
1379-
1380-
Ok(())
13811393
}
13821394

13831395
pub fn rmdir(p: &Path) -> io::Result<()> {

0 commit comments

Comments
 (0)