@@ -11,22 +11,24 @@ use std::ptr;
1111use windows_sys:: Wdk :: Foundation :: OBJECT_ATTRIBUTES ;
1212use windows_sys:: Wdk :: Storage :: FileSystem :: {
1313 FILE_CREATE , FILE_DIRECTORY_FILE , FILE_NON_DIRECTORY_FILE , FILE_OPEN , FILE_OPEN_IF ,
14- FILE_OPEN_REPARSE_POINT , FILE_SYNCHRONOUS_IO_NONALERT , NtCreateFile ,
14+ FILE_OPEN_REPARSE_POINT , FILE_RENAME_INFORMATION , FILE_SYNCHRONOUS_IO_NONALERT ,
15+ FileRenameInformation , NtCreateFile , NtSetInformationFile ,
1516} ;
1617use windows_sys:: Win32 :: Foundation :: {
17- HANDLE , OBJ_CASE_INSENSITIVE , RtlNtStatusToDosError , STATUS_OBJECT_NAME_COLLISION ,
18- STATUS_OBJECT_NAME_NOT_FOUND , UNICODE_STRING ,
18+ HANDLE , NTSTATUS , OBJ_CASE_INSENSITIVE , RtlNtStatusToDosError , STATUS_NO_SUCH_FILE ,
19+ STATUS_OBJECT_NAME_COLLISION , STATUS_OBJECT_NAME_EXISTS , STATUS_OBJECT_NAME_NOT_FOUND ,
20+ STATUS_OBJECT_PATH_NOT_FOUND , UNICODE_STRING ,
1921} ;
2022use windows_sys:: Win32 :: Storage :: FileSystem :: {
2123 DELETE , FILE_ADD_FILE , FILE_ADD_SUBDIRECTORY , FILE_ATTRIBUTE_NORMAL , FILE_ATTRIBUTE_READONLY ,
2224 FILE_ATTRIBUTE_REPARSE_POINT , FILE_ATTRIBUTE_TAG_INFO , FILE_BASIC_INFO ,
2325 FILE_DISPOSITION_FLAG_DELETE , FILE_DISPOSITION_FLAG_IGNORE_READONLY_ATTRIBUTE ,
2426 FILE_DISPOSITION_FLAG_POSIX_SEMANTICS , FILE_DISPOSITION_INFO_EX , FILE_ID_INFO ,
25- FILE_LIST_DIRECTORY , FILE_READ_ATTRIBUTES , FILE_READ_DATA , FILE_RENAME_INFO , FILE_SHARE_DELETE ,
26- FILE_SHARE_READ , FILE_SHARE_WRITE , FILE_TRAVERSE , FILE_WRITE_ATTRIBUTES , FILE_WRITE_DATA ,
27- FileAttributeTagInfo , FileBasicInfo , FileDispositionInfoEx , FileIdInfo , FileRenameInfo ,
28- FlushFileBuffers , GetFileInformationByHandleEx , LOCKFILE_EXCLUSIVE_LOCK , LockFileEx ,
29- SYNCHRONIZE , SetFileInformationByHandle ,
27+ FILE_LIST_DIRECTORY , FILE_READ_ATTRIBUTES , FILE_READ_DATA , FILE_SHARE_DELETE , FILE_SHARE_READ ,
28+ FILE_SHARE_WRITE , FILE_TRAVERSE , FILE_WRITE_ATTRIBUTES , FILE_WRITE_DATA , FileAttributeTagInfo ,
29+ FileBasicInfo , FileDispositionInfoEx , FileIdInfo , FlushFileBuffers ,
30+ GetFileInformationByHandleEx , LOCKFILE_EXCLUSIVE_LOCK , LockFileEx , SYNCHRONIZE ,
31+ SetFileInformationByHandle ,
3032} ;
3133use windows_sys:: Win32 :: System :: IO :: { IO_STATUS_BLOCK , OVERLAPPED } ;
3234
@@ -380,24 +382,29 @@ impl WindowsDirectory {
380382 . and_then ( |length| u32:: try_from ( length) . ok ( ) )
381383 . ok_or_else ( || io:: Error :: other ( "workspace destination name is too long" ) ) ?;
382384 let total = rename_information_buffer_size ( name. len ( ) ) ?;
385+ let total_u32 = u32:: try_from ( total)
386+ . map_err ( |_| io:: Error :: other ( "workspace rename buffer is too large" ) ) ?;
383387 let words = total. div_ceil ( size_of :: < usize > ( ) ) ;
384388 let mut storage = vec ! [ 0_usize ; words] ;
385- let info = storage. as_mut_ptr ( ) . cast :: < FILE_RENAME_INFO > ( ) ;
389+ let info = storage. as_mut_ptr ( ) . cast :: < FILE_RENAME_INFORMATION > ( ) ;
386390 // SAFETY: `storage` is suitably aligned and large enough for the fixed
387- // header plus the exact UTF-16 name payload.
391+ // header plus the exact UTF-16 name payload. Both source and parent
392+ // handles remain owned for the synchronous native rename call.
388393 unsafe {
389394 ( * info) . Anonymous . ReplaceIfExists = false ;
390395 ( * info) . RootDirectory = raw_handle ( & destination_parent. handle ) ;
391396 ( * info) . FileNameLength = byte_len;
392397 ptr:: copy_nonoverlapping ( name. as_ptr ( ) , ( * info) . FileName . as_mut_ptr ( ) , name. len ( ) ) ;
393- if SetFileInformationByHandle (
398+ let mut status_block: IO_STATUS_BLOCK = zeroed ( ) ;
399+ let status = NtSetInformationFile (
394400 raw_handle ( & self . handle ) ,
395- FileRenameInfo ,
401+ & mut status_block ,
396402 info. cast ( ) ,
397- total as u32 ,
398- ) == 0
399- {
400- return Err ( io:: Error :: last_os_error ( ) ) ;
403+ total_u32,
404+ FileRenameInformation ,
405+ ) ;
406+ if status < 0 || status == STATUS_OBJECT_NAME_EXISTS {
407+ return Err ( rename_status_error ( status) ) ;
401408 }
402409 }
403410 Ok ( ( ) )
@@ -411,14 +418,28 @@ fn rename_information_buffer_size(name_units: usize) -> io::Result<usize> {
411418 let trailing_bytes = trailing_units
412419 . checked_mul ( size_of :: < u16 > ( ) )
413420 . ok_or_else ( || io:: Error :: other ( "workspace rename buffer overflow" ) ) ?;
414- // `FILE_RENAME_INFO ` already contains one UTF-16 code unit plus tail
421+ // `FILE_RENAME_INFORMATION ` already contains one UTF-16 code unit plus tail
415422 // padding. Starting at `FileName` omits that padding and Windows rejects
416423 // the otherwise valid rename buffer with ERROR_INVALID_PARAMETER.
417- size_of :: < FILE_RENAME_INFO > ( )
424+ size_of :: < FILE_RENAME_INFORMATION > ( )
418425 . checked_add ( trailing_bytes)
419426 . ok_or_else ( || io:: Error :: other ( "workspace rename buffer overflow" ) )
420427}
421428
429+ fn rename_status_error ( status : NTSTATUS ) -> io:: Error {
430+ let kind = match status {
431+ STATUS_OBJECT_NAME_COLLISION | STATUS_OBJECT_NAME_EXISTS => io:: ErrorKind :: AlreadyExists ,
432+ STATUS_OBJECT_NAME_NOT_FOUND | STATUS_OBJECT_PATH_NOT_FOUND | STATUS_NO_SUCH_FILE => {
433+ io:: ErrorKind :: NotFound
434+ }
435+ _ => io:: ErrorKind :: Other ,
436+ } ;
437+ io:: Error :: new (
438+ kind,
439+ io:: Error :: from_raw_os_error ( unsafe { RtlNtStatusToDosError ( status) } as i32 ) ,
440+ )
441+ }
442+
422443pub ( crate ) fn lock_file_exclusive ( file : & File ) -> io:: Result < ( ) > {
423444 let mut overlapped = OVERLAPPED :: default ( ) ;
424445 // SAFETY: the file handle and overlapped structure are valid for this
@@ -718,16 +739,28 @@ mod lock_tests {
718739 fn rename_buffer_includes_the_struct_tail_and_alignment ( ) {
719740 assert_eq ! (
720741 rename_information_buffer_size( 1 ) . expect( "one-unit rename buffer" ) ,
721- size_of:: <FILE_RENAME_INFO >( )
742+ size_of:: <FILE_RENAME_INFORMATION >( )
722743 ) ;
723744 assert_eq ! (
724745 rename_information_buffer_size( 8 ) . expect( "eight-unit rename buffer" ) ,
725- size_of:: <FILE_RENAME_INFO >( ) + 7 * size_of:: <u16 >( )
746+ size_of:: <FILE_RENAME_INFORMATION >( ) + 7 * size_of:: <u16 >( )
726747 ) ;
727748 assert ! ( rename_information_buffer_size( 0 ) . is_err( ) ) ;
728749 assert ! (
729750 rename_information_buffer_size( 8 ) . expect( "aligned rename buffer" )
730- > std:: mem:: offset_of!( FILE_RENAME_INFO , FileName ) + 8 * size_of:: <u16 >( )
751+ > std:: mem:: offset_of!( FILE_RENAME_INFORMATION , FileName ) + 8 * size_of:: <u16 >( )
752+ ) ;
753+ assert_eq ! (
754+ rename_status_error( STATUS_OBJECT_NAME_COLLISION ) . kind( ) ,
755+ io:: ErrorKind :: AlreadyExists
756+ ) ;
757+ assert_eq ! (
758+ rename_status_error( STATUS_OBJECT_NAME_NOT_FOUND ) . kind( ) ,
759+ io:: ErrorKind :: NotFound
760+ ) ;
761+ assert_eq ! (
762+ rename_status_error( STATUS_OBJECT_PATH_NOT_FOUND ) . kind( ) ,
763+ io:: ErrorKind :: NotFound
731764 ) ;
732765 }
733766
@@ -753,6 +786,39 @@ mod lock_tests {
753786 expected
754787 ) ;
755788
789+ let collision_source = parent
790+ . create_directory ( OsStr :: new ( "collision-source" ) )
791+ . expect ( "create collision source" ) ;
792+ let collision_source_identity = collision_source. identity ( ) . expect ( "collision source id" ) ;
793+ let collision_destination = parent
794+ . create_directory ( OsStr :: new ( "collision-destination" ) )
795+ . expect ( "create collision destination" ) ;
796+ let collision_destination_identity = collision_destination
797+ . identity ( )
798+ . expect ( "collision destination id" ) ;
799+ let error = collision_source
800+ . rename_no_replace ( & parent, OsStr :: new ( "collision-destination" ) )
801+ . expect_err ( "native relative rename must not replace an existing destination" ) ;
802+ assert_eq ! ( error. kind( ) , io:: ErrorKind :: AlreadyExists ) ;
803+ assert_eq ! (
804+ parent
805+ . open_directory_read_only( OsStr :: new( "collision-source" ) )
806+ . expect( "collision source remains" )
807+ . identity( )
808+ . expect( "remaining collision source id" ) ,
809+ collision_source_identity
810+ ) ;
811+ assert_eq ! (
812+ parent
813+ . open_directory_read_only( OsStr :: new( "collision-destination" ) )
814+ . expect( "collision destination remains" )
815+ . identity( )
816+ . expect( "remaining collision destination id" ) ,
817+ collision_destination_identity
818+ ) ;
819+
820+ drop ( collision_destination) ;
821+ drop ( collision_source) ;
756822 drop ( source) ;
757823 drop ( parent) ;
758824 std:: fs:: remove_dir_all ( path) . expect ( "remove rename test directory" ) ;
0 commit comments