diff --git a/external/corefx-bugfix/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs b/external/corefx-bugfix/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs
new file mode 100644
index 000000000000..31f1e0e89b86
--- /dev/null
+++ b/external/corefx-bugfix/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs
@@ -0,0 +1,561 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.IO
+{
+ /// Provides an implementation of FileSystem for Unix systems.
+ internal static partial class FileSystem
+ {
+ internal const int DefaultBufferSize = 4096;
+
+ private static bool CopyDanglingSymlink(string sourceFullPath, string destFullPath)
+ {
+ // Check if the source is a dangling symlink. In those cases, we just want to copy the link
+ Interop.Sys.FileStatus ignored;
+ if (! (Interop.Sys.Stat(sourceFullPath, out ignored) < 0 &&
+ Interop.Sys.LStat(sourceFullPath, out ignored) == 0))
+ {
+ return false;
+ }
+
+ Interop.ErrorInfo errorInfo;
+ // get the target of the symlink
+ string linkTarget = Interop.Sys.ReadLink(sourceFullPath);
+ if (linkTarget == null)
+ {
+ errorInfo = Interop.Sys.GetLastErrorInfo();
+ throw Interop.GetExceptionForIoErrno(errorInfo, sourceFullPath);
+ }
+
+ if (Interop.Sys.Symlink(linkTarget, destFullPath) == 0)
+ return true;
+
+ errorInfo = Interop.Sys.GetLastErrorInfo();
+ throw Interop.GetExceptionForIoErrno(errorInfo, destFullPath);
+ }
+
+ public static void CopyFile(string sourceFullPath, string destFullPath, bool overwrite)
+ {
+ if (DirectoryExists(destFullPath))
+ {
+ throw new System.UnauthorizedAccessException(SR.Format(SR.Arg_FileIsDirectory_Name, destFullPath));
+ }
+
+ if (CopyDanglingSymlink(sourceFullPath, destFullPath))
+ return;
+
+ // Copy the contents of the file from the source to the destination, creating the destination in the process
+ using (var src = new FileStream(sourceFullPath, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultBufferSize, FileOptions.None))
+ using (var dst = new FileStream(destFullPath, overwrite ? FileMode.Create : FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, DefaultBufferSize, FileOptions.None))
+ {
+ Interop.CheckIo(Interop.Sys.CopyFile(src.SafeFileHandle, dst.SafeFileHandle));
+ }
+ }
+
+ private static void LinkOrCopyFile (string sourceFullPath, string destFullPath)
+ {
+ if (CopyDanglingSymlink(sourceFullPath, destFullPath))
+ return;
+
+ if (Interop.Sys.Link(sourceFullPath, destFullPath) >= 0)
+ return;
+
+ // If link fails, we can fall back to doing a full copy, but we'll only do so for
+ // cases where we expect link could fail but such a copy could succeed. We don't
+ // want to do so for all errors, because the copy could incur a lot of cost
+ // even if we know it'll eventually fail, e.g. EROFS means that the source file
+ // system is read-only and couldn't support the link being added, but if it's
+ // read-only, then the move should fail any way due to an inability to delete
+ // the source file.
+ Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
+ if (errorInfo.Error == Interop.Error.EXDEV || // rename fails across devices / mount points
+ errorInfo.Error == Interop.Error.EACCES ||
+ errorInfo.Error == Interop.Error.EPERM || // permissions might not allow creating hard links even if a copy would work
+ errorInfo.Error == Interop.Error.EOPNOTSUPP || // links aren't supported by the source file system
+ errorInfo.Error == Interop.Error.EMLINK || // too many hard links to the source file
+ errorInfo.Error == Interop.Error.ENOSYS) // the file system doesn't support link
+ {
+ CopyFile(sourceFullPath, destFullPath, overwrite: false);
+ }
+ else
+ {
+ // The operation failed. Within reason, try to determine which path caused the problem
+ // so we can throw a detailed exception.
+ string path = null;
+ bool isDirectory = false;
+ if (errorInfo.Error == Interop.Error.ENOENT)
+ {
+ if (!Directory.Exists(Path.GetDirectoryName(destFullPath)))
+ {
+ // The parent directory of destFile can't be found.
+ // Windows distinguishes between whether the directory or the file isn't found,
+ // and throws a different exception in these cases. We attempt to approximate that
+ // here; there is a race condition here, where something could change between
+ // when the error occurs and our checks, but it's the best we can do, and the
+ // worst case in such a race condition (which could occur if the file system is
+ // being manipulated concurrently with these checks) is that we throw a
+ // FileNotFoundException instead of DirectoryNotFoundexception.
+ path = destFullPath;
+ isDirectory = true;
+ }
+ else
+ {
+ path = sourceFullPath;
+ }
+ }
+ else if (errorInfo.Error == Interop.Error.EEXIST)
+ {
+ path = destFullPath;
+ }
+
+ throw Interop.GetExceptionForIoErrno(errorInfo, path, isDirectory);
+ }
+ }
+
+
+ public static void ReplaceFile(string sourceFullPath, string destFullPath, string destBackupFullPath, bool ignoreMetadataErrors)
+ {
+ if (destBackupFullPath != null)
+ {
+ // We're backing up the destination file to the backup file, so we need to first delete the backup
+ // file, if it exists. If deletion fails for a reason other than the file not existing, fail.
+ if (Interop.Sys.Unlink(destBackupFullPath) != 0)
+ {
+ Interop.ErrorInfo errno = Interop.Sys.GetLastErrorInfo();
+ if (errno.Error != Interop.Error.ENOENT)
+ {
+ throw Interop.GetExceptionForIoErrno(errno, destBackupFullPath);
+ }
+ }
+
+ // Now that the backup is gone, link the backup to point to the same file as destination.
+ // This way, we don't lose any data in the destination file, no copy is necessary, etc.
+ LinkOrCopyFile(destFullPath, destBackupFullPath);
+ }
+ else
+ {
+ // There is no backup file. Just make sure the destination file exists, throwing if it doesn't.
+ Interop.Sys.FileStatus ignored;
+ if (Interop.Sys.Stat(destFullPath, out ignored) != 0)
+ {
+ Interop.ErrorInfo errno = Interop.Sys.GetLastErrorInfo();
+ if (errno.Error == Interop.Error.ENOENT)
+ {
+ throw Interop.GetExceptionForIoErrno(errno, destBackupFullPath);
+ }
+ }
+ }
+
+ // Finally, rename the source to the destination, overwriting the destination.
+ Interop.CheckIo(Interop.Sys.Rename(sourceFullPath, destFullPath));
+ }
+
+ public static void MoveFile(string sourceFullPath, string destFullPath)
+ {
+ // The desired behavior for Move(source, dest) is to not overwrite the destination file
+ // if it exists. Since rename(source, dest) will replace the file at 'dest' if it exists,
+ // link/unlink are used instead. Rename is more efficient than link/unlink on file systems
+ // where hard links are not supported (such as FAT). Therefore, given that source file exists,
+ // rename is used in 2 cases: when dest file does not exist or when source path and dest
+ // path refer to the same file (on the same device). This is important for case-insensitive
+ // file systems (e.g. renaming a file in a way that just changes casing), so that we support
+ // changing the casing in the naming of the file. If this fails in any way (e.g. source file
+ // doesn't exist, dest file doesn't exist, rename fails, etc.), we just fall back to trying the
+ // link/unlink approach and generating any exceptional messages from there as necessary.
+ Interop.Sys.FileStatus sourceStat, destStat;
+ if (Interop.Sys.LStat(sourceFullPath, out sourceStat) == 0 && // source file exists
+ (Interop.Sys.LStat(destFullPath, out destStat) != 0 || // dest file does not exist
+ (sourceStat.Dev == destStat.Dev && // source and dest are on the same device
+ sourceStat.Ino == destStat.Ino)) && // source and dest are the same file on that device
+ Interop.Sys.Rename(sourceFullPath, destFullPath) == 0) // try the rename
+ {
+ // Renamed successfully.
+ return;
+ }
+
+ LinkOrCopyFile(sourceFullPath, destFullPath);
+ DeleteFile(sourceFullPath);
+ }
+
+ 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:
+ // ENOENT means it already doesn't exist; nop
+ 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);
+ }
+ }
+ }
+
+ 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 stackDir = new Stack();
+ 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);
+ }
+ }
+
+ 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);
+
+ // ... and dest cannot be an existing file.
+ if(FileExists(destFullPath))
+ throw new IOException(SR.IO_FileCreateAlreadyExists);
+ }
+
+ 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 RemoveDirectory(string fullPath, bool recursive)
+ {
+ var di = new DirectoryInfo(fullPath);
+ if (!di.Exists)
+ {
+ throw Interop.GetExceptionForIoErrno(Interop.Error.ENOENT.Info(), fullPath, isDirectory: true);
+ }
+ RemoveDirectoryInternal(di, recursive, throwOnTopLevelDirectoryNotFound: true);
+ }
+
+ private static void RemoveDirectoryInternal(DirectoryInfo directory, bool recursive, bool throwOnTopLevelDirectoryNotFound)
+ {
+ Exception firstException = null;
+
+ if ((directory.Attributes & FileAttributes.ReparsePoint) != 0)
+ {
+ DeleteFile(directory.FullName);
+ return;
+ }
+
+ if (recursive)
+ {
+ try
+ {
+ foreach (string item in Directory.EnumerateFileSystemEntries(directory.FullName))
+ {
+ if (!ShouldIgnoreDirectory(Path.GetFileName(item)))
+ {
+ try
+ {
+ var childDirectory = new DirectoryInfo(item);
+ if (childDirectory.Exists)
+ {
+ RemoveDirectoryInternal(childDirectory, recursive, throwOnTopLevelDirectoryNotFound: false);
+ }
+ else
+ {
+ DeleteFile(item);
+ }
+ }
+ catch (Exception exc)
+ {
+ if (firstException != null)
+ {
+ firstException = exc;
+ }
+ }
+ }
+ }
+ }
+ catch (Exception exc)
+ {
+ if (firstException != null)
+ {
+ firstException = exc;
+ }
+ }
+
+ if (firstException != null)
+ {
+ throw firstException;
+ }
+ }
+
+ if (Interop.Sys.RmDir(directory.FullName) < 0)
+ {
+ Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
+ switch (errorInfo.Error)
+ {
+ case Interop.Error.EACCES:
+ case Interop.Error.EPERM:
+ case Interop.Error.EROFS:
+ case Interop.Error.EISDIR:
+ throw new IOException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, directory.FullName)); // match Win32 exception
+ case Interop.Error.ENOENT:
+ if (!throwOnTopLevelDirectoryNotFound)
+ {
+ return;
+ }
+ goto default;
+ default:
+ throw Interop.GetExceptionForIoErrno(errorInfo, directory.FullName, isDirectory: true);
+ }
+ }
+ }
+
+ public static bool DirectoryExists(ReadOnlySpan fullPath)
+ {
+ Interop.ErrorInfo ignored;
+ return DirectoryExists(fullPath, out ignored);
+ }
+
+ private static bool DirectoryExists(ReadOnlySpan fullPath, out Interop.ErrorInfo errorInfo)
+ {
+ return FileExists(fullPath, Interop.Sys.FileTypes.S_IFDIR, out errorInfo);
+ }
+
+ public static bool FileExists(ReadOnlySpan 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);
+ }
+
+ private static bool FileExists(ReadOnlySpan fullPath, int fileType, out Interop.ErrorInfo errorInfo)
+ {
+ Debug.Assert(fileType == Interop.Sys.FileTypes.S_IFREG || fileType == Interop.Sys.FileTypes.S_IFDIR);
+
+ Interop.Sys.FileStatus fileinfo;
+ errorInfo = default(Interop.ErrorInfo);
+
+ // First use stat, as we want to follow symlinks. If that fails, it could be because the symlink
+ // is broken, we don't have permissions, etc., in which case fall back to using LStat to evaluate
+ // based on the symlink itself.
+ if (Interop.Sys.Stat(fullPath, out fileinfo) < 0 &&
+ Interop.Sys.LStat(fullPath, out fileinfo) < 0)
+ {
+ errorInfo = Interop.Sys.GetLastErrorInfo();
+ return false;
+ }
+
+ // Something exists at this path. If the caller is asking for a directory, return true if it's
+ // a directory and false for everything else. If the caller is asking for a file, return false for
+ // a directory and true for everything else.
+ return
+ (fileType == Interop.Sys.FileTypes.S_IFDIR) ==
+ ((fileinfo.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR);
+ }
+
+ /// Determines whether the specified directory name should be ignored.
+ /// The name to evaluate.
+ /// true if the name is "." or ".."; otherwise, false.
+ private static bool ShouldIgnoreDirectory(string name)
+ {
+ return name == "." || name == "..";
+ }
+
+ public static FileAttributes GetAttributes(string fullPath)
+ {
+ FileAttributes attributes = new FileInfo(fullPath, null).Attributes;
+
+ if (attributes == (FileAttributes)(-1))
+ FileSystemInfo.ThrowNotFound(fullPath);
+
+ return attributes;
+ }
+
+ public static void SetAttributes(string fullPath, FileAttributes attributes)
+ {
+ new FileInfo(fullPath, null).Attributes = attributes;
+ }
+
+ public static DateTimeOffset GetCreationTime(string fullPath)
+ {
+ return new FileInfo(fullPath, null).CreationTime;
+ }
+
+ public static void SetCreationTime(string fullPath, DateTimeOffset time, bool asDirectory)
+ {
+ FileSystemInfo info = asDirectory ?
+ (FileSystemInfo)new DirectoryInfo(fullPath, null) :
+ (FileSystemInfo)new FileInfo(fullPath, null);
+
+ info.CreationTimeCore = time;
+ }
+
+ public static DateTimeOffset GetLastAccessTime(string fullPath)
+ {
+ return new FileInfo(fullPath, null).LastAccessTime;
+ }
+
+ public static void SetLastAccessTime(string fullPath, DateTimeOffset time, bool asDirectory)
+ {
+ FileSystemInfo info = asDirectory ?
+ (FileSystemInfo)new DirectoryInfo(fullPath, null) :
+ (FileSystemInfo)new FileInfo(fullPath, null);
+
+ info.LastAccessTimeCore = time;
+ }
+
+ public static DateTimeOffset GetLastWriteTime(string fullPath)
+ {
+ return new FileInfo(fullPath, null).LastWriteTime;
+ }
+
+ public static void SetLastWriteTime(string fullPath, DateTimeOffset time, bool asDirectory)
+ {
+ FileSystemInfo info = asDirectory ?
+ (FileSystemInfo)new DirectoryInfo(fullPath, null) :
+ (FileSystemInfo)new FileInfo(fullPath, null);
+
+ info.LastWriteTimeCore = time;
+ }
+
+ public static string[] GetLogicalDrives()
+ {
+ return DriveInfoInternal.GetLogicalDrives();
+ }
+ }
+}
diff --git a/external/corefx-bugfix/src/System.IO.FileSystem/src/System/IO/FileSystem.Windows.cs b/external/corefx-bugfix/src/System.IO.FileSystem/src/System/IO/FileSystem.Windows.cs
index 97aac4363905..459c68fdf86c 100644
--- a/external/corefx-bugfix/src/System.IO.FileSystem/src/System/IO/FileSystem.Windows.cs
+++ b/external/corefx-bugfix/src/System.IO.FileSystem/src/System/IO/FileSystem.Windows.cs
@@ -38,7 +38,7 @@ public static void CopyFile(string sourceFullPath, string destFullPath, bool ove
if (errorCode == Interop.Errors.ERROR_ACCESS_DENIED)
{
if (DirectoryExists(destFullPath))
- throw new IOException(SR.Format(SR.Arg_FileIsDirectory_Name, destFullPath), Interop.Errors.ERROR_ACCESS_DENIED);
+ throw new System.UnauthorizedAccessException(SR.Format(SR.Arg_FileIsDirectory_Name, destFullPath));
}
}
diff --git a/mcs/class/corlib/unix_build_corlib.dll.sources b/mcs/class/corlib/unix_build_corlib.dll.sources
index a3ebc60f09cb..99280b6fcf01 100644
--- a/mcs/class/corlib/unix_build_corlib.dll.sources
+++ b/mcs/class/corlib/unix_build_corlib.dll.sources
@@ -1,5 +1,5 @@
#include corlib.dll.sources
-../../../external/corefx/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs
+../../../external/corefx-bugfix/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs
../../../external/corefx/src/System.IO.FileSystem/src/System/IO/FileSystemInfo.Unix.cs
../../../external/corefx/src/System.IO.FileSystem/src/System/IO/FileStatus.Unix.cs
../../../external/corefx/src/Common/src/CoreLib/Internal/IO/File.Unix.cs