|
1 | 1 | use super::api::{self, WinError};
|
2 | 2 | use super::{IoResult, to_u16s};
|
| 3 | +use crate::alloc::{alloc, handle_alloc_error}; |
3 | 4 | use crate::borrow::Cow;
|
4 | 5 | use crate::ffi::{OsStr, OsString, c_void};
|
5 | 6 | use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom};
|
@@ -1223,7 +1224,139 @@ pub fn unlink(p: &Path) -> io::Result<()> {
|
1223 | 1224 | pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
|
1224 | 1225 | let old = maybe_verbatim(old)?;
|
1225 | 1226 | let new = maybe_verbatim(new)?;
|
1226 |
| - cvt(unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), c::MOVEFILE_REPLACE_EXISTING) })?; |
| 1227 | + |
| 1228 | + let new_len_without_nul_in_bytes = (new.len() - 1).try_into().unwrap(); |
| 1229 | + |
| 1230 | + // The last field of FILE_RENAME_INFO, the file name, is unsized, |
| 1231 | + // and FILE_RENAME_INFO has two padding bytes. |
| 1232 | + // Therefore we need to make sure to not allocate less than |
| 1233 | + // size_of::<c::FILE_RENAME_INFO>() bytes, which would be the case with |
| 1234 | + // 0 or 1 character paths + a null byte. |
| 1235 | + let struct_size = mem::size_of::<c::FILE_RENAME_INFO>() |
| 1236 | + .max(mem::offset_of!(c::FILE_RENAME_INFO, FileName) + new.len() * mem::size_of::<u16>()); |
| 1237 | + |
| 1238 | + let struct_size: u32 = struct_size.try_into().unwrap(); |
| 1239 | + |
| 1240 | + let create_file = |extra_access, extra_flags| { |
| 1241 | + let handle = unsafe { |
| 1242 | + HandleOrInvalid::from_raw_handle(c::CreateFileW( |
| 1243 | + old.as_ptr(), |
| 1244 | + c::SYNCHRONIZE | c::DELETE | extra_access, |
| 1245 | + c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE, |
| 1246 | + ptr::null(), |
| 1247 | + c::OPEN_EXISTING, |
| 1248 | + c::FILE_ATTRIBUTE_NORMAL | c::FILE_FLAG_BACKUP_SEMANTICS | extra_flags, |
| 1249 | + ptr::null_mut(), |
| 1250 | + )) |
| 1251 | + }; |
| 1252 | + |
| 1253 | + OwnedHandle::try_from(handle).map_err(|_| io::Error::last_os_error()) |
| 1254 | + }; |
| 1255 | + |
| 1256 | + // The following code replicates `MoveFileEx`'s behavior as reverse-engineered from its disassembly. |
| 1257 | + // If `old` refers to a mount point, we move it instead of the target. |
| 1258 | + let handle = match create_file(c::FILE_READ_ATTRIBUTES, c::FILE_FLAG_OPEN_REPARSE_POINT) { |
| 1259 | + Ok(handle) => { |
| 1260 | + let mut file_attribute_tag_info: MaybeUninit<c::FILE_ATTRIBUTE_TAG_INFO> = |
| 1261 | + MaybeUninit::uninit(); |
| 1262 | + |
| 1263 | + let result = unsafe { |
| 1264 | + cvt(c::GetFileInformationByHandleEx( |
| 1265 | + handle.as_raw_handle(), |
| 1266 | + c::FileAttributeTagInfo, |
| 1267 | + file_attribute_tag_info.as_mut_ptr().cast(), |
| 1268 | + mem::size_of::<c::FILE_ATTRIBUTE_TAG_INFO>().try_into().unwrap(), |
| 1269 | + )) |
| 1270 | + }; |
| 1271 | + |
| 1272 | + if let Err(err) = result { |
| 1273 | + if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) |
| 1274 | + || err.raw_os_error() == Some(c::ERROR_INVALID_FUNCTION as _) |
| 1275 | + { |
| 1276 | + // `GetFileInformationByHandleEx` documents that not all underlying drivers support all file information classes. |
| 1277 | + // Since we know we passed the correct arguments, this means the underlying driver didn't understand our request; |
| 1278 | + // `MoveFileEx` proceeds by reopening the file without inhibiting reparse point behavior. |
| 1279 | + None |
| 1280 | + } else { |
| 1281 | + Some(Err(err)) |
| 1282 | + } |
| 1283 | + } else { |
| 1284 | + // SAFETY: The struct has been initialized by GetFileInformationByHandleEx |
| 1285 | + let file_attribute_tag_info = unsafe { file_attribute_tag_info.assume_init() }; |
| 1286 | + |
| 1287 | + if file_attribute_tag_info.FileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 |
| 1288 | + && file_attribute_tag_info.ReparseTag != c::IO_REPARSE_TAG_MOUNT_POINT |
| 1289 | + { |
| 1290 | + // The file is not a mount point: Reopen the file without inhibiting reparse point behavior. |
| 1291 | + None |
| 1292 | + } else { |
| 1293 | + // The file is a mount point: Don't reopen the file so that the mount point gets renamed. |
| 1294 | + Some(Ok(handle)) |
| 1295 | + } |
| 1296 | + } |
| 1297 | + } |
| 1298 | + // The underlying driver may not support `FILE_FLAG_OPEN_REPARSE_POINT`: Retry without it. |
| 1299 | + Err(err) if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) => None, |
| 1300 | + Err(err) => Some(Err(err)), |
| 1301 | + } |
| 1302 | + .unwrap_or_else(|| create_file(0, 0))?; |
| 1303 | + |
| 1304 | + let layout = core::alloc::Layout::from_size_align( |
| 1305 | + struct_size as _, |
| 1306 | + mem::align_of::<c::FILE_RENAME_INFO>(), |
| 1307 | + ) |
| 1308 | + .unwrap(); |
| 1309 | + |
| 1310 | + let file_rename_info = unsafe { alloc(layout) } as *mut c::FILE_RENAME_INFO; |
| 1311 | + |
| 1312 | + if file_rename_info.is_null() { |
| 1313 | + handle_alloc_error(layout); |
| 1314 | + } |
| 1315 | + |
| 1316 | + // SAFETY: file_rename_info is a non-null pointer pointing to memory allocated by the global allocator. |
| 1317 | + let mut file_rename_info = unsafe { Box::from_raw(file_rename_info) }; |
| 1318 | + |
| 1319 | + // SAFETY: We have allocated enough memory for a full FILE_RENAME_INFO struct and a filename. |
| 1320 | + unsafe { |
| 1321 | + (&raw mut (*file_rename_info).Anonymous).write(c::FILE_RENAME_INFO_0 { |
| 1322 | + Flags: c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS | c::FILE_RENAME_FLAG_POSIX_SEMANTICS, |
| 1323 | + }); |
| 1324 | + |
| 1325 | + (&raw mut (*file_rename_info).RootDirectory).write(ptr::null_mut()); |
| 1326 | + (&raw mut (*file_rename_info).FileNameLength).write(new_len_without_nul_in_bytes); |
| 1327 | + |
| 1328 | + new.as_ptr() |
| 1329 | + .copy_to_nonoverlapping((&raw mut (*file_rename_info).FileName) as *mut u16, new.len()); |
| 1330 | + } |
| 1331 | + |
| 1332 | + // We don't use `set_file_information_by_handle` here as `FILE_RENAME_INFO` is used for both `FileRenameInfo` and `FileRenameInfoEx`. |
| 1333 | + let result = unsafe { |
| 1334 | + cvt(c::SetFileInformationByHandle( |
| 1335 | + handle.as_raw_handle(), |
| 1336 | + c::FileRenameInfoEx, |
| 1337 | + (&raw const *file_rename_info).cast::<c_void>(), |
| 1338 | + struct_size, |
| 1339 | + )) |
| 1340 | + }; |
| 1341 | + |
| 1342 | + if let Err(err) = result { |
| 1343 | + if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) { |
| 1344 | + // FileRenameInfoEx and FILE_RENAME_FLAG_POSIX_SEMANTICS were added in Windows 10 1607; retry with FileRenameInfo. |
| 1345 | + file_rename_info.Anonymous.ReplaceIfExists = 1; |
| 1346 | + |
| 1347 | + cvt(unsafe { |
| 1348 | + c::SetFileInformationByHandle( |
| 1349 | + handle.as_raw_handle(), |
| 1350 | + c::FileRenameInfo, |
| 1351 | + (&raw const *file_rename_info).cast::<c_void>(), |
| 1352 | + struct_size, |
| 1353 | + ) |
| 1354 | + })?; |
| 1355 | + } else { |
| 1356 | + return Err(err); |
| 1357 | + } |
| 1358 | + } |
| 1359 | + |
1227 | 1360 | Ok(())
|
1228 | 1361 | }
|
1229 | 1362 |
|
|
0 commit comments