Skip to content

Commit 882139c

Browse files
ChrisDentoncuviper
authored andcommitted
Windows: Use MoveFileEx by default in fs:rename
(cherry picked from commit 0dfe2ae)
1 parent b2af9a5 commit 882139c

File tree

1 file changed

+56
-125
lines changed
  • library/std/src/sys/pal/windows

1 file changed

+56
-125
lines changed

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

Lines changed: 56 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use super::api::{self, WinError};
22
use super::{IoResult, to_u16s};
3-
use crate::alloc::{alloc, handle_alloc_error};
3+
use crate::alloc::{Layout, alloc, dealloc, handle_alloc_error};
44
use crate::borrow::Cow;
55
use crate::ffi::{OsStr, OsString, c_void};
66
use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom};
7-
use crate::mem::{self, MaybeUninit};
7+
use crate::mem::{self, MaybeUninit, offset_of};
88
use crate::os::windows::io::{AsHandle, BorrowedHandle};
99
use crate::os::windows::prelude::*;
1010
use crate::path::{Path, PathBuf};
@@ -1252,141 +1252,72 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
12521252
let old = maybe_verbatim(old)?;
12531253
let new = maybe_verbatim(new)?;
12541254

1255-
let new_len_without_nul_in_bytes = (new.len() - 1).try_into().unwrap();
1256-
1257-
// The last field of FILE_RENAME_INFO, the file name, is unsized,
1258-
// and FILE_RENAME_INFO has two padding bytes.
1259-
// Therefore we need to make sure to not allocate less than
1260-
// size_of::<c::FILE_RENAME_INFO>() bytes, which would be the case with
1261-
// 0 or 1 character paths + a null byte.
1262-
let struct_size = mem::size_of::<c::FILE_RENAME_INFO>()
1263-
.max(mem::offset_of!(c::FILE_RENAME_INFO, FileName) + new.len() * mem::size_of::<u16>());
1264-
1265-
let struct_size: u32 = struct_size.try_into().unwrap();
1266-
1267-
let create_file = |extra_access, extra_flags| {
1268-
let handle = unsafe {
1269-
HandleOrInvalid::from_raw_handle(c::CreateFileW(
1270-
old.as_ptr(),
1271-
c::SYNCHRONIZE | c::DELETE | extra_access,
1272-
c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE,
1273-
ptr::null(),
1274-
c::OPEN_EXISTING,
1275-
c::FILE_ATTRIBUTE_NORMAL | c::FILE_FLAG_BACKUP_SEMANTICS | extra_flags,
1276-
ptr::null_mut(),
1277-
))
1278-
};
1279-
1280-
OwnedHandle::try_from(handle).map_err(|_| io::Error::last_os_error())
1281-
};
1282-
1283-
// The following code replicates `MoveFileEx`'s behavior as reverse-engineered from its disassembly.
1284-
// If `old` refers to a mount point, we move it instead of the target.
1285-
let handle = match create_file(c::FILE_READ_ATTRIBUTES, c::FILE_FLAG_OPEN_REPARSE_POINT) {
1286-
Ok(handle) => {
1287-
let mut file_attribute_tag_info: MaybeUninit<c::FILE_ATTRIBUTE_TAG_INFO> =
1288-
MaybeUninit::uninit();
1289-
1290-
let result = unsafe {
1291-
cvt(c::GetFileInformationByHandleEx(
1292-
handle.as_raw_handle(),
1293-
c::FileAttributeTagInfo,
1294-
file_attribute_tag_info.as_mut_ptr().cast(),
1295-
mem::size_of::<c::FILE_ATTRIBUTE_TAG_INFO>().try_into().unwrap(),
1296-
))
1255+
if unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), c::MOVEFILE_REPLACE_EXISTING) } == 0 {
1256+
let err = api::get_last_error();
1257+
// if `MoveFileExW` fails with ERROR_ACCESS_DENIED then try to move
1258+
// the file while ignoring the readonly attribute.
1259+
// This is accomplished by calling `SetFileInformationByHandle` with `FileRenameInfoEx`.
1260+
if err == WinError::ACCESS_DENIED {
1261+
let mut opts = OpenOptions::new();
1262+
opts.access_mode(c::DELETE);
1263+
opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS);
1264+
let Ok(f) = File::open_native(&old, &opts) else { return Err(err).io_result() };
1265+
1266+
// Calculate the layout of the `FILE_RENAME_INFO` we pass to `SetFileInformation`
1267+
// This is a dynamically sized struct so we need to get the position of the last field to calculate the actual size.
1268+
let Ok(new_len_without_nul_in_bytes): Result<u32, _> = ((new.len() - 1) * 2).try_into()
1269+
else {
1270+
return Err(err).io_result();
12971271
};
1298-
1299-
if let Err(err) = result {
1300-
if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _)
1301-
|| err.raw_os_error() == Some(c::ERROR_INVALID_FUNCTION as _)
1302-
{
1303-
// `GetFileInformationByHandleEx` documents that not all underlying drivers support all file information classes.
1304-
// Since we know we passed the correct arguments, this means the underlying driver didn't understand our request;
1305-
// `MoveFileEx` proceeds by reopening the file without inhibiting reparse point behavior.
1306-
None
1307-
} else {
1308-
Some(Err(err))
1309-
}
1310-
} else {
1311-
// SAFETY: The struct has been initialized by GetFileInformationByHandleEx
1312-
let file_attribute_tag_info = unsafe { file_attribute_tag_info.assume_init() };
1313-
let file_type = FileType::new(
1314-
file_attribute_tag_info.FileAttributes,
1315-
file_attribute_tag_info.ReparseTag,
1316-
);
1317-
1318-
if file_type.is_symlink() {
1319-
// The file is a mount point, junction point or symlink so
1320-
// don't reopen the file so that the link gets renamed.
1321-
Some(Ok(handle))
1322-
} else {
1323-
// Otherwise reopen the file without inhibiting reparse point behavior.
1324-
None
1272+
let offset: u32 = offset_of!(c::FILE_RENAME_INFO, FileName).try_into().unwrap();
1273+
let struct_size = offset + new_len_without_nul_in_bytes + 2;
1274+
let layout =
1275+
Layout::from_size_align(struct_size as usize, align_of::<c::FILE_RENAME_INFO>())
1276+
.unwrap();
1277+
1278+
// SAFETY: We allocate enough memory for a full FILE_RENAME_INFO struct and a filename.
1279+
let file_rename_info;
1280+
unsafe {
1281+
file_rename_info = alloc(layout).cast::<c::FILE_RENAME_INFO>();
1282+
if file_rename_info.is_null() {
1283+
handle_alloc_error(layout);
13251284
}
1326-
}
1327-
}
1328-
// The underlying driver may not support `FILE_FLAG_OPEN_REPARSE_POINT`: Retry without it.
1329-
Err(err) if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) => None,
1330-
Err(err) => Some(Err(err)),
1331-
}
1332-
.unwrap_or_else(|| create_file(0, 0))?;
1333-
1334-
let layout = core::alloc::Layout::from_size_align(
1335-
struct_size as _,
1336-
mem::align_of::<c::FILE_RENAME_INFO>(),
1337-
)
1338-
.unwrap();
1339-
1340-
let file_rename_info = unsafe { alloc(layout) } as *mut c::FILE_RENAME_INFO;
1341-
1342-
if file_rename_info.is_null() {
1343-
handle_alloc_error(layout);
1344-
}
13451285

1346-
// SAFETY: file_rename_info is a non-null pointer pointing to memory allocated by the global allocator.
1347-
let mut file_rename_info = unsafe { Box::from_raw(file_rename_info) };
1286+
(&raw mut (*file_rename_info).Anonymous).write(c::FILE_RENAME_INFO_0 {
1287+
Flags: c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS
1288+
| c::FILE_RENAME_FLAG_POSIX_SEMANTICS,
1289+
});
13481290

1349-
// SAFETY: We have allocated enough memory for a full FILE_RENAME_INFO struct and a filename.
1350-
unsafe {
1351-
(&raw mut (*file_rename_info).Anonymous).write(c::FILE_RENAME_INFO_0 {
1352-
Flags: c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS | c::FILE_RENAME_FLAG_POSIX_SEMANTICS,
1353-
});
1354-
1355-
(&raw mut (*file_rename_info).RootDirectory).write(ptr::null_mut());
1356-
(&raw mut (*file_rename_info).FileNameLength).write(new_len_without_nul_in_bytes);
1357-
1358-
new.as_ptr()
1359-
.copy_to_nonoverlapping((&raw mut (*file_rename_info).FileName) as *mut u16, new.len());
1360-
}
1361-
1362-
// We don't use `set_file_information_by_handle` here as `FILE_RENAME_INFO` is used for both `FileRenameInfo` and `FileRenameInfoEx`.
1363-
let result = unsafe {
1364-
cvt(c::SetFileInformationByHandle(
1365-
handle.as_raw_handle(),
1366-
c::FileRenameInfoEx,
1367-
(&raw const *file_rename_info).cast::<c_void>(),
1368-
struct_size,
1369-
))
1370-
};
1291+
(&raw mut (*file_rename_info).RootDirectory).write(ptr::null_mut());
1292+
// Don't include the NULL in the size
1293+
(&raw mut (*file_rename_info).FileNameLength).write(new_len_without_nul_in_bytes);
13711294

1372-
if let Err(err) = result {
1373-
if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) {
1374-
// FileRenameInfoEx and FILE_RENAME_FLAG_POSIX_SEMANTICS were added in Windows 10 1607; retry with FileRenameInfo.
1375-
file_rename_info.Anonymous.ReplaceIfExists = true;
1295+
new.as_ptr().copy_to_nonoverlapping(
1296+
(&raw mut (*file_rename_info).FileName).cast::<u16>(),
1297+
new.len(),
1298+
);
1299+
}
13761300

1377-
cvt(unsafe {
1301+
let result = unsafe {
13781302
c::SetFileInformationByHandle(
1379-
handle.as_raw_handle(),
1380-
c::FileRenameInfo,
1381-
(&raw const *file_rename_info).cast::<c_void>(),
1303+
f.as_raw_handle(),
1304+
c::FileRenameInfoEx,
1305+
file_rename_info.cast::<c_void>(),
13821306
struct_size,
13831307
)
1384-
})?;
1308+
};
1309+
unsafe { dealloc(file_rename_info.cast::<u8>(), layout) };
1310+
if result == 0 {
1311+
if api::get_last_error() == WinError::DIR_NOT_EMPTY {
1312+
return Err(WinError::DIR_NOT_EMPTY).io_result();
1313+
} else {
1314+
return Err(err).io_result();
1315+
}
1316+
}
13851317
} else {
1386-
return Err(err);
1318+
return Err(err).io_result();
13871319
}
13881320
}
1389-
13901321
Ok(())
13911322
}
13921323

0 commit comments

Comments
 (0)