@@ -355,34 +355,56 @@ private static SafeFileHandle OpenHandle(string fullPath, bool asDirectory)
355355
356356 public static void RemoveDirectory ( string fullPath , bool recursive )
357357 {
358- // Do not recursively delete through reparse points.
359- if ( ! recursive || IsReparsePoint ( fullPath ) )
358+ if ( ! recursive )
360359 {
361360 RemoveDirectoryInternal ( fullPath , topLevel : true ) ;
362361 return ;
363362 }
364363
364+ Interop . Kernel32 . WIN32_FIND_DATA findData = new Interop . Kernel32 . WIN32_FIND_DATA ( ) ;
365+ GetFindData ( fullPath , ref findData ) ;
366+ if ( IsNameSurrogateReparsePoint ( ref findData ) )
367+ {
368+ // Don't recurse
369+ RemoveDirectoryInternal ( fullPath , topLevel : true ) ;
370+ return ;
371+ }
372+
365373 // We want extended syntax so we can delete "extended" subdirectories and files
366374 // (most notably ones with trailing whitespace or periods)
367375 fullPath = PathInternal . EnsureExtendedPrefix ( fullPath ) ;
368-
369- Interop . Kernel32 . WIN32_FIND_DATA findData = new Interop . Kernel32 . WIN32_FIND_DATA ( ) ;
370376 RemoveDirectoryRecursive ( fullPath , ref findData , topLevel : true ) ;
371377 }
372378
373- private static bool IsReparsePoint ( string fullPath )
379+ private static void GetFindData ( string fullPath , ref Interop . Kernel32 . WIN32_FIND_DATA findData )
374380 {
375- Interop . Kernel32 . WIN32_FILE_ATTRIBUTE_DATA data = new Interop . Kernel32 . WIN32_FILE_ATTRIBUTE_DATA ( ) ;
376- int errorCode = FillAttributeInfo ( fullPath , ref data , returnErrorOnNotFound : true ) ;
377- if ( errorCode != Interop . Errors . ERROR_SUCCESS )
381+ using ( SafeFindHandle handle = Interop . Kernel32 . FindFirstFile ( PathInternal . TrimEndingDirectorySeparator ( fullPath ) , ref findData ) )
378382 {
379- // File not found doesn't make much sense coming from a directory delete.
380- if ( errorCode == Interop . Errors . ERROR_FILE_NOT_FOUND )
381- errorCode = Interop . Errors . ERROR_PATH_NOT_FOUND ;
382- throw Win32Marshal . GetExceptionForWin32Error ( errorCode , fullPath ) ;
383+ if ( handle . IsInvalid )
384+ {
385+ int errorCode = Marshal . GetLastWin32Error ( ) ;
386+ // File not found doesn't make much sense coming from a directory delete.
387+ if ( errorCode == Interop . Errors . ERROR_FILE_NOT_FOUND )
388+ errorCode = Interop . Errors . ERROR_PATH_NOT_FOUND ;
389+ throw Win32Marshal . GetExceptionForWin32Error ( errorCode , fullPath ) ;
390+ }
383391 }
392+ }
384393
385- return ( ( ( FileAttributes ) data . dwFileAttributes & FileAttributes . ReparsePoint ) != 0 ) ;
394+ private static bool IsNameSurrogateReparsePoint ( ref Interop . Kernel32 . WIN32_FIND_DATA data )
395+ {
396+ // Name surrogates are reparse points that point to other named entities local to the file system.
397+ // Reparse points can be used for other types of files, notably OneDrive placeholder files. We
398+ // should treat reparse points that are not name surrogates as any other directory, e.g. recurse
399+ // into them. Surrogates should just be detached.
400+ //
401+ // See
402+ // https://github.com/dotnet/corefx/issues/24250
403+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365511.aspx
404+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365197.aspx
405+
406+ return ( ( FileAttributes ) data . dwFileAttributes & FileAttributes . ReparsePoint ) != 0
407+ && ( data . dwReserved0 & 0x20000000 ) != 0 ; // IsReparseTagNameSurrogate
386408 }
387409
388410 private static void RemoveDirectoryRecursive ( string fullPath , ref Interop . Kernel32 . WIN32_FIND_DATA findData , bool topLevel )
@@ -419,9 +441,10 @@ private static void RemoveDirectoryRecursive(string fullPath, ref Interop.Kernel
419441 continue ;
420442
421443 string fileName = findData . cFileName . GetStringFromFixedBuffer ( ) ;
422- if ( ( findData . dwFileAttributes & ( int ) FileAttributes . ReparsePoint ) == 0 )
444+
445+ if ( ! IsNameSurrogateReparsePoint ( ref findData ) )
423446 {
424- // Not a reparse point, recurse.
447+ // Not a reparse point, or the reparse point isn't a name surrogate, recurse.
425448 try
426449 {
427450 RemoveDirectoryRecursive (
@@ -437,7 +460,8 @@ private static void RemoveDirectoryRecursive(string fullPath, ref Interop.Kernel
437460 }
438461 else
439462 {
440- // Reparse point, don't recurse, just remove. (dwReserved0 is documented for this flag)
463+ // Name surrogate reparse point, don't recurse, simply remove the directory.
464+ // If a mount point, we have to delete the mount point first.
441465 if ( findData . dwReserved0 == Interop . Kernel32 . IOReparseOptions . IO_REPARSE_TAG_MOUNT_POINT )
442466 {
443467 // Mount point. Unmount using full path plus a trailing '\'.
0 commit comments