@@ -181,14 +181,9 @@ public static unsafe bool TryGetDirectoryContents_Internal(string dirPath, List<
181181
182182 var isDirectory = ( info ->FileAttributes & FileAttributes . Directory ) > 0 ;
183183
184- // Skip symlinks/junctions that point elsewhere (prevents infinite recursion).
185- // Cloud folders (e.g. OneDrive) use different reparse tags, so they are
186- // treated as normal directories and enumerated. Only checked for directories;
187- // files with reparse points are always enumerated.
188- if ( isDirectory && ( info ->FileAttributes & FileAttributes . ReparsePoint ) != 0 &&
189- IsNameSurrogateReparsePoint ( $@ "{ originalDirPath } \{ fileName } ") )
190- goto nextfile ;
191-
184+ // Reparse points (junctions/symlinks/cloud placeholders) are followed as
185+ // normal directories. Infinite recursion via junction cycles is bounded by
186+ // a recursion-depth cap in the caller (IOEx.GetFilesEx).
192187 if ( isDirectory )
193188 {
194189 directories . Add ( new DirectoryInformation
@@ -222,63 +217,6 @@ public static unsafe bool TryGetDirectoryContents_Internal(string dirPath, List<
222217 return true ;
223218 }
224219
225- /// <summary>
226- /// Returns true if the reparse point at <paramref name="fullPath"/> is a 'name surrogate'
227- /// (a symlink or junction/mount-point that redirects the path). Such entries are skipped
228- /// during enumeration to prevent following them into cycles.
229- ///
230- /// Cloud providers (e.g. OneDrive) also use reparse points on real, locally-available
231- /// folders; those tags do not carry the name-surrogate bit, so this returns false for them
232- /// and the directory is enumerated normally.
233- ///
234- /// On any failure to determine the tag we conservatively return false (do not skip), since
235- /// wrongly skipping a real directory is the original bug this guards against. A genuine
236- /// symlink whose tag cannot be read would still be bounded by <c>maxDepth</c>.
237- /// </summary>
238- private static unsafe bool IsNameSurrogateReparsePoint ( string fullPath )
239- {
240- const uint FSCTL_GET_REPARSE_POINT = 0x000900A8 ;
241-
242- // Bit set in the reparse tag of entries that substitute/redirect the name
243- // (symlinks, junctions/mount-points). Cloud reparse tags do not set this bit.
244- const uint REPARSE_TAG_NAME_SURROGATE = 0x20000000 ;
245-
246- const uint GENERIC_READ = 0x80000000 ;
247- const uint FILE_SHARE_READ = 0x00000001 ;
248- const uint FILE_SHARE_WRITE = 0x00000002 ;
249- const uint OPEN_EXISTING = 3 ;
250-
251- // BACKUP_SEMANTICS allows opening directories; OPEN_REPARSE_POINT opens the
252- // reparse entry itself instead of resolving through it.
253- const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 ;
254- const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 ;
255-
256- const int BufferSize = 1024 * 16 ;
257-
258- var handle = CreateFileW ( fullPath , GENERIC_READ , FILE_SHARE_READ | FILE_SHARE_WRITE ,
259- IntPtr . Zero , OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT , IntPtr . Zero ) ;
260-
261- if ( handle == new IntPtr ( - 1 ) )
262- return false ;
263-
264- try
265- {
266- byte * buffer = stackalloc byte [ BufferSize ] ;
267- if ( ! DeviceIoControl ( handle , FSCTL_GET_REPARSE_POINT , IntPtr . Zero , 0 ,
268- ( IntPtr ) buffer , BufferSize , out _ , IntPtr . Zero ) )
269- {
270- return false ;
271- }
272-
273- // REPARSE_DATA_BUFFER.ReparseTag is the first DWORD.
274- return ( * ( uint * ) buffer & REPARSE_TAG_NAME_SURROGATE ) != 0 ;
275- }
276- finally
277- {
278- CloseHandle ( handle ) ;
279- }
280- }
281-
282220 internal struct MultithreadedDirectorySearcher : IDisposable
283221 {
284222 private Thread [ ] _threads ;
@@ -376,17 +314,6 @@ public void Dispose()
376314
377315 [ DllImport ( "kernel32.dll" , CharSet = CharSet . Ansi ) ]
378316 static extern IntPtr GetProcAddress ( IntPtr hModule , string procName ) ;
379-
380- [ DllImport ( "kernel32.dll" , CharSet = CharSet . Unicode , SetLastError = true ) ]
381- internal static extern IntPtr CreateFileW ( string lpFileName , uint dwDesiredAccess , uint dwShareMode , IntPtr lpSecurityAttributes , uint dwCreationDisposition , uint dwFlagsAndAttributes , IntPtr hTemplateFile ) ;
382-
383- [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
384- [ return : MarshalAs ( UnmanagedType . Bool ) ]
385- internal static extern bool DeviceIoControl ( IntPtr hDevice , uint dwIoControlCode , IntPtr lpInBuffer , uint nInBufferSize , IntPtr lpOutBuffer , uint nOutBufferSize , out uint lpBytesReturned , IntPtr lpOverlapped ) ;
386-
387- [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
388- [ return : MarshalAs ( UnmanagedType . Bool ) ]
389- internal static extern bool CloseHandle ( IntPtr hObject ) ;
390317 #endregion
391318
392319 #region Native Import Wrappers
0 commit comments