@@ -56,101 +56,6 @@ private static unsafe void ThrowExceptionEncryptDecryptFail(string fullPath)
5656 throw Win32Marshal . GetExceptionForWin32Error ( errorCode , fullPath ) ;
5757 }
5858
59- private static SafeFileHandle ? TryOpenTargetNoAlternativeDataStream (
60- string lpFileName ,
61- int dwDesiredAccess ,
62- FileShare dwShareMode ,
63- FileMode dwCreationDisposition ,
64- int dwFlagsAndAttributes ,
65- ref Interop . Kernel32 . WIN32_FILE_ATTRIBUTE_DATA fileInformation ,
66- out string openedFileName )
67- {
68- // Default value for openedFileName.
69- openedFileName = lpFileName ;
70-
71- // Check the file isn't an alternative data stream
72- if ( Path . GetFileName ( lpFileName . AsSpan ( ) ) . Contains ( ':' ) )
73- {
74- return null ;
75- }
76-
77- // Try to open it in a way where we can detect symlinks.
78- SafeFileHandle result = Interop . Kernel32 . CreateFile (
79- lpFileName ,
80- dwDesiredAccess ,
81- dwShareMode ,
82- dwCreationDisposition ,
83- dwFlagsAndAttributes | Interop . Kernel32 . FileOperations . FILE_FLAG_OPEN_REPARSE_POINT
84- ) ;
85-
86- // Check we successfully opened it.
87- if ( result . IsInvalid )
88- {
89- return null ;
90- }
91-
92- // Read the file attributes.
93- if ( ! Interop . Kernel32 . GetFileAttributesEx ( lpFileName , Interop . Kernel32 . GET_FILEEX_INFO_LEVELS . GetFileExInfoStandard , ref fileInformation ) )
94- {
95- result . Dispose ( ) ;
96- return null ;
97- }
98-
99- // Deal with the case of a reparse point.
100- if ( ( fileInformation . dwFileAttributes & Interop . Kernel32 . FileAttributes . FILE_ATTRIBUTE_REPARSE_POINT ) != 0 )
101- {
102- // Get the path of the target.
103- result . Dispose ( ) ;
104- //try
105- //{
106- /* */ string ? target = GetFinalLinkTarget ( lpFileName , false ) ;
107- //}
108- //catch
109- //{
110- // return false;
111- //}
112-
113- // Deal with no target
114- if ( target == null )
115- {
116- return null ;
117- }
118-
119- // Check the target's file name for alternative data streams.
120- if ( Path . GetFileName ( target . AsSpan ( ) ) . Contains ( ':' ) )
121- {
122- return null ;
123- }
124-
125- // Open the target.
126- result = Interop . Kernel32 . CreateFile (
127- target ,
128- dwDesiredAccess ,
129- dwShareMode ,
130- dwCreationDisposition ,
131- dwFlagsAndAttributes | Interop . Kernel32 . FileOperations . FILE_FLAG_OPEN_REPARSE_POINT
132- ) ;
133- openedFileName = target ;
134-
135- // Read the file attributes.
136- if ( ! Interop . Kernel32 . GetFileAttributesEx ( lpFileName , Interop . Kernel32 . GET_FILEEX_INFO_LEVELS . GetFileExInfoStandard , ref fileInformation ) )
137- {
138- result . Dispose ( ) ;
139- return null ;
140- }
141-
142- // Check we haven't somehow ended up with another symlink due to things changing very quickly.
143- if ( ( fileInformation . dwFileAttributes & Interop . Kernel32 . FileAttributes . FILE_ATTRIBUTE_REPARSE_POINT ) != 0 )
144- {
145- result . Dispose ( ) ;
146- return null ;
147- }
148- }
149-
150- // Return our result.
151- return result ;
152- }
153-
15459 private static unsafe bool TryCloneFile ( string sourceFullPath , string destFullPath , bool overwrite )
15560 {
15661 // Check the destination file isn't an alternative data stream (unsupported, and crashes on up to some versions of Windows 11).
@@ -163,20 +68,12 @@ private static unsafe bool TryCloneFile(string sourceFullPath, string destFullPa
16368 // Open the source file.
16469 // We use FILE_FLAGS_NO_BUFFERING since we're not using unaligned writes during cloning and can skip buffering overhead.
16570 const int FILE_FLAG_NO_BUFFERING = 0x20000000 ;
166- Interop . Kernel32 . WIN32_FILE_ATTRIBUTE_DATA sourceFileInformation = default ;
167- SafeFileHandle ? _sourceHandle = TryOpenTargetNoAlternativeDataStream (
71+ using ( SafeFileHandle sourceHandle = Interop . Kernel32 . CreateFile (
16872 sourceFullPath ,
16973 Interop . Kernel32 . GenericOperations . GENERIC_READ ,
17074 FileShare . Read ,
17175 FileMode . Open ,
172- FILE_FLAG_NO_BUFFERING ,
173- ref sourceFileInformation ,
174- out string openedSourceFullPath ) ;
175- if ( _sourceHandle == null )
176- {
177- return false ;
178- }
179- using ( SafeFileHandle sourceHandle = _sourceHandle )
76+ FILE_FLAG_NO_BUFFERING ) )
18077 {
18178 // Return false if we failed to open the source file.
18279 if ( sourceHandle . IsInvalid )
@@ -200,9 +97,6 @@ private static unsafe bool TryCloneFile(string sourceFullPath, string destFullPa
20097 //return false;
20198 }
20299
203- // Get file size.
204- long sourceSize = ( long ) ( ( ( ulong ) sourceFileInformation . nFileSizeHigh << 32 ) | sourceFileInformation . nFileSizeLow ) ;
205-
206100 // Helper variables.
207101 SafeFileHandle ? destinationHandle = null ;
208102 bool madeNew = false ;
@@ -214,19 +108,12 @@ private static unsafe bool TryCloneFile(string sourceFullPath, string destFullPa
214108 if ( overwrite )
215109 {
216110 // Try to open the existing file.
217- destinationHandle = TryOpenTargetNoAlternativeDataStream (
111+ destinationHandle = Interop . Kernel32 . CreateFile (
218112 destFullPath ,
219113 Interop . Kernel32 . GenericOperations . GENERIC_READ | Interop . Kernel32 . GenericOperations . GENERIC_WRITE ,
220114 FileShare . None ,
221115 FileMode . Open ,
222- 0 ,
223- ref destFileInformation ,
224- out _ ) ;
225- if ( destinationHandle == null )
226- {
227- // Alternative data stream.
228- return false ;
229- }
116+ 0 ) ;
230117 if ( destinationHandle . IsInvalid )
231118 {
232119 // Try opening as a new file.
@@ -238,19 +125,12 @@ private static unsafe bool TryCloneFile(string sourceFullPath, string destFullPa
238125 if ( destinationHandle == null )
239126 {
240127 // Try to create the destination file.
241- destinationHandle = TryOpenTargetNoAlternativeDataStream (
128+ destinationHandle = Interop . Kernel32 . CreateFile (
242129 destFullPath ,
243- Interop . Kernel32 . GenericOperations . GENERIC_READ | Interop . Kernel32 . GenericOperations . GENERIC_WRITE ,
130+ Interop . Kernel32 . GenericOperations . GENERIC_READ | Interop . Kernel32 . GenericOperations . GENERIC_WRITE | Interop . Kernel32 . GenericOperations . DELETE ,
244131 FileShare . None ,
245132 FileMode . CreateNew ,
246- 0 ,
247- ref destFileInformation ,
248- out _ ) ;
249- if ( destinationHandle == null )
250- {
251- // Alternative data stream.
252- return false ;
253- }
133+ 0 ) ;
254134 if ( destinationHandle . IsInvalid )
255135 {
256136 // Failure to open.
@@ -283,9 +163,34 @@ private static unsafe bool TryCloneFile(string sourceFullPath, string destFullPa
283163 //return false;
284164 }
285165
286- // Get the source volume path. Note: we need the real path here for symlinks also, hence openedSourceFullPath.
166+ // Get the source file path. We need to do this, because we may have opened a symlink - this returns the file we actually have open.
167+ if ( ! Interop . Kernel32 . GetFinalPathNameByHandle ( sourceHandle , Interop . Kernel32 . FILE_NAME_NORMALIZED | Interop . Kernel32 . VOLUME_NAME_DOS , out string ? sourceName ) )
168+ {
169+ // This may fail as a result of it not being on a drive with a DOS path, we shouldn't throw here
170+ throw new Exception ( "I2" ) ;
171+ //return false;
172+ }
173+
174+ // Check we haven't opened an alternate data stream - this may cause a BSOD on Windows versions lower than TBD (todo) if we don't exit before block copy.
175+ if ( Path . GetFileName ( sourceName . AsSpan ( ) ) . Contains ( ':' ) )
176+ {
177+ return false ;
178+ }
179+
180+ // Also check the destination file
181+ if ( ! Interop . Kernel32 . GetFinalPathNameByHandle ( destinationHandle , Interop . Kernel32 . FILE_NAME_NORMALIZED | Interop . Kernel32 . VOLUME_NAME_DOS , out string ? destName ) )
182+ {
183+ throw new Exception ( "I3" ) ;
184+ //return false;
185+ }
186+ if ( Path . GetFileName ( destName . AsSpan ( ) ) . Contains ( ':' ) )
187+ {
188+ return false ;
189+ }
190+
191+ // Get the source volume path. Note: we need the real path here for symlinks also, hence sourceName.
287192 // Todo: do we care about not propogating an error from this?
288- string volumePath = Interop . Kernel32 . GetVolumePathName ( openedSourceFullPath ) ;
193+ string volumePath = Interop . Kernel32 . GetVolumePathName ( sourceName ) ;
289194
290195 // Read the source volume's cluster size.
291196 if ( ! Interop . Kernel32 . GetDiskFreeSpace ( volumePath ! , out int sectorsPerCluster , out int bytesPerCluster , out _ , out _ ) )
@@ -364,6 +269,14 @@ private static unsafe bool TryCloneFile(string sourceFullPath, string destFullPa
364269 }
365270 }
366271
272+ // Read the file attributes.
273+ Interop . Kernel32 . WIN32_FILE_ATTRIBUTE_DATA sourceFileInformation = default ;
274+ int error = FillAttributeInfo ( sourceHandle , ref sourceFileInformation ) ;
275+ Debug . Assert ( error == 0 ) ; //todo: is there a good reason for this to fail? if so we should handle it properly, not by a Debug.Assert
276+
277+ // Get file size.
278+ long sourceSize = ( long ) ( ( ( ulong ) sourceFileInformation . nFileSizeHigh << 32 ) | sourceFileInformation . nFileSizeLow ) ;
279+
367280 // Set length of destination to same as source.
368281 Interop . Kernel32 . FILE_END_OF_FILE_INFO eofInfo ;
369282 eofInfo . EndOfFile = sourceSize ;
0 commit comments