// ==++== // // Copyright (c) Microsoft Corporation. All rights reserved. // // ==--==g /*============================================================ ** ** Class: File ** ** Microsoft ** ** ** Purpose: Long paths ** ===========================================================*/ using System; using System.Security.Permissions; using PermissionSet = System.Security.PermissionSet; using Win32Native = Microsoft.Win32.Win32Native; using System.Runtime.InteropServices; using System.Security; #if FEATURE_MACL using System.Security.AccessControl; #endif using System.Text; using Microsoft.Win32.SafeHandles; using System.Collections.Generic; using System.Globalization; using System.Runtime.Versioning; using System.Diagnostics.Contracts; namespace System.IO { [ComVisible(false)] static class LongPath { [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal unsafe static String NormalizePath(String path) { Contract.Requires(path != null); return NormalizePath(path, true); } [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal unsafe static String NormalizePath(String path, bool fullCheck) { Contract.Requires(path != null); return Path.NormalizePath(path, fullCheck, Path.MaxLongPath); } internal static String InternalCombine(String path1, String path2) { Contract.Requires(path1 != null); Contract.Requires(path2 != null); Contract.Requires(path2.Length != 0); Contract.Requires(!IsPathRooted(path2)); bool removedPrefix; String tempPath1 = TryRemoveLongPathPrefix(path1, out removedPrefix); String tempResult = Path.InternalCombine(tempPath1, path2); if (removedPrefix) { tempResult = Path.AddLongPathPrefix(tempResult); } return tempResult; } internal static int GetRootLength(String path) { bool removedPrefix; String tempPath = TryRemoveLongPathPrefix(path, out removedPrefix); int root = Path.GetRootLength(tempPath); if (removedPrefix) { root += 4; } return root; } // Tests if the given path contains a root. A path is considered rooted // if it starts with a backslash ("\") or a drive letter and a colon (":"). // [Pure] internal static bool IsPathRooted(String path) { Contract.Requires(path != null); String tempPath = Path.RemoveLongPathPrefix(path); return Path.IsPathRooted(tempPath); } // Returns the root portion of the given path. The resulting string // consists of those rightmost characters of the path that constitute the // root of the path. Possible patterns for the resulting string are: An // empty string (a relative path on the current drive), "\" (an absolute // path on the current drive), "X:" (a relative path on a given drive, // where X is the drive letter), "X:\" (an absolute path on a given drive), // and "\\server\share" (a UNC path for a given server and share name). // The resulting string is null if path is null. // [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal static String GetPathRoot(String path) { if (path == null) return null; bool removedPrefix; String tempPath = TryRemoveLongPathPrefix(path, out removedPrefix); tempPath = NormalizePath(tempPath, false); String result = path.Substring(0, GetRootLength(tempPath)); if (removedPrefix) { result = Path.AddLongPathPrefix(result); } return result; } // Returns the directory path of a file path. This method effectively // removes the last element of the given file path, i.e. it returns a // string consisting of all characters up to but not including the last // backslash ("\") in the file path. The returned value is null if the file // path is null or if the file path denotes a root (such as "\", "C:", or // "\\server\share"). [System.Security.SecurityCritical] // auto-generated [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] internal static String GetDirectoryName(String path) { if (path != null) { bool removedPrefix; String tempPath = TryRemoveLongPathPrefix(path, out removedPrefix); Path.CheckInvalidPathChars(tempPath); path = NormalizePath(tempPath, false); int root = GetRootLength(tempPath); int i = tempPath.Length; if (i > root) { i = tempPath.Length; if (i == root) return null; while (i > root && tempPath[--i] != Path.DirectorySeparatorChar && tempPath[i] != Path.AltDirectorySeparatorChar); String result = tempPath.Substring(0, i); if (removedPrefix) { result = Path.AddLongPathPrefix(result); } return result; } } return null; } internal static String TryRemoveLongPathPrefix(String path, out bool removed) { Contract.Requires(path != null); removed = Path.HasLongPathPrefix(path); if (!removed) return path; return Path.RemoveLongPathPrefix(path); } } [ComVisible(false)] static class LongPathFile { // Copies an existing file to a new file. If overwrite is // false, then an IOException is thrown if the destination file // already exists. If overwrite is true, the file is // overwritten. // // The caller must have certain FileIOPermissions. The caller must have // Read permission to sourceFileName // and Write permissions to destFileName. // [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal static void Copy(String sourceFileName, String destFileName, bool overwrite) { Contract.Requires(sourceFileName != null); Contract.Requires(destFileName != null); Contract.Requires(sourceFileName.Length > 0); Contract.Requires(destFileName.Length > 0); String fullSourceFileName = LongPath.NormalizePath(sourceFileName); FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, fullSourceFileName, false, false); String fullDestFileName = LongPath.NormalizePath(destFileName); FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, fullDestFileName, false, false); InternalCopy(fullSourceFileName, fullDestFileName, sourceFileName, destFileName, overwrite); } [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] private static String InternalCopy(String fullSourceFileName, String fullDestFileName, String sourceFileName, String destFileName, bool overwrite) { Contract.Requires(fullSourceFileName != null); Contract.Requires(fullDestFileName != null); Contract.Requires(fullSourceFileName.Length > 0); Contract.Requires(fullDestFileName.Length > 0); fullSourceFileName = Path.AddLongPathPrefix(fullSourceFileName); fullDestFileName = Path.AddLongPathPrefix(fullDestFileName); bool r = Win32Native.CopyFile(fullSourceFileName, fullDestFileName, !overwrite); if (!r) { // Save Win32 error because subsequent checks will overwrite this HRESULT. int errorCode = Marshal.GetLastWin32Error(); String fileName = destFileName; if (errorCode != Win32Native.ERROR_FILE_EXISTS) { // For a number of error codes (sharing violation, path // not found, etc) we don't know if the problem was with // the source or dest file. Try reading the source file. using(SafeFileHandle handle = Win32Native.UnsafeCreateFile(fullSourceFileName, FileStream.GENERIC_READ, FileShare.Read, null, FileMode.Open, 0, IntPtr.Zero)) { if (handle.IsInvalid) fileName = sourceFileName; } if (errorCode == Win32Native.ERROR_ACCESS_DENIED) { if (LongPathDirectory.InternalExists(fullDestFileName)) throw new IOException(Environment.GetResourceString("Arg_FileIsDirectory_Name", destFileName), Win32Native.ERROR_ACCESS_DENIED, fullDestFileName); } } __Error.WinIOError(errorCode, fileName); } return fullDestFileName; } // Deletes a file. The file specified by the designated path is deleted. // If the file does not exist, Delete succeeds without throwing // an exception. // // On NT, Delete will fail for a file that is open for normal I/O // or a file that is memory mapped. // // Your application must have Delete permission to the target file. // [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal static void Delete(String path) { Contract.Requires(path != null); String fullPath = LongPath.NormalizePath(path); // For security check, path should be resolved to an absolute path. FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, fullPath, false, false); String tempPath = Path.AddLongPathPrefix(fullPath); bool r = Win32Native.DeleteFile(tempPath); if (!r) { int hr = Marshal.GetLastWin32Error(); if (hr==Win32Native.ERROR_FILE_NOT_FOUND) return; else __Error.WinIOError(hr, fullPath); } } // Tests if a file exists. The result is true if the file // given by the specified path exists; otherwise, the result is // false. Note that if path describes a directory, // Exists will return true. // // Your application must have Read permission for the target directory. // [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal static bool Exists(String path) { try { if (path==null) return false; if (path.Length==0) return false; path = LongPath.NormalizePath(path); // After normalizing, check whether path ends in directory separator. // Otherwise, FillAttributeInfo removes it and we may return a false positive. // GetFullPathInternal should never return null Contract.Assert(path != null, "File.Exists: GetFullPathInternal returned null"); if (path.Length > 0 && Path.IsDirectorySeparator(path[path.Length - 1])) { return false; } FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, path, false, false ); return InternalExists(path); } catch(ArgumentException) {} catch(NotSupportedException) {} // Security can throw this on ":" catch(SecurityException) {} catch(IOException) {} catch(UnauthorizedAccessException) {} return false; } [System.Security.SecurityCritical] internal static bool InternalExists(String path) { Contract.Requires(path != null); String tempPath = Path.AddLongPathPrefix(path); return File.InternalExists(tempPath); } [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal static DateTimeOffset GetCreationTime(String path) { Contract.Requires(path != null); String fullPath = LongPath.NormalizePath(path); FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, fullPath, false, false); String tempPath = Path.AddLongPathPrefix(fullPath); Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); int dataInitialised = File.FillAttributeInfo(tempPath, ref data, false, false); if (dataInitialised != 0) __Error.WinIOError(dataInitialised, fullPath); long dt = ((long)(data.ftCreationTimeHigh) << 32) | ((long)data.ftCreationTimeLow); DateTime dtLocal = DateTime.FromFileTimeUtc(dt).ToLocalTime(); return new DateTimeOffset(dtLocal).ToLocalTime(); } [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal static DateTimeOffset GetLastAccessTime(String path) { Contract.Requires(path != null); String fullPath = LongPath.NormalizePath(path); FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, fullPath, false, false); String tempPath = Path.AddLongPathPrefix(fullPath); Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); int dataInitialised = File.FillAttributeInfo(tempPath, ref data, false, false); if (dataInitialised != 0) __Error.WinIOError(dataInitialised, fullPath); long dt = ((long)(data.ftLastAccessTimeHigh) << 32) | ((long)data.ftLastAccessTimeLow); DateTime dtLocal = DateTime.FromFileTimeUtc(dt).ToLocalTime(); return new DateTimeOffset(dtLocal).ToLocalTime(); } [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal static DateTimeOffset GetLastWriteTime(String path) { Contract.Requires(path != null); String fullPath = LongPath.NormalizePath(path); FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, fullPath, false, false); String tempPath = Path.AddLongPathPrefix(fullPath); Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); int dataInitialised = File.FillAttributeInfo(tempPath, ref data, false, false); if (dataInitialised != 0) __Error.WinIOError(dataInitialised, fullPath); long dt = ((long)data.ftLastWriteTimeHigh << 32) | ((long)data.ftLastWriteTimeLow); DateTime dtLocal = DateTime.FromFileTimeUtc(dt).ToLocalTime(); return new DateTimeOffset(dtLocal).ToLocalTime(); } // Moves a specified file to a new location and potentially a new file name. // This method does work across volumes. // // The caller must have certain FileIOPermissions. The caller must // have Read and Write permission to // sourceFileName and Write // permissions to destFileName. // [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal static void Move(String sourceFileName, String destFileName) { Contract.Requires(sourceFileName != null); Contract.Requires(destFileName != null); Contract.Requires(sourceFileName.Length > 0); Contract.Requires(destFileName.Length > 0); String fullSourceFileName = LongPath.NormalizePath(sourceFileName); FileIOPermission.QuickDemand(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, fullSourceFileName, false, false); String fullDestFileName = LongPath.NormalizePath(destFileName); FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, fullDestFileName, false, false); if (!LongPathFile.InternalExists(fullSourceFileName)) __Error.WinIOError(Win32Native.ERROR_FILE_NOT_FOUND, fullSourceFileName); String tempSourceFileName = Path.AddLongPathPrefix(fullSourceFileName); String tempDestFileName = Path.AddLongPathPrefix(fullDestFileName); if (!Win32Native.MoveFile(tempSourceFileName, tempDestFileName)) { __Error.WinIOError(); } } // throws FileNotFoundException if not found [System.Security.SecurityCritical] internal static long GetLength(String path) { Contract.Requires(path != null); String fullPath = LongPath.NormalizePath(path); FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, fullPath, false, false); String tempPath = Path.AddLongPathPrefix(fullPath); Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); int dataInitialised = File.FillAttributeInfo(tempPath, ref data, false, true); // return error if (dataInitialised != 0) __Error.WinIOError(dataInitialised, path); // from FileInfo. if ((data.fileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY) != 0) __Error.WinIOError(Win32Native.ERROR_FILE_NOT_FOUND, path); return ((long)data.fileSizeHigh) << 32 | ((long)data.fileSizeLow & 0xFFFFFFFFL); } // Defined in WinError.h private const int ERROR_ACCESS_DENIED = 0x5; } [ComVisible(false)] static class LongPathDirectory { [System.Security.SecurityCritical] // auto-generated [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal static void CreateDirectory(String path) { Contract.Requires(path != null); Contract.Requires(path.Length > 0); String fullPath = LongPath.NormalizePath(path); // You need read access to the directory to be returned back and write access to all the directories // that you need to create. If we fail any security checks we will not create any directories at all. // We attempt to create directories only after all the security checks have passed. This is avoid doing // a demand at every level. String demandDir = GetDemandDir(fullPath, true); FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, demandDir, false, false); InternalCreateDirectory(fullPath, path, null); } [System.Security.SecurityCritical] // auto-generated [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] private unsafe static void InternalCreateDirectory(String fullPath, String path, Object dirSecurityObj) { #if FEATURE_MACL DirectorySecurity dirSecurity = (DirectorySecurity)dirSecurityObj; #endif // FEATURE_MACL int length = fullPath.Length; // We need to trim the trailing slash or the code will try to create 2 directories of the same name. if (length >= 2 && Path.IsDirectorySeparator(fullPath[length - 1])) length--; int lengthRoot = LongPath.GetRootLength(fullPath); // For UNC paths that are only // or /// if (length == 2 && Path.IsDirectorySeparator(fullPath[1])) throw new IOException(Environment.GetResourceString("IO.IO_CannotCreateDirectory", path)); List stackDir = new List(); // Attempt to figure out which directories don't exist, and only // create the ones we need. Note that InternalExists may fail due // to Win32 ACL's preventing us from seeing a directory, and this // isn't threadsafe. bool somepathexists = false; if (length > lengthRoot) { // Special case root (fullpath = X:\\) int i = length - 1; while (i >= lengthRoot && !somepathexists) { String dir = fullPath.Substring(0, i + 1); if (!InternalExists(dir)) // Create only the ones missing stackDir.Add(dir); else somepathexists = true; while (i > lengthRoot && fullPath[i] != Path.DirectorySeparatorChar && fullPath[i] != Path.AltDirectorySeparatorChar) i--; i--; } } int count = stackDir.Count; if (stackDir.Count != 0 #if FEATURE_CAS_POLICY // All demands in full trust domains are no-ops, so skip // // The full path went through validity checks by being passed through FileIOPermissions already. // As a sub string of the full path can't fail the checks if the full path passes. && !CodeAccessSecurityEngine.QuickCheckForAllDemands() #endif ) { String[] securityList = new String[stackDir.Count]; stackDir.CopyTo(securityList, 0); for (int j = 0; j < securityList.Length; j++) securityList[j] += "\\."; // leaf will never have a slash at the end // Security check for all directories not present only. #if !FEATURE_PAL && FEATURE_MACL AccessControlActions control = (dirSecurity == null) ? AccessControlActions.None : AccessControlActions.Change; FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, control, securityList, false, false); #else FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, securityList, false, false); #endif } // If we were passed a DirectorySecurity, convert it to a security // descriptor and set it in he call to CreateDirectory. Win32Native.SECURITY_ATTRIBUTES secAttrs = null; #if FEATURE_MACL if (dirSecurity != null) { secAttrs = new Win32Native.SECURITY_ATTRIBUTES(); secAttrs.nLength = (int)Marshal.SizeOf(secAttrs); // For ACL's, get the security descriptor from the FileSecurity. byte[] sd = dirSecurity.GetSecurityDescriptorBinaryForm(); byte * bytesOnStack = stackalloc byte[sd.Length]; Buffer.Memcpy(bytesOnStack, 0, sd, 0, sd.Length); secAttrs.pSecurityDescriptor = bytesOnStack; } #endif bool r = true; int firstError = 0; String errorString = path; // If all the security checks succeeded create all the directories while (stackDir.Count > 0) { String name = stackDir[stackDir.Count - 1]; stackDir.RemoveAt(stackDir.Count - 1); if (name.Length >= Path.MaxLongPath) throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); r = Win32Native.CreateDirectory(PathInternal.EnsureExtendedPrefix(name), secAttrs); if (!r && (firstError == 0)) { int currentError = Marshal.GetLastWin32Error(); // While we tried to avoid creating directories that don't // exist above, there are at least two cases that will // cause us to see ERROR_ALREADY_EXISTS here. InternalExists // can fail because we didn't have permission to the // directory. Secondly, another thread or process could // create the directory between the time we check and the // time we try using the directory. Thirdly, it could // fail because the target does exist, but is a file. if (currentError != Win32Native.ERROR_ALREADY_EXISTS) firstError = currentError; else { // If there's a file in this directory's place, or if we have ERROR_ACCESS_DENIED when checking if the directory already exists throw. if (LongPathFile.InternalExists(name) || (!InternalExists(name, out currentError) && currentError == Win32Native.ERROR_ACCESS_DENIED)) { firstError = currentError; // Give the user a nice error message, but don't leak path information. try { FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, GetDemandDir(name, true), false, false); errorString = name; } catch (SecurityException) { } } } } } // We need this check to mask OS differences // Handle CreateDirectory("X:\\foo") when X: doesn't exist. Similarly for n/w paths. if ((count == 0) && !somepathexists) { String root = InternalGetDirectoryRoot(fullPath); if (!InternalExists(root)) { // Extract the root from the passed in path again for security. __Error.WinIOError(Win32Native.ERROR_PATH_NOT_FOUND, InternalGetDirectoryRoot(path)); } return; } // Only throw an exception if creating the exact directory we // wanted failed to work correctly. if (!r && (firstError != 0)) { __Error.WinIOError(firstError, errorString); } } [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal static void Move(String sourceDirName, String destDirName) { Contract.Requires(sourceDirName != null); Contract.Requires(destDirName != null); Contract.Requires(sourceDirName.Length != 0); Contract.Requires(destDirName.Length != 0); String fullsourceDirName = LongPath.NormalizePath(sourceDirName); String sourcePath = GetDemandDir(fullsourceDirName, false); if (sourcePath.Length >= Path.MaxLongPath) throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); String fulldestDirName = LongPath.NormalizePath(destDirName); String destPath = GetDemandDir(fulldestDirName, false); if (destPath.Length >= Path.MaxLongPath) throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); FileIOPermission.QuickDemand(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, sourcePath, false, false); FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, destPath, false, false); if (String.Compare(sourcePath, destPath, StringComparison.OrdinalIgnoreCase) == 0) throw new IOException(Environment.GetResourceString("IO.IO_SourceDestMustBeDifferent")); String sourceRoot = LongPath.GetPathRoot(sourcePath); String destinationRoot = LongPath.GetPathRoot(destPath); if (String.Compare(sourceRoot, destinationRoot, StringComparison.OrdinalIgnoreCase) != 0) throw new IOException(Environment.GetResourceString("IO.IO_SourceDestMustHaveSameRoot")); String tempSourceDirName = PathInternal.EnsureExtendedPrefix(sourceDirName); String tempDestDirName = PathInternal.EnsureExtendedPrefix(destDirName); if (!Win32Native.MoveFile(tempSourceDirName, tempDestDirName)) { int hr = Marshal.GetLastWin32Error(); if (hr == Win32Native.ERROR_FILE_NOT_FOUND) // Source dir not found { hr = Win32Native.ERROR_PATH_NOT_FOUND; __Error.WinIOError(hr, fullsourceDirName); } // 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 (hr == Win32Native.ERROR_ACCESS_DENIED) // WinNT throws IOException. This check is for Win9x. We can't change it for backcomp. throw new IOException(Environment.GetResourceString("UnauthorizedAccess_IODenied_Path", sourceDirName), Win32Native.MakeHRFromErrorCode(hr)); __Error.WinIOError(hr, String.Empty); } } [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal static void Delete(String path, bool recursive) { String fullPath = LongPath.NormalizePath(path); InternalDelete(fullPath, path, recursive); } // FullPath is fully qualified, while the user path is used for feedback in exceptions [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] private static void InternalDelete(String fullPath, String userPath, bool recursive) { String demandPath; // If not recursive, do permission check only on this directory // else check for the whole directory structure rooted below demandPath = GetDemandDir(fullPath, !recursive); // Make sure we have write permission to this directory FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, demandPath, false, false); String longPath = Path.AddLongPathPrefix(fullPath); // 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. Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); int dataInitialised = File.FillAttributeInfo(longPath, ref data, false, true); if (dataInitialised != 0) { // Ensure we throw a DirectoryNotFoundException. if (dataInitialised == Win32Native.ERROR_FILE_NOT_FOUND) dataInitialised = Win32Native.ERROR_PATH_NOT_FOUND; __Error.WinIOError(dataInitialised, fullPath); } if (((FileAttributes)data.fileAttributes & FileAttributes.ReparsePoint) != 0) recursive = false; DeleteHelper(longPath, userPath, recursive, true); } // Note that fullPath is fully qualified, while userPath may be // relative. Use userPath for all exception messages to avoid leaking // fully qualified path information. [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] private static void DeleteHelper(String fullPath, String userPath, bool recursive, bool throwOnTopLevelDirectoryNotFound) { bool r; int hr; 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) { Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA(); String searchPath = null; if (fullPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) { searchPath = fullPath + "*"; } else { searchPath = fullPath + Path.DirectorySeparatorChar + "*"; } // Open a Find handle using (SafeFindHandle hnd = Win32Native.FindFirstFile(searchPath, data)) { if (hnd.IsInvalid) { hr = Marshal.GetLastWin32Error(); __Error.WinIOError(hr, userPath); } do { bool isDir = (0 != (data.dwFileAttributes & Win32Native.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 = LongPath.InternalCombine(fullPath, data.cFileName); String newUserPath = LongPath.InternalCombine(userPath, data.cFileName); try { DeleteHelper(newFullPath, newUserPath, 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 == Win32Native.IO_REPARSE_TAG_MOUNT_POINT) { // Use full path plus a trailing '\' String mountPoint = LongPath.InternalCombine(fullPath, data.cFileName + Path.DirectorySeparatorChar); r = Win32Native.DeleteVolumeMountPoint(mountPoint); if (!r) { hr = Marshal.GetLastWin32Error(); if (hr != Win32Native.ERROR_PATH_NOT_FOUND) { try { __Error.WinIOError(hr, data.cFileName); } catch (Exception e) { if (ex == null) { ex = e; } } } } } // RemoveDirectory on a symbolic link will // remove the link itself. String reparsePoint = LongPath.InternalCombine(fullPath, data.cFileName); r = Win32Native.RemoveDirectory(reparsePoint); if (!r) { hr = Marshal.GetLastWin32Error(); if (hr != Win32Native.ERROR_PATH_NOT_FOUND) { try { __Error.WinIOError(hr, data.cFileName); } catch (Exception e) { if (ex == null) { ex = e; } } } } } } else { String fileName = LongPath.InternalCombine(fullPath, data.cFileName); r = Win32Native.DeleteFile(fileName); if (!r) { hr = Marshal.GetLastWin32Error(); if (hr != Win32Native.ERROR_FILE_NOT_FOUND) { try { __Error.WinIOError(hr, data.cFileName); } catch (Exception e) { if (ex == null) { ex = e; } } } } } } while (Win32Native.FindNextFile(hnd, data)); // Make sure we quit with a sensible error. hr = Marshal.GetLastWin32Error(); } if (ex != null) throw ex; if (hr != 0 && hr != Win32Native.ERROR_NO_MORE_FILES) __Error.WinIOError(hr, userPath); } r = Win32Native.RemoveDirectory(fullPath); if (!r) { hr = Marshal.GetLastWin32Error(); if (hr == Win32Native.ERROR_FILE_NOT_FOUND) // A dubious error code. hr = Win32Native.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 (hr == Win32Native.ERROR_ACCESS_DENIED) throw new IOException(Environment.GetResourceString("UnauthorizedAccess_IODenied_Path", userPath)); // don't throw the DirectoryNotFoundException since this is a subdir and there could be a ---- // between two Directory.Delete callers if (hr == Win32Native.ERROR_PATH_NOT_FOUND && !throwOnTopLevelDirectoryNotFound) return; __Error.WinIOError(hr, userPath); } } [System.Security.SecurityCritical] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal static bool Exists(String path) { try { if (path == null) return false; if (path.Length == 0) return false; // Get fully qualified file name ending in \* for security check String fullPath = LongPath.NormalizePath(path); String demandPath = GetDemandDir(fullPath, true); FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, demandPath, false, false); return InternalExists(fullPath); } catch (ArgumentException) { } catch (NotSupportedException) { } // Security can throw this on ":" catch (SecurityException) { } catch (IOException) { } catch (UnauthorizedAccessException) { #if !FEATURE_PAL Contract.Assert(false, "Ignore this assert and file a bug to the BCL team. This assert was tracking purposes only."); #endif //!FEATURE_PAL } return false; } [System.Security.SecurityCritical] // auto-generated [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal static bool InternalExists(String path) { Contract.Requires(path != null); int lastError = Win32Native.ERROR_SUCCESS; return InternalExists(path, out lastError); } [System.Security.SecurityCritical] // auto-generated [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal static bool InternalExists(String path, out int lastError) { Contract.Requires(path != null); String tempPath = Path.AddLongPathPrefix(path); return Directory.InternalExists(tempPath, out lastError); } // Input to this method should already be fullpath. This method will ensure that we append // the trailing slash only when appropriate and when thisDirOnly is specified append a "." // at the end of the path to indicate that the demand is only for the fullpath and not // everything underneath it. [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.None, ResourceScope.None)] private static String GetDemandDir(string fullPath, bool thisDirOnly) { String demandPath; fullPath = Path.RemoveLongPathPrefix(fullPath); if (thisDirOnly) { if (fullPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) || fullPath.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal)) demandPath = fullPath + '.'; else demandPath = fullPath + Path.DirectorySeparatorChar + '.'; } else { if (!(fullPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) || fullPath.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal))) demandPath = fullPath + Path.DirectorySeparatorChar; else demandPath = fullPath; } return demandPath; } private static String InternalGetDirectoryRoot(String path) { if (path == null) return null; return path.Substring(0, LongPath.GetRootLength(path)); } } }