/mcs/class/referencesource/mscorlib/system/io/longpath.cs
C# | 987 lines | 708 code | 128 blank | 151 comment | 180 complexity | 22228922e6c6ef3b7a27c9a686c960ef MD5 | raw file
Possible License(s): LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0
- // ==++==
- //
- // Copyright (c) Microsoft Corporation. All rights reserved.
- //
- // ==--==g
- /*============================================================
- **
- ** Class: File
- **
- ** <OWNER>Microsoft</OWNER>
- **
- **
- ** 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<string> stackDir = new List<string>();
- // 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));
- }
- }
- }