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); } } }
public DirectoryInfo CreateSubdirectory(string path) { if (path == null) { throw new ArgumentNullException(nameof(path)); } if (PathInternal.IsEffectivelyEmpty(path)) { throw new ArgumentException(SR.Argument_PathEmpty, nameof(path)); } if (Path.IsPathRooted(path)) { throw new ArgumentException(SR.Arg_Path2IsRooted, nameof(path)); } string newPath = Path.GetFullPath(Path.Combine(FullPath, path)); ReadOnlySpan <char> trimmedNewPath = PathInternal.TrimEndingDirectorySeparator(newPath.AsSpan()); ReadOnlySpan <char> trimmedCurrentPath = PathInternal.TrimEndingDirectorySeparator(FullPath.AsSpan()); // We want to make sure the requested directory is actually under the subdirectory. if (trimmedNewPath.StartsWith(trimmedCurrentPath, PathInternal.StringComparison) // Allow the exact same path, but prevent allowing "..\FooBar" through when the directory is "Foo" && ((trimmedNewPath.Length == trimmedCurrentPath.Length) || PathInternal.IsDirectorySeparator(newPath[trimmedCurrentPath.Length]))) { FileSystem.CreateDirectory(newPath); return(new DirectoryInfo(newPath)); } // We weren't nested throw new ArgumentException(SR.Format(SR.Argument_InvalidSubPath, path, FullPath), nameof(path)); }
public static bool FileExists(string fullPath) { Interop.ErrorInfo ignored; // Input allows trailing separators in order to match Windows behavior // Unix does not accept trailing separators, so must be trimmed return(FileExists(PathInternal.TrimEndingDirectorySeparator(fullPath), Interop.Sys.FileTypes.S_IFREG, out ignored)); }
public static bool FileExists(ReadOnlySpan <char> fullPath) { Interop.ErrorInfo ignored; // File.Exists() explicitly checks for a trailing separator and returns false if found. FileInfo.Exists and all other // internal usages do not check for the trailing separator. Historically we've always removed the trailing separator // when getting attributes as trailing separators are generally not accepted by Windows APIs. Unix will take // trailing separators, but it infers that the path must be a directory (it effectively appends "."). To align with // our historical behavior (outside of File.Exists()), we need to trim. // // See http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11 for details. return(FileExists(PathInternal.TrimEndingDirectorySeparator(fullPath), Interop.Sys.FileTypes.S_IFREG, out ignored)); }
/// <summary> /// Returns 0 on success, otherwise a Win32 error code. Note that /// classes should use -1 as the uninitialized state for dataInitialized. /// </summary> /// <param name="returnErrorOnNotFound">Return the error code for not found errors?</param> internal static int FillAttributeInfo(string path, ref Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data, bool returnErrorOnNotFound) { int errorCode = Interop.Errors.ERROR_SUCCESS; // Neither GetFileAttributes or FindFirstFile like trailing separators path = PathInternal.TrimEndingDirectorySeparator(path); using (DisableMediaInsertionPrompt.Create()) { if (!Interop.Kernel32.GetFileAttributesEx(path, Interop.Kernel32.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, ref data)) { errorCode = Marshal.GetLastWin32Error(); if (errorCode == Interop.Errors.ERROR_ACCESS_DENIED) { // Files that are marked for deletion will not let you GetFileAttributes, // ERROR_ACCESS_DENIED is given back without filling out the data struct. // FindFirstFile, however, will. Historically we always gave back attributes // for marked-for-deletion files. var findData = new Interop.Kernel32.WIN32_FIND_DATA(); using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(path, ref findData)) { if (handle.IsInvalid) { errorCode = Marshal.GetLastWin32Error(); } else { errorCode = Interop.Errors.ERROR_SUCCESS; data.PopulateFrom(ref findData); } } } } } if (errorCode != Interop.Errors.ERROR_SUCCESS && !returnErrorOnNotFound) { switch (errorCode) { case Interop.Errors.ERROR_FILE_NOT_FOUND: case Interop.Errors.ERROR_PATH_NOT_FOUND: case Interop.Errors.ERROR_NOT_READY: // Removable media not ready // Return default value for backward compatibility data.dwFileAttributes = -1; return(Interop.Errors.ERROR_SUCCESS); } } return(errorCode); }
private void Init(string originalPath, string fullPath = null, string fileName = null, bool isNormalized = false) { // Want to throw the original argument name OriginalPath = originalPath ?? throw new ArgumentNullException("path"); fullPath = fullPath ?? originalPath; fullPath = isNormalized ? fullPath : Path.GetFullPath(fullPath); _name = fileName ?? (PathInternal.IsRoot(fullPath) ? fullPath : Path.GetFileName(PathInternal.TrimEndingDirectorySeparator(fullPath.AsSpan()))).ToString(); FullPath = fullPath; }
private static void GetFindData(string fullPath, ref Interop.Kernel32.WIN32_FIND_DATA findData) { using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(PathInternal.TrimEndingDirectorySeparator(fullPath), ref findData)) { if (handle.IsInvalid) { int errorCode = Marshal.GetLastWin32Error(); // File not found doesn't make much sense coming from a directory delete. if (errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND) { errorCode = Interop.Errors.ERROR_PATH_NOT_FOUND; } throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } } }
public void Refresh(ReadOnlySpan <char> path) { // This should not throw, instead we store the result so that we can throw it // when someone actually accesses a property. // Use lstat to get the details on the object, without following symlinks. // If it is a symlink, then subsequently get details on the target of the symlink, // storing those results separately. We only report failure if the initial // lstat fails, as a broken symlink should still report info on exists, attributes, etc. _isDirectory = false; path = PathInternal.TrimEndingDirectorySeparator(path); int result = Interop.Sys.LStat(path, out _fileStatus); if (result < 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); // This should never set the error if the file can't be found. // (see the Windows refresh passing returnErrorOnNotFound: false). if (errorInfo.Error == Interop.Error.ENOENT || errorInfo.Error == Interop.Error.ENOTDIR) { _fileStatusInitialized = 0; _exists = false; } else { _fileStatusInitialized = errorInfo.RawErrno; } return; } _exists = true; // IMPORTANT: Is directory logic must match the logic in FileSystemEntry _isDirectory = (_fileStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR; // If we're a symlink, attempt to check the target to see if it is a directory if ((_fileStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFLNK && Interop.Sys.Stat(path, out Interop.Sys.FileStatus targetStatus) >= 0) { _isDirectory = (targetStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR; } _fileStatusInitialized = 0; }
public static void DeleteFile(string fullPath) { if (Interop.Sys.Unlink(fullPath) < 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); switch (errorInfo.Error) { case Interop.Error.ENOENT: // In order to match Windows behavior string directoryName = Path.GetDirectoryName(fullPath); if (directoryName.Length > 0 && !Directory.Exists(directoryName)) { throw Interop.GetExceptionForIoErrno(errorInfo, fullPath, true); } return; case Interop.Error.EROFS: // EROFS means the file system is read-only // Need to manually check file existence // github.com/dotnet/corefx/issues/21273 Interop.ErrorInfo fileExistsError; // Input allows trailing separators in order to match Windows behavior // Unix does not accept trailing separators, so must be trimmed if (!FileExists(PathInternal.TrimEndingDirectorySeparator(fullPath), Interop.Sys.FileTypes.S_IFREG, out fileExistsError) && fileExistsError.Error == Interop.Error.ENOENT) { return; } goto default; case Interop.Error.EISDIR: errorInfo = Interop.Error.EACCES.Info(); goto default; default: throw Interop.GetExceptionForIoErrno(errorInfo, fullPath); } } }
/// <summary> /// Returns 0 on success, otherwise a Win32 error code. Note that /// classes should use -1 as the uninitialized state for dataInitialized. /// </summary> /// <param name="returnErrorOnNotFound">Return the error code for not found errors?</param> internal static int FillAttributeInfo(string path, ref Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data, bool returnErrorOnNotFound) { int errorCode = Interop.Errors.ERROR_SUCCESS; // Neither GetFileAttributes or FindFirstFile like trailing separators path = PathInternal.TrimEndingDirectorySeparator(path); using (DisableMediaInsertionPrompt.Create()) { if (!Interop.Kernel32.GetFileAttributesEx(path, Interop.Kernel32.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, ref data)) { errorCode = Marshal.GetLastWin32Error(); if (errorCode != Interop.Errors.ERROR_FILE_NOT_FOUND && errorCode != Interop.Errors.ERROR_PATH_NOT_FOUND && errorCode != Interop.Errors.ERROR_NOT_READY && errorCode != Interop.Errors.ERROR_INVALID_NAME && errorCode != Interop.Errors.ERROR_BAD_PATHNAME && errorCode != Interop.Errors.ERROR_BAD_NETPATH && errorCode != Interop.Errors.ERROR_BAD_NET_NAME && errorCode != Interop.Errors.ERROR_INVALID_PARAMETER && errorCode != Interop.Errors.ERROR_NETWORK_UNREACHABLE) { // Assert so we can track down other cases (if any) to add to our test suite Debug.Assert(errorCode == Interop.Errors.ERROR_ACCESS_DENIED || errorCode == Interop.Errors.ERROR_SHARING_VIOLATION, $"Unexpected error code getting attributes {errorCode}"); // Files that are marked for deletion will not let you GetFileAttributes, // ERROR_ACCESS_DENIED is given back without filling out the data struct. // FindFirstFile, however, will. Historically we always gave back attributes // for marked-for-deletion files. // // Another case where enumeration works is with special system files such as // pagefile.sys that give back ERROR_SHARING_VIOLATION on GetAttributes. // // Ideally we'd only try again for known cases due to the potential performance // hit. The last attempt to do so baked for nearly a year before we found the // pagefile.sys case. As such we're probably stuck filtering out specific // cases that we know we don't want to retry on. var findData = new Interop.Kernel32.WIN32_FIND_DATA(); using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(path, ref findData)) { if (handle.IsInvalid) { errorCode = Marshal.GetLastWin32Error(); } else { errorCode = Interop.Errors.ERROR_SUCCESS; data.PopulateFrom(ref findData); } } } } } if (errorCode != Interop.Errors.ERROR_SUCCESS && !returnErrorOnNotFound) { switch (errorCode) { case Interop.Errors.ERROR_FILE_NOT_FOUND: case Interop.Errors.ERROR_PATH_NOT_FOUND: case Interop.Errors.ERROR_NOT_READY: // Removable media not ready // Return default value for backward compatibility data.dwFileAttributes = -1; return(Interop.Errors.ERROR_SUCCESS); } } return(errorCode); }