Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 4ea280e

Browse files
authored
Check for name surrogates when deleting reparse points (#28124)
Name surrogates are the only type of reparse points that we should be simply detaching. All other reparse points we should be drilling into- this was causing issues for OneDrive cloud files.
1 parent e2c62e0 commit 4ea280e

File tree

1 file changed

+40
-16
lines changed

1 file changed

+40
-16
lines changed

src/System.IO.FileSystem/src/System/IO/FileSystem.Windows.cs

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)