44using System ;
55using System . Diagnostics ;
66using System . IO ;
7- using System . Text ;
7+ using System . IO . Strategies ;
8+ using System . Runtime . InteropServices ;
89using System . Threading ;
910
1011namespace Microsoft . Win32 . SafeHandles
@@ -24,13 +25,6 @@ public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHand
2425 SetHandle ( preexistingHandle ) ;
2526 }
2627
27- private SafeFileHandle ( IntPtr preexistingHandle , bool ownsHandle , FileOptions fileOptions ) : base ( ownsHandle )
28- {
29- SetHandle ( preexistingHandle ) ;
30-
31- _fileOptions = fileOptions ;
32- }
33-
3428 public bool IsAsync => ( GetFileOptions ( ) & FileOptions . Asynchronous ) != 0 ;
3529
3630 internal bool CanSeek => ! IsClosed && GetFileType ( ) == Interop . Kernel32 . FileTypes . FILE_TYPE_DISK ;
@@ -43,59 +37,106 @@ internal static unsafe SafeFileHandle Open(string fullPath, FileMode mode, FileA
4337 {
4438 using ( DisableMediaInsertionPrompt . Create ( ) )
4539 {
46- SafeFileHandle fileHandle = new SafeFileHandle (
47- NtCreateFile ( fullPath , mode , access , share , options , preallocationSize ) ,
48- ownsHandle : true ,
49- options ) ;
40+ // we don't use NtCreateFile as there is no public and reliable way
41+ // of converting DOS to NT file paths (RtlDosPathNameToRelativeNtPathName_U_WithStatus is not documented)
42+ SafeFileHandle fileHandle = CreateFile ( fullPath , mode , access , share , options ) ;
43+
44+ if ( FileStreamHelpers . ShouldPreallocate ( preallocationSize , access , mode ) )
45+ {
46+ Preallocate ( fullPath , preallocationSize , fileHandle ) ;
47+ }
5048
5149 fileHandle . InitThreadPoolBindingIfNeeded ( ) ;
5250
5351 return fileHandle ;
5452 }
5553 }
5654
57- private static IntPtr NtCreateFile ( string fullPath , FileMode mode , FileAccess access , FileShare share , FileOptions options , long preallocationSize )
55+ private static unsafe SafeFileHandle CreateFile ( string fullPath , FileMode mode , FileAccess access , FileShare share , FileOptions options )
5856 {
59- uint ntStatus ;
60- IntPtr fileHandle ;
57+ Interop . Kernel32 . SECURITY_ATTRIBUTES secAttrs = default ;
58+ if ( ( share & FileShare . Inheritable ) != 0 )
59+ {
60+ secAttrs = new Interop . Kernel32 . SECURITY_ATTRIBUTES
61+ {
62+ nLength = ( uint ) sizeof ( Interop . Kernel32 . SECURITY_ATTRIBUTES ) ,
63+ bInheritHandle = Interop . BOOL . TRUE
64+ } ;
65+ }
6166
62- const string MandatoryNtPrefix = @"\??\" ;
63- if ( fullPath . StartsWith ( MandatoryNtPrefix , StringComparison . Ordinal ) )
67+ int fAccess =
68+ ( ( access & FileAccess . Read ) == FileAccess . Read ? Interop . Kernel32 . GenericOperations . GENERIC_READ : 0 ) |
69+ ( ( access & FileAccess . Write ) == FileAccess . Write ? Interop . Kernel32 . GenericOperations . GENERIC_WRITE : 0 ) ;
70+
71+ // Our Inheritable bit was stolen from Windows, but should be set in
72+ // the security attributes class. Don't leave this bit set.
73+ share &= ~ FileShare . Inheritable ;
74+
75+ // Must use a valid Win32 constant here...
76+ if ( mode == FileMode . Append )
6477 {
65- ( ntStatus , fileHandle ) = Interop . NtDll . NtCreateFile ( fullPath , mode , access , share , options , preallocationSize ) ;
78+ mode = FileMode . OpenOrCreate ;
6679 }
67- else
80+
81+ int flagsAndAttributes = ( int ) options ;
82+
83+ // For mitigating local elevation of privilege attack through named pipes
84+ // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the
85+ // named pipe server can't impersonate a high privileged client security context
86+ // (note that this is the effective default on CreateFile2)
87+ flagsAndAttributes |= ( Interop . Kernel32 . SecurityOptions . SECURITY_SQOS_PRESENT | Interop . Kernel32 . SecurityOptions . SECURITY_ANONYMOUS ) ;
88+
89+ SafeFileHandle fileHandle = Interop . Kernel32 . CreateFile ( fullPath , fAccess , share , & secAttrs , mode , flagsAndAttributes , IntPtr . Zero ) ;
90+ if ( fileHandle . IsInvalid )
6891 {
69- var vsb = new ValueStringBuilder ( stackalloc char [ 256 ] ) ;
70- vsb . Append ( MandatoryNtPrefix ) ;
92+ // Return a meaningful exception with the full path.
7193
72- if ( fullPath . StartsWith ( @"\\?\" , StringComparison . Ordinal ) ) // NtCreateFile does not support "\\?\" prefix, only "\??\"
73- {
74- vsb . Append ( fullPath . AsSpan ( 4 ) ) ;
75- }
76- else
94+ // NT5 oddity - when trying to open "C:\" as a Win32FileStream,
95+ // we usually get ERROR_PATH_NOT_FOUND from the OS. We should
96+ // probably be consistent w/ every other directory.
97+ int errorCode = Marshal . GetLastPInvokeError ( ) ;
98+
99+ if ( errorCode == Interop . Errors . ERROR_PATH_NOT_FOUND && fullPath ! . Length == PathInternal . GetRootLength ( fullPath ) )
77100 {
78- vsb . Append ( fullPath ) ;
101+ errorCode = Interop . Errors . ERROR_ACCESS_DENIED ;
79102 }
80103
81- ( ntStatus , fileHandle ) = Interop . NtDll . NtCreateFile ( vsb . AsSpan ( ) , mode , access , share , options , preallocationSize ) ;
82- vsb . Dispose ( ) ;
104+ throw Win32Marshal . GetExceptionForWin32Error ( errorCode , fullPath ) ;
83105 }
84106
85- switch ( ntStatus )
86- {
87- case Interop . StatusOptions . STATUS_SUCCESS :
88- return fileHandle ;
89- case Interop . StatusOptions . STATUS_DISK_FULL :
90- throw new IOException ( SR . Format ( SR . IO_DiskFull_Path_AllocationSize , fullPath , preallocationSize ) ) ;
91- // NtCreateFile has a bug and it reports STATUS_INVALID_PARAMETER for files
92- // that are too big for the current file system. Example: creating a 4GB+1 file on a FAT32 drive.
93- case Interop . StatusOptions . STATUS_INVALID_PARAMETER when preallocationSize > 0 :
94- case Interop . StatusOptions . STATUS_FILE_TOO_LARGE :
95- throw new IOException ( SR . Format ( SR . IO_FileTooLarge_Path_AllocationSize , fullPath , preallocationSize ) ) ;
96- default :
97- int error = ( int ) Interop . NtDll . RtlNtStatusToDosError ( ( int ) ntStatus ) ;
98- throw Win32Marshal . GetExceptionForWin32Error ( error , fullPath ) ;
107+ fileHandle . _fileOptions = options ;
108+ return fileHandle ;
109+ }
110+
111+ private static unsafe void Preallocate ( string fullPath , long preallocationSize , SafeFileHandle fileHandle )
112+ {
113+ var allocationInfo = new Interop . Kernel32 . FILE_ALLOCATION_INFO
114+ {
115+ AllocationSize = preallocationSize
116+ } ;
117+
118+ if ( ! Interop . Kernel32 . SetFileInformationByHandle (
119+ fileHandle ,
120+ Interop . Kernel32 . FileAllocationInfo ,
121+ & allocationInfo ,
122+ ( uint ) sizeof ( Interop . Kernel32 . FILE_ALLOCATION_INFO ) ) )
123+ {
124+ int errorCode = Marshal . GetLastPInvokeError ( ) ;
125+
126+ // we try to mimic the atomic NtCreateFile here:
127+ // if preallocation fails, close the handle and delete the file
128+ fileHandle . Dispose ( ) ;
129+ Interop . Kernel32 . DeleteFile ( fullPath ) ;
130+
131+ switch ( errorCode )
132+ {
133+ case Interop . Errors . ERROR_DISK_FULL :
134+ throw new IOException ( SR . Format ( SR . IO_DiskFull_Path_AllocationSize , fullPath , preallocationSize ) ) ;
135+ case Interop . Errors . ERROR_FILE_TOO_LARGE :
136+ throw new IOException ( SR . Format ( SR . IO_FileTooLarge_Path_AllocationSize , fullPath , preallocationSize ) ) ;
137+ default :
138+ throw Win32Marshal . GetExceptionForWin32Error ( errorCode , fullPath ) ;
139+ }
99140 }
100141 }
101142
0 commit comments