public static void MoveDirectory(string sourceFullPath, string destFullPath) { // Windows doesn't care if you try and copy a file via "MoveDirectory"... if (FileExists(sourceFullPath)) { // ... but it doesn't like the source to have a trailing slash ... // On Windows we end up with ERROR_INVALID_NAME, which is // "The filename, directory name, or volume label syntax is incorrect." // // This surfaces as a IOException, if we let it go beyond here it would // give DirectoryNotFound. if (PathInternal.EndsInDirectorySeparator(sourceFullPath)) { throw new IOException(SR.Format(SR.IO_PathNotFound_Path, sourceFullPath)); } // ... but it doesn't care if the destination has a trailing separator. destFullPath = PathInternal.TrimEndingDirectorySeparator(destFullPath); } if (Interop.Sys.Rename(sourceFullPath, destFullPath) < 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); switch (errorInfo.Error) { case Interop.Error.EACCES: // match Win32 exception throw new IOException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, sourceFullPath), errorInfo.RawErrno); default: throw Interop.GetExceptionForIoErrno(errorInfo, sourceFullPath, isDirectory: true); } } }
/// <summary> /// Trims the ending directory separator if present. /// </summary> /// <param name="path"></param> internal static ReadOnlySpan <char> TrimEndingDirectorySeparator(ReadOnlySpan <char> path) => PathInternal.EndsInDirectorySeparator(path) ? path.Slice(0, path.Length - 1) : path;
public static void CreateDirectory(string fullPath) { // We can save a bunch of work if the directory we want to create already exists. This also // saves us in the case where sub paths are inaccessible (due to ERROR_ACCESS_DENIED) but the // final path is accessible and the directory already exists. For example, consider trying // to create c:\Foo\Bar\Baz, where everything already exists but ACLS prevent access to c:\Foo // and c:\Foo\Bar. In that case, this code will think it needs to create c:\Foo, and c:\Foo\Bar // and fail to due so, causing an exception to be thrown. This is not what we want. if (DirectoryExists(fullPath)) { return; } List <string> stackDir = new List <string>(); // Attempt to figure out which directories don't exist, and only // create the ones we need. Note that FileExists may fail due // to Win32 ACL's preventing us from seeing a directory, and this // isn't threadsafe. bool somepathexists = false; 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 && PathInternal.EndsInDirectorySeparator(fullPath)) { length--; } int lengthRoot = PathInternal.GetRootLength(fullPath); if (length > lengthRoot) { // Special case root (fullpath = X:\\) int i = length - 1; while (i >= lengthRoot && !somepathexists) { string dir = fullPath.Substring(0, i + 1); if (!DirectoryExists(dir)) // Create only the ones missing { stackDir.Add(dir); } else { somepathexists = true; } while (i > lengthRoot && !PathInternal.IsDirectorySeparator(fullPath[i])) { i--; } i--; } } int count = stackDir.Count; // If we were passed a DirectorySecurity, convert it to a security // descriptor and set it in he call to CreateDirectory. Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = default; bool r = true; int firstError = 0; string errorString = fullPath; // 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); r = Interop.Kernel32.CreateDirectory(name, ref 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. FileExists // 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 != Interop.Errors.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 (FileExists(name) || (!DirectoryExists(name, out currentError) && currentError == Interop.Errors.ERROR_ACCESS_DENIED)) { firstError = currentError; errorString = name; } } } } // We need this check to mask OS differences // Handle CreateDirectory("X:\\") when X: doesn't exist. Similarly for n/w paths. if ((count == 0) && !somepathexists) { string root = Directory.InternalGetDirectoryRoot(fullPath); if (!DirectoryExists(root)) { throw Win32Marshal.GetExceptionForWin32Error(Interop.Errors.ERROR_PATH_NOT_FOUND, root); } return; } // Only throw an exception if creating the exact directory we // wanted failed to work correctly. if (!r && (firstError != 0)) { throw Win32Marshal.GetExceptionForWin32Error(firstError, errorString); } }
private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType) { if (string.IsNullOrEmpty(relativeTo)) { throw new ArgumentNullException(nameof(relativeTo)); } if (string.IsNullOrWhiteSpace(path)) { throw new ArgumentNullException(nameof(path)); } Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase); relativeTo = GetFullPath(relativeTo); path = GetFullPath(path); // Need to check if the roots are different- if they are we need to return the "to" path. if (!PathInternal.AreRootsEqual(relativeTo, path, comparisonType)) { return(path); } int commonLength = PathInternal.GetCommonPathLength(relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase); // If there is nothing in common they can't share the same root, return the "to" path as is. if (commonLength == 0) { return(path); } // Trailing separators aren't significant for comparison int relativeToLength = relativeTo.Length; if (PathInternal.EndsInDirectorySeparator(relativeTo)) { relativeToLength--; } bool pathEndsInSeparator = PathInternal.EndsInDirectorySeparator(path); int pathLength = path.Length; if (pathEndsInSeparator) { pathLength--; } // If we have effectively the same path, return "." if (relativeToLength == pathLength && commonLength >= relativeToLength) { return("."); } // We have the same root, we need to calculate the difference now using the // common Length and Segment count past the length. // // Some examples: // // C:\Foo C:\Bar L3, S1 -> ..\Bar // C:\Foo C:\Foo\Bar L6, S0 -> Bar // C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar // C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar StringBuilder sb = StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length)); // Add parent segments for segments past the common on the "from" path if (commonLength < relativeToLength) { sb.Append(PathInternal.ParentDirectoryPrefix); for (int i = commonLength; i < relativeToLength; i++) { if (PathInternal.IsDirectorySeparator(relativeTo[i])) { sb.Append(PathInternal.ParentDirectoryPrefix); } } } else if (PathInternal.IsDirectorySeparator(path[commonLength])) { // No parent segments and we need to eat the initial separator // (C:\Foo C:\Foo\Bar case) commonLength++; } // Now add the rest of the "to" path, adding back the trailing separator int count = pathLength - commonLength; if (pathEndsInSeparator) { count++; } sb.Append(path, commonLength, count); return(StringBuilderCache.GetStringAndRelease(sb)); }
public static void CreateDirectory(string fullPath) { // NOTE: This logic is primarily just carried forward from Win32FileSystem.CreateDirectory. 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 && PathInternal.EndsInDirectorySeparator(fullPath)) { length--; } // For paths that are only // or /// if (length == 2 && PathInternal.IsDirectorySeparator(fullPath[1])) { throw new IOException(SR.Format(SR.IO_CannotCreateDirectory, fullPath)); } // We can save a bunch of work if the directory we want to create already exists. if (DirectoryExists(fullPath)) { return; } // Attempt to figure out which directories don't exist, and only create the ones we need. bool somepathexists = false; Stack <string> stackDir = new Stack <string>(); int lengthRoot = PathInternal.GetRootLength(fullPath); if (length > lengthRoot) { int i = length - 1; while (i >= lengthRoot && !somepathexists) { string dir = fullPath.Substring(0, i + 1); if (!DirectoryExists(dir)) // Create only the ones missing { stackDir.Push(dir); } else { somepathexists = true; } while (i > lengthRoot && !PathInternal.IsDirectorySeparator(fullPath[i])) { i--; } i--; } } int count = stackDir.Count; if (count == 0 && !somepathexists) { string root = Directory.InternalGetDirectoryRoot(fullPath); if (!DirectoryExists(root)) { throw Interop.GetExceptionForIoErrno(Interop.Error.ENOENT.Info(), fullPath, isDirectory: true); } return; } // Create all the directories int result = 0; Interop.ErrorInfo firstError = default(Interop.ErrorInfo); string errorString = fullPath; while (stackDir.Count > 0) { string name = stackDir.Pop(); // The mkdir command uses 0777 by default (it'll be AND'd with the process umask internally). // We do the same. result = Interop.Sys.MkDir(name, (int)Interop.Sys.Permissions.Mask); if (result < 0 && firstError.Error == 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); // While we tried to avoid creating directories that don't // exist above, there are a few cases that can fail, e.g. // a race condition where another process or thread creates // the directory first, or there's a file at the location. if (errorInfo.Error != Interop.Error.EEXIST) { firstError = errorInfo; } else if (FileExists(name) || (!DirectoryExists(name, out errorInfo) && errorInfo.Error == Interop.Error.EACCES)) { // 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. firstError = errorInfo; errorString = name; } } } // Only throw an exception if creating the exact directory we wanted failed to work correctly. if (result < 0 && firstError.Error != 0) { throw Interop.GetExceptionForIoErrno(firstError, errorString, isDirectory: true); } }