Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Win: Fix std::fs::rename failing on Windows Server by attempting the non-atomic rename first #138133

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 28 additions & 16 deletions library/std/src/sys/pal/windows/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1338,9 +1338,8 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> {

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

(&raw mut (*file_rename_info).RootDirectory).write(ptr::null_mut());
(&raw mut (*file_rename_info).FileNameLength).write(new_len_without_nul_in_bytes);
Expand All @@ -1353,31 +1352,44 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
let result = unsafe {
cvt(c::SetFileInformationByHandle(
handle.as_raw_handle(),
c::FileRenameInfoEx,
c::FileRenameInfo,
(&raw const *file_rename_info).cast::<c_void>(),
struct_size,
))
};

if let Err(err) = result {
if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) {
// FileRenameInfoEx and FILE_RENAME_FLAG_POSIX_SEMANTICS were added in Windows 10 1607; retry with FileRenameInfo.
file_rename_info.Anonymous.ReplaceIfExists = true;

cvt(unsafe {
match result {
Ok(_) => Ok(()),
Err(err) if err.raw_os_error() == Some(c::ERROR_ACCESS_DENIED as _) => {
// If a process already has an open file handle to the target file, we need POSIX rename semantics to succeed
// (open file handles still refer to the old file, but the path will refer to the new file).
// As this is only supported starting with Windows 10 1607 and does not work out of the box on some versions
// of Windows Server, we only try this if the initial rename failed.
file_rename_info.Anonymous.Flags =
c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS | c::FILE_RENAME_FLAG_POSIX_SEMANTICS;

let result = cvt(unsafe {
c::SetFileInformationByHandle(
handle.as_raw_handle(),
c::FileRenameInfo,
c::FileRenameInfoEx,
(&raw const *file_rename_info).cast::<c_void>(),
struct_size,
)
})?;
} else {
return Err(err);
});

match result {
Ok(_) => Ok(()),
Err(err) if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) => {
// FileRenameInfoEx was only tried because the initial rename failed with ERROR_ACCESS_DENIED.
// If FileRenameInfoEx isn't supported, return the original error as ERROR_INVALID_PARAMETER
// doesn't convey useful error information.
Err(io::Error::from_raw_os_error(c::ERROR_ACCESS_DENIED as _))
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}

Ok(())
}

pub fn rmdir(p: &Path) -> io::Result<()> {
Expand Down
Loading