|
1 | 1 | use super::api::{self, WinError};
|
2 | 2 | use super::{IoResult, to_u16s};
|
3 |
| -use crate::alloc::{alloc, handle_alloc_error}; |
| 3 | +use crate::alloc::{Layout, alloc, dealloc}; |
4 | 4 | use crate::borrow::Cow;
|
5 | 5 | use crate::ffi::{OsStr, OsString, c_void};
|
6 | 6 | use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom};
|
7 |
| -use crate::mem::{self, MaybeUninit}; |
| 7 | +use crate::mem::{self, MaybeUninit, offset_of}; |
8 | 8 | use crate::os::windows::io::{AsHandle, BorrowedHandle};
|
9 | 9 | use crate::os::windows::prelude::*;
|
10 | 10 | use crate::path::{Path, PathBuf};
|
@@ -296,6 +296,10 @@ impl OpenOptions {
|
296 | 296 | impl File {
|
297 | 297 | pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
|
298 | 298 | let path = maybe_verbatim(path)?;
|
| 299 | + Self::open_native(&path, opts) |
| 300 | + } |
| 301 | + |
| 302 | + fn open_native(path: &[u16], opts: &OpenOptions) -> io::Result<File> { |
299 | 303 | let creation = opts.get_creation_mode()?;
|
300 | 304 | let handle = unsafe {
|
301 | 305 | c::CreateFileW(
|
@@ -1234,141 +1238,72 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
|
1234 | 1238 | let old = maybe_verbatim(old)?;
|
1235 | 1239 | let new = maybe_verbatim(new)?;
|
1236 | 1240 |
|
1237 |
| - let new_len_without_nul_in_bytes = (new.len() - 1).try_into().unwrap(); |
1238 |
| - |
1239 |
| - // The last field of FILE_RENAME_INFO, the file name, is unsized, |
1240 |
| - // and FILE_RENAME_INFO has two padding bytes. |
1241 |
| - // Therefore we need to make sure to not allocate less than |
1242 |
| - // size_of::<c::FILE_RENAME_INFO>() bytes, which would be the case with |
1243 |
| - // 0 or 1 character paths + a null byte. |
1244 |
| - let struct_size = mem::size_of::<c::FILE_RENAME_INFO>() |
1245 |
| - .max(mem::offset_of!(c::FILE_RENAME_INFO, FileName) + new.len() * mem::size_of::<u16>()); |
1246 |
| - |
1247 |
| - let struct_size: u32 = struct_size.try_into().unwrap(); |
1248 |
| - |
1249 |
| - let create_file = |extra_access, extra_flags| { |
1250 |
| - let handle = unsafe { |
1251 |
| - HandleOrInvalid::from_raw_handle(c::CreateFileW( |
1252 |
| - old.as_ptr(), |
1253 |
| - c::SYNCHRONIZE | c::DELETE | extra_access, |
1254 |
| - c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE, |
1255 |
| - ptr::null(), |
1256 |
| - c::OPEN_EXISTING, |
1257 |
| - c::FILE_ATTRIBUTE_NORMAL | c::FILE_FLAG_BACKUP_SEMANTICS | extra_flags, |
1258 |
| - ptr::null_mut(), |
1259 |
| - )) |
1260 |
| - }; |
1261 |
| - |
1262 |
| - OwnedHandle::try_from(handle).map_err(|_| io::Error::last_os_error()) |
1263 |
| - }; |
1264 |
| - |
1265 |
| - // The following code replicates `MoveFileEx`'s behavior as reverse-engineered from its disassembly. |
1266 |
| - // If `old` refers to a mount point, we move it instead of the target. |
1267 |
| - let handle = match create_file(c::FILE_READ_ATTRIBUTES, c::FILE_FLAG_OPEN_REPARSE_POINT) { |
1268 |
| - Ok(handle) => { |
1269 |
| - let mut file_attribute_tag_info: MaybeUninit<c::FILE_ATTRIBUTE_TAG_INFO> = |
1270 |
| - MaybeUninit::uninit(); |
1271 |
| - |
1272 |
| - let result = unsafe { |
1273 |
| - cvt(c::GetFileInformationByHandleEx( |
1274 |
| - handle.as_raw_handle(), |
1275 |
| - c::FileAttributeTagInfo, |
1276 |
| - file_attribute_tag_info.as_mut_ptr().cast(), |
1277 |
| - mem::size_of::<c::FILE_ATTRIBUTE_TAG_INFO>().try_into().unwrap(), |
1278 |
| - )) |
| 1241 | + if unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), c::MOVEFILE_REPLACE_EXISTING) } == 0 { |
| 1242 | + let err = api::get_last_error(); |
| 1243 | + // if `MoveFileExW` fails with ERROR_ACCESS_DENIED then try to move |
| 1244 | + // the file while ignoring the readonly attribute. |
| 1245 | + // This is accomplished by calling `SetFileInformationByHandle` with `FileRenameInfoEx`. |
| 1246 | + if err == WinError::ACCESS_DENIED { |
| 1247 | + let mut opts = OpenOptions::new(); |
| 1248 | + opts.access_mode(c::DELETE); |
| 1249 | + opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS); |
| 1250 | + let Ok(f) = File::open_native(&old, &opts) else { return Err(err).io_result() }; |
| 1251 | + |
| 1252 | + // Calculate the layout of the `FILE_RENAME_INFO` we pass to `SetFileInformation` |
| 1253 | + // This is a dynamically sized struct so we need to get the position of the last field to calculate the actual size. |
| 1254 | + let Ok(new_len_without_nul_in_bytes): Result<u32, _> = ((new.len() - 1) * 2).try_into() |
| 1255 | + else { |
| 1256 | + return Err(err).io_result(); |
1279 | 1257 | };
|
1280 |
| - |
1281 |
| - if let Err(err) = result { |
1282 |
| - if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) |
1283 |
| - || err.raw_os_error() == Some(c::ERROR_INVALID_FUNCTION as _) |
1284 |
| - { |
1285 |
| - // `GetFileInformationByHandleEx` documents that not all underlying drivers support all file information classes. |
1286 |
| - // Since we know we passed the correct arguments, this means the underlying driver didn't understand our request; |
1287 |
| - // `MoveFileEx` proceeds by reopening the file without inhibiting reparse point behavior. |
1288 |
| - None |
1289 |
| - } else { |
1290 |
| - Some(Err(err)) |
1291 |
| - } |
1292 |
| - } else { |
1293 |
| - // SAFETY: The struct has been initialized by GetFileInformationByHandleEx |
1294 |
| - let file_attribute_tag_info = unsafe { file_attribute_tag_info.assume_init() }; |
1295 |
| - let file_type = FileType::new( |
1296 |
| - file_attribute_tag_info.FileAttributes, |
1297 |
| - file_attribute_tag_info.ReparseTag, |
1298 |
| - ); |
1299 |
| - |
1300 |
| - if file_type.is_symlink() { |
1301 |
| - // The file is a mount point, junction point or symlink so |
1302 |
| - // don't reopen the file so that the link gets renamed. |
1303 |
| - Some(Ok(handle)) |
1304 |
| - } else { |
1305 |
| - // Otherwise reopen the file without inhibiting reparse point behavior. |
1306 |
| - None |
| 1258 | + let offset: u32 = offset_of!(c::FILE_RENAME_INFO, FileName).try_into().unwrap(); |
| 1259 | + let struct_size = offset + new_len_without_nul_in_bytes + 2; |
| 1260 | + let layout = |
| 1261 | + Layout::from_size_align(struct_size as usize, align_of::<c::FILE_RENAME_INFO>()) |
| 1262 | + .unwrap(); |
| 1263 | + |
| 1264 | + // SAFETY: We allocate enough memory for a full FILE_RENAME_INFO struct and a filename. |
| 1265 | + let file_rename_info; |
| 1266 | + unsafe { |
| 1267 | + file_rename_info = alloc(layout).cast::<c::FILE_RENAME_INFO>(); |
| 1268 | + if file_rename_info.is_null() { |
| 1269 | + return Err(io::ErrorKind::OutOfMemory.into()); |
1307 | 1270 | }
|
1308 |
| - } |
1309 |
| - } |
1310 |
| - // The underlying driver may not support `FILE_FLAG_OPEN_REPARSE_POINT`: Retry without it. |
1311 |
| - Err(err) if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) => None, |
1312 |
| - Err(err) => Some(Err(err)), |
1313 |
| - } |
1314 |
| - .unwrap_or_else(|| create_file(0, 0))?; |
1315 | 1271 |
|
1316 |
| - let layout = core::alloc::Layout::from_size_align( |
1317 |
| - struct_size as _, |
1318 |
| - mem::align_of::<c::FILE_RENAME_INFO>(), |
1319 |
| - ) |
1320 |
| - .unwrap(); |
1321 |
| - |
1322 |
| - let file_rename_info = unsafe { alloc(layout) } as *mut c::FILE_RENAME_INFO; |
1323 |
| - |
1324 |
| - if file_rename_info.is_null() { |
1325 |
| - handle_alloc_error(layout); |
1326 |
| - } |
1327 |
| - |
1328 |
| - // SAFETY: file_rename_info is a non-null pointer pointing to memory allocated by the global allocator. |
1329 |
| - let mut file_rename_info = unsafe { Box::from_raw(file_rename_info) }; |
1330 |
| - |
1331 |
| - // SAFETY: We have allocated enough memory for a full FILE_RENAME_INFO struct and a filename. |
1332 |
| - unsafe { |
1333 |
| - (&raw mut (*file_rename_info).Anonymous).write(c::FILE_RENAME_INFO_0 { |
1334 |
| - Flags: c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS | c::FILE_RENAME_FLAG_POSIX_SEMANTICS, |
1335 |
| - }); |
1336 |
| - |
1337 |
| - (&raw mut (*file_rename_info).RootDirectory).write(ptr::null_mut()); |
1338 |
| - (&raw mut (*file_rename_info).FileNameLength).write(new_len_without_nul_in_bytes); |
1339 |
| - |
1340 |
| - new.as_ptr() |
1341 |
| - .copy_to_nonoverlapping((&raw mut (*file_rename_info).FileName) as *mut u16, new.len()); |
1342 |
| - } |
| 1272 | + (&raw mut (*file_rename_info).Anonymous).write(c::FILE_RENAME_INFO_0 { |
| 1273 | + Flags: c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS |
| 1274 | + | c::FILE_RENAME_FLAG_POSIX_SEMANTICS, |
| 1275 | + }); |
1343 | 1276 |
|
1344 |
| - // We don't use `set_file_information_by_handle` here as `FILE_RENAME_INFO` is used for both `FileRenameInfo` and `FileRenameInfoEx`. |
1345 |
| - let result = unsafe { |
1346 |
| - cvt(c::SetFileInformationByHandle( |
1347 |
| - handle.as_raw_handle(), |
1348 |
| - c::FileRenameInfoEx, |
1349 |
| - (&raw const *file_rename_info).cast::<c_void>(), |
1350 |
| - struct_size, |
1351 |
| - )) |
1352 |
| - }; |
| 1277 | + (&raw mut (*file_rename_info).RootDirectory).write(ptr::null_mut()); |
| 1278 | + // Don't include the NULL in the size |
| 1279 | + (&raw mut (*file_rename_info).FileNameLength).write(new_len_without_nul_in_bytes); |
1353 | 1280 |
|
1354 |
| - if let Err(err) = result { |
1355 |
| - if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) { |
1356 |
| - // FileRenameInfoEx and FILE_RENAME_FLAG_POSIX_SEMANTICS were added in Windows 10 1607; retry with FileRenameInfo. |
1357 |
| - file_rename_info.Anonymous.ReplaceIfExists = 1; |
| 1281 | + new.as_ptr().copy_to_nonoverlapping( |
| 1282 | + (&raw mut (*file_rename_info).FileName).cast::<u16>(), |
| 1283 | + new.len(), |
| 1284 | + ); |
| 1285 | + } |
1358 | 1286 |
|
1359 |
| - cvt(unsafe { |
| 1287 | + let result = unsafe { |
1360 | 1288 | c::SetFileInformationByHandle(
|
1361 |
| - handle.as_raw_handle(), |
1362 |
| - c::FileRenameInfo, |
1363 |
| - (&raw const *file_rename_info).cast::<c_void>(), |
| 1289 | + f.as_raw_handle(), |
| 1290 | + c::FileRenameInfoEx, |
| 1291 | + file_rename_info.cast::<c_void>(), |
1364 | 1292 | struct_size,
|
1365 | 1293 | )
|
1366 |
| - })?; |
| 1294 | + }; |
| 1295 | + unsafe { dealloc(file_rename_info.cast::<u8>(), layout) }; |
| 1296 | + if result == 0 { |
| 1297 | + if api::get_last_error() == WinError::DIR_NOT_EMPTY { |
| 1298 | + return Err(WinError::DIR_NOT_EMPTY).io_result(); |
| 1299 | + } else { |
| 1300 | + return Err(err).io_result(); |
| 1301 | + } |
| 1302 | + } |
1367 | 1303 | } else {
|
1368 |
| - return Err(err); |
| 1304 | + return Err(err).io_result(); |
1369 | 1305 | }
|
1370 | 1306 | }
|
1371 |
| - |
1372 | 1307 | Ok(())
|
1373 | 1308 | }
|
1374 | 1309 |
|
|
0 commit comments