@@ -1318,6 +1318,66 @@ pub fn unlink(path: &WCStr) -> io::Result<()> {
13181318 }
13191319}
13201320
1321+ /// Renames a file using NtSetInformationFile and FileRenameInformation. If to_dir is None, passes a
1322+ /// null pointer, which uses the directory `from` is already in (or it's ignored if `to` is an
1323+ /// absolute path).
1324+ fn nt_rename ( from : impl AsHandle , to : & [ u16 ] , to_dir : Option < & Handle > ) -> io:: Result < ( ) > {
1325+ let handle = from. as_handle ( ) ;
1326+
1327+ const too_long_err: io:: Error =
1328+ io:: const_error!( io:: ErrorKind :: InvalidFilename , "Filename too long" ) ;
1329+ let struct_size = to
1330+ . len ( )
1331+ . checked_mul ( 2 )
1332+ . and_then ( |x| x. checked_add ( offset_of ! ( c:: FILE_RENAME_INFORMATION , FileName ) ) )
1333+ . ok_or ( too_long_err) ?;
1334+ let layout = Layout :: from_size_align ( struct_size, align_of :: < c:: FILE_RENAME_INFORMATION > ( ) )
1335+ . map_err ( |_| too_long_err) ?;
1336+ let struct_size = u32:: try_from ( struct_size) . map_err ( |_| too_long_err) ?;
1337+ let to_byte_len = u32:: try_from ( to. len ( ) * 2 ) . map_err ( |_| too_long_err) ?;
1338+
1339+ let file_rename_info;
1340+ // SAFETY: We allocate enough memory for a full FILE_RENAME_INFORMATION struct and the filename.
1341+ unsafe {
1342+ file_rename_info = alloc ( layout) . cast :: < c:: FILE_RENAME_INFORMATION > ( ) ;
1343+ if file_rename_info. is_null ( ) {
1344+ return Err ( io:: ErrorKind :: OutOfMemory . into ( ) ) ;
1345+ }
1346+
1347+ ( & raw mut ( * file_rename_info) . Anonymous ) . write ( c:: FILE_RENAME_INFORMATION_0 {
1348+ Flags : c:: FILE_RENAME_FLAG_REPLACE_IF_EXISTS | c:: FILE_RENAME_FLAG_POSIX_SEMANTICS ,
1349+ } ) ;
1350+
1351+ ( & raw mut ( * file_rename_info) . RootDirectory )
1352+ . write ( to_dir. map ( Handle :: as_raw_handle) . unwrap_or_else ( ptr:: null_mut) ) ;
1353+ // Don't include the NULL in the size
1354+ ( & raw mut ( * file_rename_info) . FileNameLength ) . write ( to_byte_len) ;
1355+
1356+ to. as_ptr ( ) . copy_to_nonoverlapping (
1357+ ( & raw mut ( * file_rename_info) . FileName ) . cast :: < u16 > ( ) ,
1358+ to. len ( ) ,
1359+ ) ;
1360+ }
1361+
1362+ let status = unsafe {
1363+ c:: NtSetInformationFile (
1364+ handle. as_raw_handle ( ) ,
1365+ & mut c:: IO_STATUS_BLOCK :: default ( ) ,
1366+ file_rename_info. cast :: < c_void > ( ) ,
1367+ struct_size,
1368+ c:: FileRenameInformation ,
1369+ )
1370+ } ;
1371+ unsafe { dealloc ( file_rename_info. cast :: < u8 > ( ) , layout) } ;
1372+ if c:: nt_success ( status) {
1373+ // SAFETY: nt_success guarantees that handle is no longer null
1374+ Ok ( ( ) )
1375+ } else {
1376+ Err ( WinError :: new ( unsafe { c:: RtlNtStatusToDosError ( status) } ) )
1377+ }
1378+ . io_result ( )
1379+ }
1380+
13211381pub fn rename ( old : & WCStr , new : & WCStr ) -> io:: Result < ( ) > {
13221382 if unsafe { c:: MoveFileExW ( old. as_ptr ( ) , new. as_ptr ( ) , c:: MOVEFILE_REPLACE_EXISTING ) } == 0 {
13231383 let err = api:: get_last_error ( ) ;
@@ -1330,58 +1390,7 @@ pub fn rename(old: &WCStr, new: &WCStr) -> io::Result<()> {
13301390 opts. custom_flags ( c:: FILE_FLAG_OPEN_REPARSE_POINT | c:: FILE_FLAG_BACKUP_SEMANTICS ) ;
13311391 let Ok ( f) = File :: open_native ( & old, & opts) else { return Err ( err) . io_result ( ) } ;
13321392
1333- // Calculate the layout of the `FILE_RENAME_INFO` we pass to `SetFileInformation`
1334- // This is a dynamically sized struct so we need to get the position of the last field to calculate the actual size.
1335- let Ok ( new_len_without_nul_in_bytes) : Result < u32 , _ > =
1336- ( ( new. count_bytes ( ) - 1 ) * 2 ) . try_into ( )
1337- else {
1338- return Err ( err) . io_result ( ) ;
1339- } ;
1340- let offset: u32 = offset_of ! ( c:: FILE_RENAME_INFO , FileName ) . try_into ( ) . unwrap ( ) ;
1341- let struct_size = offset + new_len_without_nul_in_bytes + 2 ;
1342- let layout =
1343- Layout :: from_size_align ( struct_size as usize , align_of :: < c:: FILE_RENAME_INFO > ( ) )
1344- . unwrap ( ) ;
1345-
1346- let file_rename_info;
1347- // SAFETY: We allocate enough memory for a full FILE_RENAME_INFO struct and a filename.
1348- unsafe {
1349- file_rename_info = alloc ( layout) . cast :: < c:: FILE_RENAME_INFO > ( ) ;
1350- if file_rename_info. is_null ( ) {
1351- return Err ( io:: ErrorKind :: OutOfMemory . into ( ) ) ;
1352- }
1353-
1354- ( & raw mut ( * file_rename_info) . Anonymous ) . write ( c:: FILE_RENAME_INFO_0 {
1355- Flags : c:: FILE_RENAME_FLAG_REPLACE_IF_EXISTS
1356- | c:: FILE_RENAME_FLAG_POSIX_SEMANTICS ,
1357- } ) ;
1358-
1359- ( & raw mut ( * file_rename_info) . RootDirectory ) . write ( ptr:: null_mut ( ) ) ;
1360- // Don't include the NULL in the size
1361- ( & raw mut ( * file_rename_info) . FileNameLength ) . write ( new_len_without_nul_in_bytes) ;
1362-
1363- new. as_ptr ( ) . copy_to_nonoverlapping (
1364- ( & raw mut ( * file_rename_info) . FileName ) . cast :: < u16 > ( ) ,
1365- new. count_bytes ( ) ,
1366- ) ;
1367- }
1368-
1369- let result = unsafe {
1370- c:: SetFileInformationByHandle (
1371- f. as_raw_handle ( ) ,
1372- c:: FileRenameInfoEx ,
1373- file_rename_info. cast :: < c_void > ( ) ,
1374- struct_size,
1375- )
1376- } ;
1377- unsafe { dealloc ( file_rename_info. cast :: < u8 > ( ) , layout) } ;
1378- if result == 0 {
1379- if api:: get_last_error ( ) == WinError :: DIR_NOT_EMPTY {
1380- return Err ( WinError :: DIR_NOT_EMPTY ) . io_result ( ) ;
1381- } else {
1382- return Err ( err) . io_result ( ) ;
1383- }
1384- }
1393+ nt_rename ( f. handle , new. as_slice ( ) , None ) ?;
13851394 } else {
13861395 return Err ( err) . io_result ( ) ;
13871396 }
0 commit comments