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