@@ -1233,74 +1233,147 @@ pub fn unlink(path: &WCStr) -> io::Result<()> {
12331233}
12341234
12351235pub fn rename ( old : & WCStr , new : & WCStr ) -> io:: Result < ( ) > {
1236- if unsafe { c:: MoveFileExW ( old. as_ptr ( ) , new. as_ptr ( ) , c:: MOVEFILE_REPLACE_EXISTING ) } == 0 {
1237- let err = api:: get_last_error ( ) ;
1238- // if `MoveFileExW` fails with ERROR_ACCESS_DENIED then try to move
1239- // the file while ignoring the readonly attribute.
1240- // This is accomplished by calling `SetFileInformationByHandle` with `FileRenameInfoEx`.
1241- if err == WinError :: ACCESS_DENIED {
1242- let mut opts = OpenOptions :: new ( ) ;
1243- opts. access_mode ( c:: DELETE ) ;
1244- opts. custom_flags ( c:: FILE_FLAG_OPEN_REPARSE_POINT | c:: FILE_FLAG_BACKUP_SEMANTICS ) ;
1245- let Ok ( f) = File :: open_native ( & old, & opts) else { return Err ( err) . io_result ( ) } ;
1246-
1247- // Calculate the layout of the `FILE_RENAME_INFO` we pass to `SetFileInformation`
1248- // This is a dynamically sized struct so we need to get the position of the last field to calculate the actual size.
1249- let Ok ( new_len_without_nul_in_bytes) : Result < u32 , _ > =
1250- ( ( new. count_bytes ( ) - 1 ) * 2 ) . try_into ( )
1251- else {
1252- return Err ( err) . io_result ( ) ;
1236+ // Calculate the layout of the `FILE_RENAME_INFO` we pass to `SetFileInformation`
1237+ // This is a dynamically sized struct so we need to get the position of the last field to calculate the actual size.
1238+ let Ok ( new_len_without_nul_in_bytes) : Result < u32 , _ > = ( ( new. count_bytes ( ) - 1 ) * 2 ) . try_into ( )
1239+ else {
1240+ return Err ( WinError :: INVALID_PARAMETER ) . io_result ( ) ;
1241+ } ;
1242+ let offset: u32 = offset_of ! ( c:: FILE_RENAME_INFO , FileName ) . try_into ( ) . unwrap ( ) ;
1243+ let struct_size = offset + new_len_without_nul_in_bytes + 2 ;
1244+ let layout =
1245+ Layout :: from_size_align ( struct_size as usize , align_of :: < c:: FILE_RENAME_INFO > ( ) ) . unwrap ( ) ;
1246+
1247+ let create_file = |extra_access, extra_flags| {
1248+ let handle = unsafe {
1249+ HandleOrInvalid :: from_raw_handle ( c:: CreateFileW (
1250+ old. as_ptr ( ) ,
1251+ c:: SYNCHRONIZE | c:: DELETE | extra_access,
1252+ c:: FILE_SHARE_READ | c:: FILE_SHARE_WRITE | c:: FILE_SHARE_DELETE ,
1253+ ptr:: null ( ) ,
1254+ c:: OPEN_EXISTING ,
1255+ c:: FILE_ATTRIBUTE_NORMAL | c:: FILE_FLAG_BACKUP_SEMANTICS | extra_flags,
1256+ ptr:: null_mut ( ) ,
1257+ ) )
1258+ } ;
1259+
1260+ OwnedHandle :: try_from ( handle) . map_err ( |_| io:: Error :: last_os_error ( ) )
1261+ } ;
1262+
1263+ // The following code replicates `MoveFileEx`'s behavior as reverse-engineered from its disassembly.
1264+ // If `old` refers to a mount point, we move it instead of the target.
1265+ let f = match create_file ( c:: FILE_READ_ATTRIBUTES , c:: FILE_FLAG_OPEN_REPARSE_POINT ) {
1266+ Ok ( handle) => {
1267+ let mut file_attribute_tag_info: MaybeUninit < c:: FILE_ATTRIBUTE_TAG_INFO > =
1268+ MaybeUninit :: uninit ( ) ;
1269+
1270+ let result = unsafe {
1271+ cvt ( c:: GetFileInformationByHandleEx (
1272+ handle. as_raw_handle ( ) ,
1273+ c:: FileAttributeTagInfo ,
1274+ file_attribute_tag_info. as_mut_ptr ( ) . cast ( ) ,
1275+ mem:: size_of :: < c:: FILE_ATTRIBUTE_TAG_INFO > ( ) . try_into ( ) . unwrap ( ) ,
1276+ ) )
12531277 } ;
1254- let offset: u32 = offset_of ! ( c:: FILE_RENAME_INFO , FileName ) . try_into ( ) . unwrap ( ) ;
1255- let struct_size = offset + new_len_without_nul_in_bytes + 2 ;
1256- let layout =
1257- Layout :: from_size_align ( struct_size as usize , align_of :: < c:: FILE_RENAME_INFO > ( ) )
1258- . unwrap ( ) ;
1259-
1260- // SAFETY: We allocate enough memory for a full FILE_RENAME_INFO struct and a filename.
1261- let file_rename_info;
1262- unsafe {
1263- file_rename_info = alloc ( layout) . cast :: < c:: FILE_RENAME_INFO > ( ) ;
1264- if file_rename_info. is_null ( ) {
1265- return Err ( io:: ErrorKind :: OutOfMemory . into ( ) ) ;
1278+
1279+ if let Err ( err) = result {
1280+ if err. raw_os_error ( ) == Some ( c:: ERROR_INVALID_PARAMETER as _ )
1281+ || err. raw_os_error ( ) == Some ( c:: ERROR_INVALID_FUNCTION as _ )
1282+ {
1283+ // `GetFileInformationByHandleEx` documents that not all underlying drivers support all file information classes.
1284+ // Since we know we passed the correct arguments, this means the underlying driver didn't understand our request;
1285+ // `MoveFileEx` proceeds by reopening the file without inhibiting reparse point behavior.
1286+ None
1287+ } else {
1288+ Some ( Err ( err) )
1289+ }
1290+ } else {
1291+ // SAFETY: The struct has been initialized by GetFileInformationByHandleEx
1292+ let file_attribute_tag_info = unsafe { file_attribute_tag_info. assume_init ( ) } ;
1293+ let file_type = FileType :: new (
1294+ file_attribute_tag_info. FileAttributes ,
1295+ file_attribute_tag_info. ReparseTag ,
1296+ ) ;
1297+
1298+ if file_type. is_symlink ( ) {
1299+ // The file is a mount point, junction point or symlink so
1300+ // don't reopen the file so that the link gets renamed.
1301+ Some ( Ok ( handle) )
1302+ } else {
1303+ // Otherwise reopen the file without inhibiting reparse point behavior.
1304+ None
12661305 }
1306+ }
1307+ }
1308+ // The underlying driver may not support `FILE_FLAG_OPEN_REPARSE_POINT`: Retry without it.
1309+ Err ( err) if err. raw_os_error ( ) == Some ( c:: ERROR_INVALID_PARAMETER as _ ) => None ,
1310+ Err ( err) => Some ( Err ( err) ) ,
1311+ }
1312+ . unwrap_or_else ( || create_file ( 0 , 0 ) ) ?;
12671313
1268- ( & raw mut ( * file_rename_info) . Anonymous ) . write ( c:: FILE_RENAME_INFO_0 {
1269- Flags : c:: FILE_RENAME_FLAG_REPLACE_IF_EXISTS
1270- | c:: FILE_RENAME_FLAG_POSIX_SEMANTICS ,
1271- } ) ;
1314+ // SAFETY: We allocate enough memory for a full FILE_RENAME_INFO struct and a filename.
1315+ let file_rename_info;
1316+ unsafe {
1317+ file_rename_info = alloc ( layout) . cast :: < c:: FILE_RENAME_INFO > ( ) ;
1318+ if file_rename_info. is_null ( ) {
1319+ return Err ( io:: ErrorKind :: OutOfMemory . into ( ) ) ;
1320+ }
12721321
1273- ( & raw mut ( * file_rename_info) . RootDirectory ) . write ( ptr :: null_mut ( ) ) ;
1274- // Don't include the NULL in the size
1275- ( & raw mut ( * file_rename_info ) . FileNameLength ) . write ( new_len_without_nul_in_bytes ) ;
1322+ ( & raw mut ( * file_rename_info) . Anonymous ) . write ( c :: FILE_RENAME_INFO_0 {
1323+ Flags : c :: FILE_RENAME_FLAG_REPLACE_IF_EXISTS | c :: FILE_RENAME_FLAG_POSIX_SEMANTICS ,
1324+ } ) ;
12761325
1277- new. as_ptr ( ) . copy_to_nonoverlapping (
1278- ( & raw mut ( * file_rename_info) . FileName ) . cast :: < u16 > ( ) ,
1279- new. count_bytes ( ) ,
1280- ) ;
1326+ ( & raw mut ( * file_rename_info) . RootDirectory ) . write ( ptr:: null_mut ( ) ) ;
1327+ // Don't include the NULL in the size
1328+ ( & raw mut ( * file_rename_info) . FileNameLength ) . write ( new_len_without_nul_in_bytes) ;
1329+
1330+ new. as_ptr ( ) . copy_to_nonoverlapping (
1331+ ( & raw mut ( * file_rename_info) . FileName ) . cast :: < u16 > ( ) ,
1332+ new. count_bytes ( ) ,
1333+ ) ;
1334+ }
1335+
1336+ let result = cvt ( unsafe {
1337+ c:: SetFileInformationByHandle (
1338+ f. as_raw_handle ( ) ,
1339+ c:: FileRenameInfoEx ,
1340+ file_rename_info. cast :: < c_void > ( ) ,
1341+ struct_size,
1342+ )
1343+ } ) ;
1344+
1345+ let result = match result {
1346+ Ok ( i) => Ok ( i) ,
1347+ // Windows version older than Windows 10 1607 don't support FileRenameInfoEx and fail with ERROR_INVALID_PARAMETER.
1348+ // On Windows 10 1607, it may fail with STATUS_VOLUME_NOT_UPGRADED, which gets mapped to ERROR_INVALID_FUNCTION.
1349+ // On Windows Server 2022, it may fail on ReFS with ERROR_NOT_SUPPORTED.
1350+ Err ( err)
1351+ if err. raw_os_error ( ) == Some ( c:: ERROR_INVALID_PARAMETER as _ )
1352+ || err. raw_os_error ( ) == Some ( c:: ERROR_INVALID_FUNCTION as _ )
1353+ || err. raw_os_error ( ) == Some ( c:: ERROR_NOT_SUPPORTED as _ ) =>
1354+ {
1355+ unsafe {
1356+ ( & raw mut ( * file_rename_info) . Anonymous )
1357+ . write ( c:: FILE_RENAME_INFO_0 { ReplaceIfExists : true } ) ;
12811358 }
12821359
1283- let result = unsafe {
1360+ cvt ( unsafe {
12841361 c:: SetFileInformationByHandle (
12851362 f. as_raw_handle ( ) ,
1286- c:: FileRenameInfoEx ,
1363+ c:: FileRenameInfo ,
12871364 file_rename_info. cast :: < c_void > ( ) ,
12881365 struct_size,
12891366 )
1290- } ;
1291- unsafe { dealloc ( file_rename_info. cast :: < u8 > ( ) , layout) } ;
1292- if result == 0 {
1293- if api:: get_last_error ( ) == WinError :: DIR_NOT_EMPTY {
1294- return Err ( WinError :: DIR_NOT_EMPTY ) . io_result ( ) ;
1295- } else {
1296- return Err ( err) . io_result ( ) ;
1297- }
1298- }
1299- } else {
1300- return Err ( err) . io_result ( ) ;
1367+ } )
13011368 }
1369+ Err ( err) => Err ( err) ,
1370+ } ;
1371+
1372+ unsafe {
1373+ dealloc ( file_rename_info. cast :: < u8 > ( ) , layout) ;
13021374 }
1303- Ok ( ( ) )
1375+
1376+ result. map ( |_| ( ) )
13041377}
13051378
13061379pub fn rmdir ( p : & WCStr ) -> io:: Result < ( ) > {
0 commit comments