Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -357,32 +357,45 @@ private static void RemoveDirectoryRecursive(string fullPath, ref Interop.Kernel
else
{
// Name surrogate reparse point, don't recurse, simply remove the directory.
// If a mount point, we have to delete the mount point first.
// If a volume mount point, we have to delete the mount point first.
// Note that IO_REPARSE_TAG_MOUNT_POINT is used for both volume mount points
// and directory junctions. DeleteVolumeMountPoint only works for volume mount
// points; for directory junctions, RemoveDirectory handles removal directly.
Exception? mountPointException = null;
if (findData.dwReserved0 == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT)
{
// Mount point. Unmount using full path plus a trailing '\'.
// (Note: This doesn't remove the underlying directory)
string mountPoint = Path.Join(fullPath, fileName, PathInternal.DirectorySeparatorCharAsString);
if (!Interop.Kernel32.DeleteVolumeMountPoint(mountPoint) && exception == null)
if (!Interop.Kernel32.DeleteVolumeMountPoint(mountPoint))
{
errorCode = Marshal.GetLastPInvokeError();
if (errorCode != Interop.Errors.ERROR_SUCCESS &&
errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND)
{
exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName);
mountPointException = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName);
}
}
}

// Note that RemoveDirectory on a symbolic link will remove the link itself.
if (!Interop.Kernel32.RemoveDirectory(Path.Combine(fullPath, fileName)) && exception == null)
if (!Interop.Kernel32.RemoveDirectory(Path.Combine(fullPath, fileName)))
{
errorCode = Marshal.GetLastPInvokeError();
if (errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND)
if (exception == null)
{
exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName);
errorCode = Marshal.GetLastPInvokeError();
if (errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND)
{
// For a true volume mount point, use its error (it indicates why the
// unmount step failed). If this is a directory junction, RemoveDirectory
// succeeds and this code path is not reached.
exception = mountPointException ?? Win32Marshal.GetExceptionForWin32Error(errorCode, fileName);
}
}
}
// If RemoveDirectory succeeded, mountPointException is discarded. This correctly
// handles directory junctions: DeleteVolumeMountPoint fails for them (since they
// are not volume mount points), but RemoveDirectory removes them successfully.
}
}
} while (Interop.Kernel32.FindNextFile(handle, ref findData));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,33 @@ namespace System.IO.Tests
{
public partial class Directory_Delete_str_bool : Directory_Delete_str
{
[Fact]
[PlatformSpecific(TestPlatforms.Windows)]
public void RecursiveDelete_DirectoryContainingJunction()
{
// Junctions (NTFS directory junctions) share the IO_REPARSE_TAG_MOUNT_POINT reparse
// tag with volume mount points, but DeleteVolumeMountPoint only works for volume mount
// points. Ensure that recursive delete succeeds when the directory contains a junction.
string target = GetTestFilePath();
Directory.CreateDirectory(target);

string linkParent = GetTestFilePath();
Directory.CreateDirectory(linkParent);

string junctionPath = Path.Combine(linkParent, GetTestFileName());
Assert.True(MountHelper.CreateJunction(junctionPath, target));

// Both the junction and the target exist before deletion
Assert.True(Directory.Exists(junctionPath), "junction should exist before delete");
Assert.True(Directory.Exists(target), "target should exist before delete");

// Recursive delete of the parent should succeed and remove the junction without following it
Delete(linkParent, recursive: true);

Assert.False(Directory.Exists(linkParent), "parent should be deleted");
Assert.True(Directory.Exists(target), "target should still exist after deleting junction");
}

[Fact]
[PlatformSpecific(TestPlatforms.Windows)]
public void RecursiveDelete_NoListDirectoryPermission() // https://github.com/dotnet/runtime/issues/56922
Expand Down