[System.Security.SecurityCritical] // auto-generated private static void RemoveDirectoryHelper(string fullPath, bool recursive, bool throwOnTopLevelDirectoryNotFound) { bool r; int errorCode; Exception ex = null; // Do not recursively delete through reparse points. Perhaps in a // future version we will add a new flag to control this behavior, // but for now we're much safer if we err on the conservative side. // This applies to symbolic links and mount points. // Note the logic to check whether fullPath is a reparse point is // in Delete(String, String, bool), and will set "recursive" to false. // Note that Win32's DeleteFile and RemoveDirectory will just delete // the reparse point itself. if (recursive) { Interop.mincore.WIN32_FIND_DATA data = new Interop.mincore.WIN32_FIND_DATA(); // Open a Find handle using (SafeFindHandle hnd = Interop.mincore.FindFirstFile(Directory.EnsureTrailingDirectorySeparator(fullPath) + "*", ref data)) { if (hnd.IsInvalid) { throw Win32Marshal.GetExceptionForLastWin32Error(fullPath); } do { bool isDir = (0 != (data.dwFileAttributes & Interop.mincore.FileAttributes.FILE_ATTRIBUTE_DIRECTORY)); if (isDir) { // Skip ".", "..". if (data.cFileName.Equals(".") || data.cFileName.Equals("..")) { continue; } // Recurse for all directories, unless they are // reparse points. Do not follow mount points nor // symbolic links, but do delete the reparse point // itself. bool shouldRecurse = (0 == (data.dwFileAttributes & (int)FileAttributes.ReparsePoint)); if (shouldRecurse) { string newFullPath = Path.Combine(fullPath, data.cFileName); try { RemoveDirectoryHelper(newFullPath, recursive, false); } catch (Exception e) { if (ex == null) { ex = e; } } } else { // Check to see if this is a mount point, and // unmount it. if (data.dwReserved0 == Interop.mincore.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) { // Use full path plus a trailing '\' String mountPoint = Path.Combine(fullPath, data.cFileName + PathHelpers.DirectorySeparatorCharAsString); if (!Interop.mincore.DeleteVolumeMountPoint(mountPoint)) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.mincore.Errors.ERROR_SUCCESS && errorCode != Interop.mincore.Errors.ERROR_PATH_NOT_FOUND) { try { throw Win32Marshal.GetExceptionForWin32Error(errorCode, data.cFileName); } catch (Exception e) { if (ex == null) { ex = e; } } } } } // RemoveDirectory on a symbolic link will // remove the link itself. String reparsePoint = Path.Combine(fullPath, data.cFileName); r = Interop.mincore.RemoveDirectory(reparsePoint); if (!r) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.mincore.Errors.ERROR_PATH_NOT_FOUND) { try { throw Win32Marshal.GetExceptionForWin32Error(errorCode, data.cFileName); } catch (Exception e) { if (ex == null) { ex = e; } } } } } } else { String fileName = Path.Combine(fullPath, data.cFileName); r = Interop.mincore.DeleteFile(fileName); if (!r) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.mincore.Errors.ERROR_FILE_NOT_FOUND) { try { throw Win32Marshal.GetExceptionForWin32Error(errorCode, data.cFileName); } catch (Exception e) { if (ex == null) { ex = e; } } } } } } while (Interop.mincore.FindNextFile(hnd, ref data)); // Make sure we quit with a sensible error. errorCode = Marshal.GetLastWin32Error(); } if (ex != null) { throw ex; } if (errorCode != 0 && errorCode != Interop.mincore.Errors.ERROR_NO_MORE_FILES) { throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } } r = Interop.mincore.RemoveDirectory(fullPath); if (!r) { errorCode = Marshal.GetLastWin32Error(); if (errorCode == Interop.mincore.Errors.ERROR_FILE_NOT_FOUND) // A dubious error code. { errorCode = Interop.mincore.Errors.ERROR_PATH_NOT_FOUND; } // This check was originally put in for Win9x (unfortunately without special casing it to be for Win9x only). We can't change the NT codepath now for backcomp reasons. if (errorCode == Interop.mincore.Errors.ERROR_ACCESS_DENIED) { throw new IOException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, fullPath)); } // don't throw the DirectoryNotFoundException since this is a subdir and // there could be a race condition between two Directory.Delete callers if (errorCode == Interop.mincore.Errors.ERROR_PATH_NOT_FOUND && !throwOnTopLevelDirectoryNotFound) { return; } throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } }
private static void RemoveDirectoryRecursive(string fullPath, ref Interop.Kernel32.WIN32_FIND_DATA findData, bool topLevel) { int errorCode; Exception exception = null; using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(Directory.EnsureTrailingDirectorySeparator(fullPath) + "*", ref findData)) { if (handle.IsInvalid) { throw Win32Marshal.GetExceptionForLastWin32Error(fullPath); } do { if ((findData.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) == 0) { // File string fileName = findData.cFileName.GetStringFromFixedBuffer(); if (!Interop.Kernel32.DeleteFile(Path.Combine(fullPath, fileName)) && exception == null) { errorCode = Marshal.GetLastWin32Error(); // We don't care if something else deleted the file first if (errorCode != Interop.Errors.ERROR_FILE_NOT_FOUND) { exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); } } } else { // Directory, skip ".", "..". if (findData.cFileName.FixedBufferEqualsString(".") || findData.cFileName.FixedBufferEqualsString("..")) { continue; } string fileName = findData.cFileName.GetStringFromFixedBuffer(); if ((findData.dwFileAttributes & (int)FileAttributes.ReparsePoint) == 0) { // Not a reparse point, recurse. try { RemoveDirectoryRecursive( Path.Combine(fullPath, fileName), findData: ref findData, topLevel: false); } catch (Exception e) { if (exception == null) { exception = e; } } } else { // Reparse point, don't recurse, just remove. (dwReserved0 is documented for this flag) 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.Combine(fullPath, fileName + PathHelpers.DirectorySeparatorCharAsString); if (!Interop.Kernel32.DeleteVolumeMountPoint(mountPoint) && exception == null) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_SUCCESS && errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) { exception = 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) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND) { exception = Win32Marshal.GetExceptionForWin32Error(errorCode, fileName); } } } } } while (Interop.Kernel32.FindNextFile(handle, ref findData)); if (exception != null) { throw exception; } errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_SUCCESS && errorCode != Interop.Errors.ERROR_NO_MORE_FILES) { throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } } // As we successfully removed all of the files we shouldn't care about the directory itself // not being empty. As file deletion is just a marker to remove the file when all handles // are closed we could still have contents hanging around. RemoveDirectoryInternal(fullPath, topLevel: topLevel, allowDirectoryNotEmpty: true); }