| 
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