|  | 
| 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