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 (Path.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 = Path.TrimEndingDirectorySeparator(destFullPath); } if (FileExists(destFullPath)) { // Some Unix distros will overwrite the destination file if it already exists. // Throwing IOException to match Windows behavior. throw new IOException(SR.Format(SR.IO_AlreadyExists_Name, 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); } } }
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 && Path.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) { if (!DirectoryExists(fullPath.AsSpan(0, i + 1))) // Create only the ones missing { stackDir.Push(fullPath.Substring(0, i + 1)); } else { somepathexists = true; } while (i > lengthRoot && !PathInternal.IsDirectorySeparator(fullPath[i])) { i--; } i--; } } int count = stackDir.Count; if (count == 0 && !somepathexists) { ReadOnlySpan <char> root = Path.GetPathRoot(fullPath.AsSpan()); 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); } }
internal static string EnsureTrailingSeparator(string path) => Path.EndsInDirectorySeparator(path.AsSpan()) ? path : path + DirectorySeparatorCharAsString;