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