@@ -1233,74 +1233,147 @@ pub fn unlink(path: &WCStr) -> io::Result<()> {
1233
1233
}
1234
1234
1235
1235
pub 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
+ ) )
1253
1277
} ;
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
1266
1305
}
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 ) ) ?;
1267
1313
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
+ }
1272
1321
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
+ } ) ;
1276
1325
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 } ) ;
1281
1358
}
1282
1359
1283
- let result = unsafe {
1360
+ cvt ( unsafe {
1284
1361
c:: SetFileInformationByHandle (
1285
1362
f. as_raw_handle ( ) ,
1286
- c:: FileRenameInfoEx ,
1363
+ c:: FileRenameInfo ,
1287
1364
file_rename_info. cast :: < c_void > ( ) ,
1288
1365
struct_size,
1289
1366
)
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
+ } )
1301
1368
}
1369
+ Err ( err) => Err ( err) ,
1370
+ } ;
1371
+
1372
+ unsafe {
1373
+ dealloc ( file_rename_info. cast :: < u8 > ( ) , layout) ;
1302
1374
}
1303
- Ok ( ( ) )
1375
+
1376
+ result. map ( |_| ( ) )
1304
1377
}
1305
1378
1306
1379
pub fn rmdir ( p : & WCStr ) -> io:: Result < ( ) > {
0 commit comments