From 11c2422dbcefcab26eae25efd6d06aa9ec97ed27 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Mon, 30 May 2022 11:34:36 +0200 Subject: [PATCH 01/42] Implement UnixFileMode APIs on Unix. --- .../FileSystem.DirectoryCreation.Windows.cs | 4 +- .../Win32/SafeHandles/SafeFileHandle.Unix.cs | 51 +++++++++++-------- .../SafeHandles/SafeFileHandle.Windows.cs | 6 ++- .../src/Resources/Strings.resx | 3 ++ .../System.Private.CoreLib.Shared.projitems | 1 + .../src/System/IO/Directory.cs | 8 ++- .../src/System/IO/File.cs | 12 +++++ .../src/System/IO/FileStatus.Unix.cs | 43 ++++++++++++++++ .../src/System/IO/FileStream.cs | 4 +- .../src/System/IO/FileStreamOptions.cs | 18 +++++++ .../src/System/IO/FileSystem.Unix.cs | 50 +++++++++++++++--- .../src/System/IO/FileSystem.Windows.cs | 12 +++++ .../src/System/IO/FileSystem.cs | 14 +++++ .../src/System/IO/FileSystemInfo.Unix.cs | 6 +++ .../src/System/IO/FileSystemInfo.Windows.cs | 14 +++++ .../src/System/IO/FileSystemInfo.cs | 6 +++ .../AsyncWindowsFileStreamStrategy.cs | 4 +- .../IO/Strategies/FileStreamHelpers.Unix.cs | 4 +- .../Strategies/FileStreamHelpers.Windows.cs | 6 +-- .../System/IO/Strategies/FileStreamHelpers.cs | 4 +- .../IO/Strategies/OSFileStreamStrategy.cs | 4 +- .../SyncWindowsFileStreamStrategy.cs | 4 +- .../IO/Strategies/UnixFileStreamStrategy.cs | 4 +- .../src/System/IO/UnixFileMode.cs | 22 ++++++++ .../System.Runtime/ref/System.Runtime.cs | 24 +++++++++ 25 files changed, 280 insertions(+), 48 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/IO/UnixFileMode.cs diff --git a/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs b/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs index dba07a8fb4156..9ae58805d6dd3 100644 --- a/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs +++ b/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs @@ -13,8 +13,10 @@ namespace System.IO { internal static partial class FileSystem { - public static unsafe void CreateDirectory(string fullPath, byte[]? securityDescriptor = null) + public static unsafe void CreateDirectory(string fullPath, byte[]? securityDescriptor = null, UnixFileMode? unixCreateMode = null) { + // TODO (Windows): apply unixCreateMode. 'umask' ... ? + // 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 diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs index 73f4c3014a33b..2ff0d4a5cbecd 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs @@ -11,6 +11,29 @@ namespace Microsoft.Win32.SafeHandles { public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid { + private const UnixFileMode PermissionMask = + UnixFileMode.UserRead | + UnixFileMode.UserWrite | + UnixFileMode.UserExecute | + UnixFileMode.GroupRead | + UnixFileMode.GroupWrite | + UnixFileMode.GroupExecute | + UnixFileMode.OtherRead | + UnixFileMode.OtherWrite | + UnixFileMode.OtherExecute; + + // If the file gets created a new, we'll select the permissions for it. Most Unix utilities by default use 666 (read and + // write for all), so we do the same (even though this doesn't match Windows, where by default it's possible to write out + // a file and then execute it). No matter what we choose, it'll be subject to the umask applied by the system, such that the + // actual permissions will typically be less than what we select here. + internal const UnixFileMode DefaultCreateMode = + UnixFileMode.UserRead | + UnixFileMode.UserWrite | + UnixFileMode.GroupRead | + UnixFileMode.GroupWrite | + UnixFileMode.OtherRead | + UnixFileMode.OtherWrite; + internal static bool DisableFileLocking { get; } = OperatingSystem.IsBrowser() // #40065: Emscripten does not support file locking || AppContextConfigHelper.GetBooleanConfig("System.IO.DisableFileLocking", "DOTNET_SYSTEM_IO_DISABLEFILELOCKING", defaultValue: false); @@ -164,35 +187,23 @@ public override bool IsInvalid } } - // If the file gets created a new, we'll select the permissions for it. Most Unix utilities by default use 666 (read and - // write for all), so we do the same (even though this doesn't match Windows, where by default it's possible to write out - // a file and then execute it). No matter what we choose, it'll be subject to the umask applied by the system, such that the - // actual permissions will typically be less than what we select here. - private const Interop.Sys.Permissions DefaultOpenPermissions = - Interop.Sys.Permissions.S_IRUSR | Interop.Sys.Permissions.S_IWUSR | - Interop.Sys.Permissions.S_IRGRP | Interop.Sys.Permissions.S_IWGRP | - Interop.Sys.Permissions.S_IROTH | Interop.Sys.Permissions.S_IWOTH; - // Specialized Open that returns the file length and permissions of the opened file. // This information is retrieved from the 'stat' syscall that must be performed to ensure the path is not a directory. - internal static SafeFileHandle OpenReadOnly(string fullPath, FileOptions options, out long fileLength, out Interop.Sys.Permissions filePermissions) + internal static SafeFileHandle OpenReadOnly(string fullPath, FileOptions options, out long fileLength, out UnixFileMode filePermissions) { - SafeFileHandle handle = Open(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read, options, preallocationSize: 0, DefaultOpenPermissions, out fileLength, out filePermissions, null); + SafeFileHandle handle = Open(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read, options, preallocationSize: 0, DefaultCreateMode, out fileLength, out filePermissions, null); Debug.Assert(fileLength >= 0); return handle; } - internal static SafeFileHandle Open(string fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize, - Interop.Sys.Permissions openPermissions = DefaultOpenPermissions, + internal static SafeFileHandle Open(string fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize, UnixFileMode? unixCreateMode = null, Func? createOpenException = null) { - return Open(fullPath, mode, access, share, options, preallocationSize, openPermissions, out _, out _, createOpenException); + return Open(fullPath, mode, access, share, options, preallocationSize, unixCreateMode ?? DefaultCreateMode, out _, out _, createOpenException); } - private static SafeFileHandle Open(string fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize, - Interop.Sys.Permissions openPermissions, - out long fileLength, - out Interop.Sys.Permissions filePermissions, + private static SafeFileHandle Open(string fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize, UnixFileMode openPermissions, + out long fileLength, out UnixFileMode filePermissions, Func? createOpenException = null) { // Translate the arguments into arguments for an open call. @@ -305,7 +316,7 @@ private static Interop.Sys.OpenFlags PreOpenConfigurationFromOptions(FileMode mo } private bool Init(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize, - out long fileLength, out Interop.Sys.Permissions filePermissions) + out long fileLength, out UnixFileMode filePermissions) { Interop.Sys.FileStatus status = default; bool statusHasValue = false; @@ -334,7 +345,7 @@ private bool Init(string path, FileMode mode, FileAccess access, FileShare share } fileLength = status.Size; - filePermissions = (Interop.Sys.Permissions)(status.Mode & (int)Interop.Sys.Permissions.Mask); + filePermissions = (UnixFileMode)(status.Mode & (int)PermissionMask); } IsAsync = (options & FileOptions.Asynchronous) != 0; diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs index d6bbfdd166bf0..f30f03d6f1a20 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs @@ -35,10 +35,14 @@ internal bool TryGetCachedLength(out long cachedLength) return _lengthCanBeCached && cachedLength >= 0; } - internal static unsafe SafeFileHandle Open(string fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize) + internal static unsafe SafeFileHandle Open(string fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize, UnixFileMode? unixCreateMode = null) { using (DisableMediaInsertionPrompt.Create()) { + // TODO (Windows): set unixCreateMode. + // On Unix, this gets filtered by the process umask. + // Is there a umask we can use? Or should we assume a umask, like 002, or 022? + // we don't use NtCreateFile as there is no public and reliable way // of converting DOS to NT file paths (RtlDosPathNameToRelativeNtPathName_U_WithStatus is not documented) SafeFileHandle fileHandle = CreateFile(fullPath, mode, access, share, options); diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index b1deeb8c26484..693143e30f2d9 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3748,6 +3748,9 @@ Invalid File or Directory attributes value. + + Invalid UnixFileMode value. + Second path fragment must not be a drive or UNC name. diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index bc0a83e466f5e..603e1f1310079 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -461,6 +461,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs index 8e85f3a74e2cc..91687e71fa23c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs @@ -24,12 +24,18 @@ public static partial class Directory } public static DirectoryInfo CreateDirectory(string path) + => CreateDirectory(path, null); + + public static DirectoryInfo CreateDirectory(string path, UnixFileMode unixCreateMode) + => CreateDirectory(path, (UnixFileMode?)unixCreateMode); + + private static DirectoryInfo CreateDirectory(string path, UnixFileMode? unixCreateMode) { ArgumentException.ThrowIfNullOrEmpty(path); string fullPath = Path.GetFullPath(path); - FileSystem.CreateDirectory(fullPath); + FileSystem.CreateDirectory(fullPath, unixCreateMode); return new DirectoryInfo(path, fullPath, isNormalized: true); } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index de14b513cfccc..7982b41616f75 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -220,6 +220,18 @@ public static FileAttributes GetAttributes(string path) public static void SetAttributes(string path, FileAttributes fileAttributes) => FileSystem.SetAttributes(Path.GetFullPath(path), fileAttributes); + public static UnixFileMode GetUnixFileMode(string path) + => FileSystem.GetUnixFileMode(Path.GetFullPath(path)); + + public static UnixFileMode GetUnixFileMode(SafeFileHandle fileHandle) + => FileSystem.GetUnixFileMode(fileHandle); + + public static void SetUnixFileMode(string path, UnixFileMode mode) + => FileSystem.SetUnixFileMode(Path.GetFullPath(path), mode); + + public static void SetUnixFileMode(SafeFileHandle fileHandle, UnixFileMode mode) + => FileSystem.SetUnixFileMode(fileHandle, mode); + public static FileStream OpenRead(string path) => new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs index e90408a7481e2..5c3355ac47650 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; namespace System.IO { @@ -399,6 +400,48 @@ internal long GetLength(ReadOnlySpan path, bool continueOnError = false) return EntryExists ? _fileCache.Size : 0; } + internal UnixFileMode GetUnixFileMode(ReadOnlySpan path, bool continueOnError = false) + => GetUnixFileMode(handle: null, path, continueOnError); + + internal UnixFileMode GetUnixFileMode(SafeFileHandle handle, bool continueOnError = false) + => GetUnixFileMode(handle, handle.Path, continueOnError); + + private UnixFileMode GetUnixFileMode(SafeFileHandle? handle, ReadOnlySpan path, bool continueOnError = false) + { + EnsureCachesInitialized(handle, path, continueOnError); + + if (!EntryExists) + return (UnixFileMode)(-1); + + return (UnixFileMode)(_fileCache.Mode & (int)FileSystem.ValidUnixFileModes); + } + + internal void SetUnixFileMode(string path, UnixFileMode mode) + => SetUnixFileMode(handle: null, path, mode); + + internal void SetUnixFileMode(SafeFileHandle handle, UnixFileMode mode) + => SetUnixFileMode(handle, handle.Path, mode); + + private void SetUnixFileMode(SafeFileHandle? handle, string? path, UnixFileMode mode) + { + if ((mode & ~FileSystem.ValidUnixFileModes) != 0) + { + // Using constant string for argument to match historical throw + throw new ArgumentException(SR.Arg_InvalidUnixFileMode, "UnixFileMode"); + } + + EnsureCachesInitialized(handle, path); + + if (!EntryExists) + FileSystemInfo.ThrowNotFound(path); + + int rv = handle is not null ? Interop.Sys.FChMod(handle, (int)mode) : + Interop.Sys.ChMod(path!, (int)mode); + Interop.CheckIo(rv, path); + + InvalidateCaches(); + } + // Tries to refresh the lstat cache (_fileCache). // This method should not throw. Instead, we store the results, and we will throw when the user attempts to access any of the properties when there was a failure internal void RefreshCaches(ReadOnlySpan path) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs index 9dcec98522b52..23ea5bf333303 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs @@ -203,14 +203,14 @@ public FileStream(string path, FileStreamOptions options) FileStreamHelpers.SerializationGuard(options.Access); _strategy = FileStreamHelpers.ChooseStrategy( - this, path, options.Mode, options.Access, options.Share, options.BufferSize, options.Options, options.PreallocationSize); + this, path, options.Mode, options.Access, options.Share, options.BufferSize, options.Options, options.PreallocationSize, options.UnixCreateMode); } private FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize) { FileStreamHelpers.ValidateArguments(path, mode, access, share, bufferSize, options, preallocationSize); - _strategy = FileStreamHelpers.ChooseStrategy(this, path, mode, access, share, bufferSize, options, preallocationSize); + _strategy = FileStreamHelpers.ChooseStrategy(this, path, mode, access, share, bufferSize, options, preallocationSize, unixCreateMode: null); } [Obsolete("FileStream.Handle has been deprecated. Use FileStream's SafeFileHandle property instead.")] diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs index 491d739de40f5..c9a4dbdf9f5d5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs @@ -11,6 +11,7 @@ public sealed class FileStreamOptions private FileOptions _options; private long _preallocationSize; private int _bufferSize = FileStream.DefaultBufferSize; + private UnixFileMode? _unixCreateMode; /// /// One of the enumeration values that determines how to open or create the file. @@ -109,5 +110,22 @@ public int BufferSize get => _bufferSize; set => _bufferSize = value >= 0 ? value : throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum); } + + /// + /// Unix file mode used when a new file is created. + /// + public UnixFileMode? UnixCreateMode + { + get => _unixCreateMode; + set + { + if ((value & ~FileSystem.ValidUnixFileModes) != 0) + { + ThrowHelper.ArgumentOutOfRangeException_Enum_Value(); + } + + _unixCreateMode = value; + } + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs index 0e64c0b4d34d0..347c95ad7821f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs @@ -16,13 +16,25 @@ internal static partial class FileSystem // See: https://man7.org/linux/man-pages/man7/path_resolution.7.html private const int MaxFollowedLinks = 40; + // This gets filtered by umask. + internal const UnixFileMode DefaultUnixCreateDirectoryMode = + UnixFileMode.UserRead | + UnixFileMode.UserWrite | + UnixFileMode.UserExecute | + UnixFileMode.GroupRead | + UnixFileMode.GroupWrite | + UnixFileMode.GroupExecute | + UnixFileMode.OtherRead | + UnixFileMode.OtherWrite | + UnixFileMode.OtherExecute; + public static void CopyFile(string sourceFullPath, string destFullPath, bool overwrite) { long fileLength; - Interop.Sys.Permissions filePermissions; + UnixFileMode filePermissions; using SafeFileHandle src = SafeFileHandle.OpenReadOnly(sourceFullPath, FileOptions.None, out fileLength, out filePermissions); using SafeFileHandle dst = SafeFileHandle.Open(destFullPath, overwrite ? FileMode.Create : FileMode.CreateNew, - FileAccess.ReadWrite, FileShare.None, FileOptions.None, preallocationSize: 0, openPermissions: filePermissions, + FileAccess.ReadWrite, FileShare.None, FileOptions.None, preallocationSize: 0, filePermissions, (Interop.ErrorInfo error, Interop.Sys.OpenFlags flags, string path) => CreateOpenException(error, flags, path)); Interop.CheckIo(Interop.Sys.CopyFile(src, dst, fileLength)); @@ -278,7 +290,10 @@ public static void DeleteFile(string fullPath) } } - public static void CreateDirectory(string fullPath) + public static void CreateDirectory(string fullPath, UnixFileMode? unixCreateMode = null) + => CreateDirectory(fullPath, unixCreateMode ?? DefaultUnixCreateDirectoryMode); + + public static void CreateDirectory(string fullPath, UnixFileMode unixCreateMode) { // The argument is a full path, which means it is an absolute path that // doesn't contain "//", "/./", and "/../". @@ -290,7 +305,7 @@ public static void CreateDirectory(string fullPath) return; // fullPath is '/'. } - int result = Interop.Sys.MkDir(fullPath, (int)Interop.Sys.Permissions.Mask); + int result = Interop.Sys.MkDir(fullPath, (int)unixCreateMode); if (result == 0) { return; // Created directory. @@ -303,7 +318,7 @@ public static void CreateDirectory(string fullPath) } else if (errorInfo.Error == Interop.Error.ENOENT) // Some parts of the path don't exist yet. { - CreateParentsAndDirectory(fullPath); + CreateParentsAndDirectory(fullPath, unixCreateMode); } else { @@ -311,7 +326,7 @@ public static void CreateDirectory(string fullPath) } } - private static void CreateParentsAndDirectory(string fullPath) + private static void CreateParentsAndDirectory(string fullPath, UnixFileMode unixCreateMode) { // Try create parents bottom to top and track those that could not // be created due to missing parents. Then create them top to bottom. @@ -334,7 +349,7 @@ private static void CreateParentsAndDirectory(string fullPath) } ReadOnlySpan mkdirPath = fullPath.AsSpan(0, i); - int result = Interop.Sys.MkDir(mkdirPath, (int)Interop.Sys.Permissions.Mask); + int result = Interop.Sys.MkDir(mkdirPath, (int)unixCreateMode); if (result == 0) { break; // Created parent. @@ -366,7 +381,7 @@ private static void CreateParentsAndDirectory(string fullPath) for (i = stackDir.Length - 1; i >= 0; i--) { ReadOnlySpan mkdirPath = fullPath.AsSpan(0, stackDir[i]); - int result = Interop.Sys.MkDir(mkdirPath, (int)Interop.Sys.Permissions.Mask); + int result = Interop.Sys.MkDir(mkdirPath, (int)unixCreateMode); if (result < 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); @@ -588,6 +603,25 @@ public static FileAttributes GetAttributes(string fullPath) public static void SetAttributes(string fullPath, FileAttributes attributes) => default(FileStatus).SetAttributes(fullPath, attributes, asDirectory: false); + public static UnixFileMode GetUnixFileMode(string fullPath) + { + UnixFileMode mode = default(FileStatus).GetUnixFileMode(fullPath); + + if (mode == (UnixFileMode)(-1)) + FileSystemInfo.ThrowNotFound(fullPath); + + return mode; + } + + public static UnixFileMode GetUnixFileMode(SafeFileHandle fileHandle) + => default(FileStatus).GetUnixFileMode(fileHandle); + + public static void SetUnixFileMode(string fullPath, UnixFileMode mode) + => default(FileStatus).SetUnixFileMode(fullPath, mode); + + public static void SetUnixFileMode(SafeFileHandle fileHandle, UnixFileMode mode) + => default(FileStatus).SetUnixFileMode(fileHandle, mode); + public static DateTimeOffset GetCreationTime(string fullPath) => default(FileStatus).GetCreationTime(fullPath).UtcDateTime; diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs index c9ecc55ca59c5..c2545adc0ac8f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs @@ -459,6 +459,18 @@ public static void SetLastAccessTime(string fullPath, DateTimeOffset time, bool public static void SetLastWriteTime(string fullPath, DateTimeOffset time, bool asDirectory) => SetFileTime(fullPath, asDirectory, lastWriteTime: time.ToFileTime()); + public static UnixFileMode GetUnixFileMode(string fullPath) + => throw NotImplementedException(); // TODO (Windows) + + public static UnixFileMode GetUnixFileMode(SafeFileHandle fileHandle) + => throw NotImplementedException(); // TODO (Windows) + + public static void SetUnixFileMode(string fullPath, UnixFileMode mode) + => throw NotImplementedException(); // TODO (Windows) + + public static void SetUnixFileMode(SafeFileHandle fileHandle, UnixFileMode mode) + => throw NotImplementedException(); // TODO (Windows) + public static string[] GetLogicalDrives() => DriveInfoInternal.GetLogicalDrives(); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.cs index aee8736cd8321..392791fd462b6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.cs @@ -5,6 +5,20 @@ namespace System.IO { internal static partial class FileSystem { + internal const UnixFileMode ValidUnixFileModes = + UnixFileMode.UserRead | + UnixFileMode.UserWrite | + UnixFileMode.UserExecute | + UnixFileMode.GroupRead | + UnixFileMode.GroupWrite | + UnixFileMode.GroupExecute | + UnixFileMode.OtherRead | + UnixFileMode.OtherWrite | + UnixFileMode.OtherExecute | + UnixFileMode.StickyBit | + UnixFileMode.SetGroup | + UnixFileMode.SetUser; + internal static void VerifyValidPath(string path, string argName) { ArgumentException.ThrowIfNullOrEmpty(path, argName); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs index f74e3c411dd6f..a1113a1fd10e8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs @@ -63,6 +63,12 @@ internal DateTimeOffset LastWriteTimeCore internal long LengthCore => _fileStatus.GetLength(FullPath); + internal UnixFileMode UnixFileModeCore + { + get => _fileStatus.GetUnixFileMode(FullPath); + set => _fileStatus.SetUnixFileMode(FullPath, value); + } + public void Refresh() { _linkTargetIsValid = false; diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs index cadd69ce7e67a..469b3b09a7434 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs @@ -130,6 +130,20 @@ internal long LengthCore } } + internal UnixFileMode UnixFileModeCore + { + get + { + EnsureDataInitialized(); + throw new NotImplementedException(); // TODO (Windows) + } + set + { + FileSystem.SetUnixFileMode(FullPath, value); + _dataInitialized = -1; + } + } + private void EnsureDataInitialized() { if (_dataInitialized == -1) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs index 26a06b20986f8..fb8d087540104 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs @@ -130,6 +130,12 @@ public string? LinkTarget } } + public UnixFileMode UnixFileMode + { + get => UnixFileModeCore; + set => UnixFileModeCore = value; + } + /// /// Creates a symbolic link located in that points to the specified . /// diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs index 82ed7659b8363..0ed73e5e74cc7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs @@ -14,8 +14,8 @@ internal AsyncWindowsFileStreamStrategy(SafeFileHandle handle, FileAccess access { } - internal AsyncWindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize) - : base(path, mode, access, share, options, preallocationSize) + internal AsyncWindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize, UnixFileMode? unixCreateMode) + : base(path, mode, access, share, options, preallocationSize, unixCreateMode) { } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs index 3df3aa692f64c..534a49217408c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs @@ -13,8 +13,8 @@ private static OSFileStreamStrategy ChooseStrategyCore(SafeFileHandle handle, Fi new UnixFileStreamStrategy(handle, access); #pragma warning restore IDE0060 - private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize) => - new UnixFileStreamStrategy(path, mode, access, share, options, preallocationSize); + private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize, UnixFileMode? unixCreateMode) => + new UnixFileStreamStrategy(path, mode, access, share, options, preallocationSize, unixCreateMode); internal static long CheckFileCall(long result, string? path, bool ignoreNotSupported = false) { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs index 3e90bf225385c..4cc2b56008c58 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs @@ -31,10 +31,10 @@ private static OSFileStreamStrategy ChooseStrategyCore(SafeFileHandle handle, Fi new AsyncWindowsFileStreamStrategy(handle, access) : new SyncWindowsFileStreamStrategy(handle, access); - private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize) => + private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize, UnixFileMode? unixCreateMode) => (options & FileOptions.Asynchronous) != 0 ? - new AsyncWindowsFileStreamStrategy(path, mode, access, share, options, preallocationSize) : - new SyncWindowsFileStreamStrategy(path, mode, access, share, options, preallocationSize); + new AsyncWindowsFileStreamStrategy(path, mode, access, share, options, preallocationSize, unixCreateMode) : + new SyncWindowsFileStreamStrategy(path, mode, access, share, options, preallocationSize, unixCreateMode); internal static void FlushToDisk(SafeFileHandle handle) { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs index 5a067fd76ee46..e0aeb3da747e5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs @@ -19,10 +19,10 @@ internal static FileStreamStrategy ChooseStrategy(FileStream fileStream, SafeFil return WrapIfDerivedType(fileStream, strategy); } - internal static FileStreamStrategy ChooseStrategy(FileStream fileStream, string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize) + internal static FileStreamStrategy ChooseStrategy(FileStream fileStream, string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize, UnixFileMode? unixCreateMode) { FileStreamStrategy strategy = - EnableBufferingIfNeeded(ChooseStrategyCore(path, mode, access, share, options, preallocationSize), bufferSize); + EnableBufferingIfNeeded(ChooseStrategyCore(path, mode, access, share, options, preallocationSize, unixCreateMode), bufferSize); return WrapIfDerivedType(fileStream, strategy); } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs index 912b6481cb3c2..20df91acc9341 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs @@ -37,13 +37,13 @@ internal OSFileStreamStrategy(SafeFileHandle handle, FileAccess access) _fileHandle = handle; } - internal OSFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize) + internal OSFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize, UnixFileMode? unixCreateMode) { string fullPath = Path.GetFullPath(path); _access = access; - _fileHandle = SafeFileHandle.Open(fullPath, mode, access, share, options, preallocationSize); + _fileHandle = SafeFileHandle.Open(fullPath, mode, access, share, options, preallocationSize, unixCreateMode); try { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs index 5e2513252a510..6166df51ad83e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs @@ -11,8 +11,8 @@ internal SyncWindowsFileStreamStrategy(SafeFileHandle handle, FileAccess access) { } - internal SyncWindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize) - : base(path, mode, access, share, options, preallocationSize) + internal SyncWindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize, UnixFileMode? unixCreateMode) + : base(path, mode, access, share, options, preallocationSize, unixCreateMode) { } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs index 3c54f1b23a476..0ab7d5cb7f086 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs @@ -11,8 +11,8 @@ internal UnixFileStreamStrategy(SafeFileHandle handle, FileAccess access) : base { } - internal UnixFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize) : - base(path, mode, access, share, options, preallocationSize) + internal UnixFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize, UnixFileMode? unixCreateMode) : + base(path, mode, access, share, options, preallocationSize, unixCreateMode) { } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/UnixFileMode.cs b/src/libraries/System.Private.CoreLib/src/System/IO/UnixFileMode.cs new file mode 100644 index 0000000000000..af182066fbd9d --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IO/UnixFileMode.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.IO; + +[Flags] +public enum UnixFileMode +{ + None = 0, + OtherExecute = 1, + OtherWrite = 2, + OtherRead = 4, + GroupExecute = 8, + GroupWrite = 16, + GroupRead = 32, + UserExecute = 64, + UserWrite = 128, + UserRead = 256, + StickyBit = 512, + SetGroup = 1024, + SetUser = 2048 +} diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 9aebff4f1e12a..ad5e5b32e31fd 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -9216,6 +9216,7 @@ public override void WriteByte(byte value) { } public static partial class Directory { public static System.IO.DirectoryInfo CreateDirectory(string path) { throw null; } + public static System.IO.DirectoryInfo CreateDirectory(string path, System.IO.UnixFileMode unixCreateMode) { throw null; } public static System.IO.FileSystemInfo CreateSymbolicLink(string path, string pathToTarget) { throw null; } public static void Delete(string path) { } public static void Delete(string path, bool recursive) { } @@ -9359,6 +9360,8 @@ public static void Encrypt(string path) { } public static System.DateTime GetLastAccessTimeUtc(string path) { throw null; } public static System.DateTime GetLastWriteTime(string path) { throw null; } public static System.DateTime GetLastWriteTimeUtc(string path) { throw null; } + public static System.IO.UnixFileMode GetUnixFileMode(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; } + public static System.IO.UnixFileMode GetUnixFileMode(string path) { throw null; } public static void Move(string sourceFileName, string destFileName) { } public static void Move(string sourceFileName, string destFileName, bool overwrite) { } public static System.IO.FileStream Open(string path, System.IO.FileMode mode) { throw null; } @@ -9393,6 +9396,8 @@ public static void SetLastAccessTime(string path, System.DateTime lastAccessTime public static void SetLastAccessTimeUtc(string path, System.DateTime lastAccessTimeUtc) { } public static void SetLastWriteTime(string path, System.DateTime lastWriteTime) { } public static void SetLastWriteTimeUtc(string path, System.DateTime lastWriteTimeUtc) { } + public static void SetUnixFileMode(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, System.IO.UnixFileMode mode) { } + public static void SetUnixFileMode(string path, System.IO.UnixFileMode mode) { } public static void WriteAllBytes(string path, byte[] bytes) { } public static System.Threading.Tasks.Task WriteAllBytesAsync(string path, byte[] bytes, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static void WriteAllLines(string path, System.Collections.Generic.IEnumerable contents) { } @@ -9599,6 +9604,7 @@ public FileStreamOptions() { } public System.IO.FileOptions Options { get { throw null; } set { } } public long PreallocationSize { get { throw null; } set { } } public System.IO.FileShare Share { get { throw null; } set { } } + public System.IO.UnixFileMode? UnixCreateMode { get { throw null; } set { } } } public abstract partial class FileSystemInfo : System.MarshalByRefObject, System.Runtime.Serialization.ISerializable { @@ -9618,6 +9624,7 @@ protected FileSystemInfo(System.Runtime.Serialization.SerializationInfo info, Sy public System.DateTime LastWriteTimeUtc { get { throw null; } set { } } public string? LinkTarget { get { throw null; } } public abstract string Name { get; } + public System.IO.UnixFileMode UnixFileMode { get { throw null; } set { } } public void CreateAsSymbolicLink(string pathToTarget) { } public abstract void Delete(); public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } @@ -10075,6 +10082,23 @@ public virtual void WriteLine(ulong value) { } public virtual System.Threading.Tasks.Task WriteLineAsync(string? value) { throw null; } public virtual System.Threading.Tasks.Task WriteLineAsync(System.Text.StringBuilder? value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } + [System.FlagsAttribute] + public enum UnixFileMode + { + None = 0, + OtherExecute = 1, + OtherWrite = 2, + OtherRead = 4, + GroupExecute = 8, + GroupWrite = 16, + GroupRead = 32, + UserExecute = 64, + UserWrite = 128, + UserRead = 256, + StickyBit = 512, + SetGroup = 1024, + SetUser = 2048, + } public partial class UnmanagedMemoryStream : System.IO.Stream { protected UnmanagedMemoryStream() { } From b182b4dcb6f896b6fe093d2f4e4bfe87a45ab483 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Thu, 2 Jun 2022 11:39:51 +0200 Subject: [PATCH 02/42] Throw PNSE on Windows, add UnsupportedOSPlatform. --- .../IO/FileSystem.DirectoryCreation.Windows.cs | 7 +++++-- .../Win32/SafeHandles/SafeFileHandle.Windows.cs | 6 ++---- .../src/Resources/Strings.resx | 3 +++ .../src/System/IO/Directory.cs | 15 +++++++++++---- .../System.Private.CoreLib/src/System/IO/File.cs | 4 ++++ .../src/System/IO/FileStatus.Unix.cs | 1 - .../src/System/IO/FileStreamOptions.cs | 15 +++++++++++++-- .../src/System/IO/FileSystem.Unix.cs | 4 ++-- .../src/System/IO/FileSystem.Windows.cs | 8 ++++---- .../src/System/IO/FileSystemInfo.Windows.cs | 8 ++++---- .../src/System/IO/FileSystemInfo.cs | 2 ++ .../System.Runtime/ref/System.Runtime.cs | 9 +++++++-- 12 files changed, 57 insertions(+), 25 deletions(-) diff --git a/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs b/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs index 9ae58805d6dd3..3850803846743 100644 --- a/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs +++ b/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs @@ -13,10 +13,13 @@ namespace System.IO { internal static partial class FileSystem { - public static unsafe void CreateDirectory(string fullPath, byte[]? securityDescriptor = null, UnixFileMode? unixCreateMode = null) + public static void CreateDirectory(string fullPath, UnixFileMode unixCreateMode) { - // TODO (Windows): apply unixCreateMode. 'umask' ... ? + throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); + } + public static unsafe void CreateDirectory(string fullPath, byte[]? securityDescriptor = null) + { // 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 diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs index f30f03d6f1a20..66218ec2c61a8 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs @@ -37,12 +37,10 @@ internal bool TryGetCachedLength(out long cachedLength) internal static unsafe SafeFileHandle Open(string fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize, UnixFileMode? unixCreateMode = null) { + Debug.Assert(!unixCreateMode.HasValue); + using (DisableMediaInsertionPrompt.Create()) { - // TODO (Windows): set unixCreateMode. - // On Unix, this gets filtered by the process umask. - // Is there a umask we can use? Or should we assume a umask, like 002, or 022? - // we don't use NtCreateFile as there is no public and reliable way // of converting DOS to NT file paths (RtlDosPathNameToRelativeNtPathName_U_WithStatus is not documented) SafeFileHandle fileHandle = CreateFile(fullPath, mode, access, share, options); diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 693143e30f2d9..71c84bd4cf0df 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3088,6 +3088,9 @@ Strong-name signing is not supported on this platform. + + Unix file modes are not supported on this platform. + This API is specific to the way in which Windows handles asynchronous I/O, and is not supported on this platform. diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs index 91687e71fa23c..f4fa955780ece 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Enumeration; +using System.Runtime.Versioning; namespace System.IO { @@ -24,12 +25,18 @@ public static partial class Directory } public static DirectoryInfo CreateDirectory(string path) - => CreateDirectory(path, null); + { + ArgumentException.ThrowIfNullOrEmpty(path); - public static DirectoryInfo CreateDirectory(string path, UnixFileMode unixCreateMode) - => CreateDirectory(path, (UnixFileMode?)unixCreateMode); + string fullPath = Path.GetFullPath(path); + + FileSystem.CreateDirectory(fullPath); - private static DirectoryInfo CreateDirectory(string path, UnixFileMode? unixCreateMode) + return new DirectoryInfo(path, fullPath, isNormalized: true); + } + + [UnsupportedOSPlatform("windows")] + public static DirectoryInfo CreateDirectory(string path, UnixFileMode unixCreateMode) { ArgumentException.ThrowIfNullOrEmpty(path); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index 7982b41616f75..8758b174dde50 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -220,15 +220,19 @@ public static FileAttributes GetAttributes(string path) public static void SetAttributes(string path, FileAttributes fileAttributes) => FileSystem.SetAttributes(Path.GetFullPath(path), fileAttributes); + [UnsupportedOSPlatform("windows")] public static UnixFileMode GetUnixFileMode(string path) => FileSystem.GetUnixFileMode(Path.GetFullPath(path)); + [UnsupportedOSPlatform("windows")] public static UnixFileMode GetUnixFileMode(SafeFileHandle fileHandle) => FileSystem.GetUnixFileMode(fileHandle); + [UnsupportedOSPlatform("windows")] public static void SetUnixFileMode(string path, UnixFileMode mode) => FileSystem.SetUnixFileMode(Path.GetFullPath(path), mode); + [UnsupportedOSPlatform("windows")] public static void SetUnixFileMode(SafeFileHandle fileHandle, UnixFileMode mode) => FileSystem.SetUnixFileMode(fileHandle, mode); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs index 5c3355ac47650..1b77d232ca894 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs @@ -426,7 +426,6 @@ private void SetUnixFileMode(SafeFileHandle? handle, string? path, UnixFileMode { if ((mode & ~FileSystem.ValidUnixFileModes) != 0) { - // Using constant string for argument to match historical throw throw new ArgumentException(SR.Arg_InvalidUnixFileMode, "UnixFileMode"); } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs index c9a4dbdf9f5d5..c15f358dfefb2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.Versioning; + namespace System.IO { public sealed class FileStreamOptions @@ -116,10 +118,19 @@ public int BufferSize /// public UnixFileMode? UnixCreateMode { - get => _unixCreateMode; + get + { + return _unixCreateMode; + } + [UnsupportedOSPlatform("windows")] set { - if ((value & ~FileSystem.ValidUnixFileModes) != 0) + if (OperatingSystem.IsWindows()) + { + throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); + } + + if (value.HasValue && ((value & ~FileSystem.ValidUnixFileModes) != 0)) { ThrowHelper.ArgumentOutOfRangeException_Enum_Value(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs index 347c95ad7821f..5d9f71c522a66 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs @@ -290,8 +290,8 @@ public static void DeleteFile(string fullPath) } } - public static void CreateDirectory(string fullPath, UnixFileMode? unixCreateMode = null) - => CreateDirectory(fullPath, unixCreateMode ?? DefaultUnixCreateDirectoryMode); + public static void CreateDirectory(string fullPath) + => CreateDirectory(fullPath, DefaultUnixCreateDirectoryMode); public static void CreateDirectory(string fullPath, UnixFileMode unixCreateMode) { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs index c2545adc0ac8f..4ea6615d0c6fd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs @@ -460,16 +460,16 @@ public static void SetLastWriteTime(string fullPath, DateTimeOffset time, bool a => SetFileTime(fullPath, asDirectory, lastWriteTime: time.ToFileTime()); public static UnixFileMode GetUnixFileMode(string fullPath) - => throw NotImplementedException(); // TODO (Windows) + => throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); public static UnixFileMode GetUnixFileMode(SafeFileHandle fileHandle) - => throw NotImplementedException(); // TODO (Windows) + => throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); public static void SetUnixFileMode(string fullPath, UnixFileMode mode) - => throw NotImplementedException(); // TODO (Windows) + => throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); public static void SetUnixFileMode(SafeFileHandle fileHandle, UnixFileMode mode) - => throw NotImplementedException(); // TODO (Windows) + => throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); public static string[] GetLogicalDrives() => DriveInfoInternal.GetLogicalDrives(); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs index 469b3b09a7434..61ed222498f28 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs @@ -130,19 +130,19 @@ internal long LengthCore } } +#pragma warning disable CA1822 internal UnixFileMode UnixFileModeCore { get { - EnsureDataInitialized(); - throw new NotImplementedException(); // TODO (Windows) + throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); } set { - FileSystem.SetUnixFileMode(FullPath, value); - _dataInitialized = -1; + throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); } } +#pragma warning restore CA1822 private void EnsureDataInitialized() { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs index fb8d087540104..9cc658b8c2cac 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Runtime.Serialization; +using System.Runtime.Versioning; namespace System.IO { @@ -130,6 +131,7 @@ public string? LinkTarget } } + [UnsupportedOSPlatform("windows")] public UnixFileMode UnixFileMode { get => UnixFileModeCore; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index ad5e5b32e31fd..42966c9329dcd 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -9216,6 +9216,7 @@ public override void WriteByte(byte value) { } public static partial class Directory { public static System.IO.DirectoryInfo CreateDirectory(string path) { throw null; } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] public static System.IO.DirectoryInfo CreateDirectory(string path, System.IO.UnixFileMode unixCreateMode) { throw null; } public static System.IO.FileSystemInfo CreateSymbolicLink(string path, string pathToTarget) { throw null; } public static void Delete(string path) { } @@ -9360,7 +9361,9 @@ public static void Encrypt(string path) { } public static System.DateTime GetLastAccessTimeUtc(string path) { throw null; } public static System.DateTime GetLastWriteTime(string path) { throw null; } public static System.DateTime GetLastWriteTimeUtc(string path) { throw null; } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] public static System.IO.UnixFileMode GetUnixFileMode(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { throw null; } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] public static System.IO.UnixFileMode GetUnixFileMode(string path) { throw null; } public static void Move(string sourceFileName, string destFileName) { } public static void Move(string sourceFileName, string destFileName, bool overwrite) { } @@ -9396,7 +9399,9 @@ public static void SetLastAccessTime(string path, System.DateTime lastAccessTime public static void SetLastAccessTimeUtc(string path, System.DateTime lastAccessTimeUtc) { } public static void SetLastWriteTime(string path, System.DateTime lastWriteTime) { } public static void SetLastWriteTimeUtc(string path, System.DateTime lastWriteTimeUtc) { } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] public static void SetUnixFileMode(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle, System.IO.UnixFileMode mode) { } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] public static void SetUnixFileMode(string path, System.IO.UnixFileMode mode) { } public static void WriteAllBytes(string path, byte[] bytes) { } public static System.Threading.Tasks.Task WriteAllBytesAsync(string path, byte[] bytes, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } @@ -9604,7 +9609,7 @@ public FileStreamOptions() { } public System.IO.FileOptions Options { get { throw null; } set { } } public long PreallocationSize { get { throw null; } set { } } public System.IO.FileShare Share { get { throw null; } set { } } - public System.IO.UnixFileMode? UnixCreateMode { get { throw null; } set { } } + public System.IO.UnixFileMode? UnixCreateMode { get { throw null; } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] set { } } } public abstract partial class FileSystemInfo : System.MarshalByRefObject, System.Runtime.Serialization.ISerializable { @@ -9624,7 +9629,7 @@ protected FileSystemInfo(System.Runtime.Serialization.SerializationInfo info, Sy public System.DateTime LastWriteTimeUtc { get { throw null; } set { } } public string? LinkTarget { get { throw null; } } public abstract string Name { get; } - public System.IO.UnixFileMode UnixFileMode { get { throw null; } set { } } + public System.IO.UnixFileMode UnixFileMode { [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] get { throw null; } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] set { } } public void CreateAsSymbolicLink(string pathToTarget) { } public abstract void Delete(); public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } From f1434999302cbc3643c7576ba15a665cb2def72a Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Thu, 2 Jun 2022 13:30:56 +0200 Subject: [PATCH 03/42] Fix API compat issue. --- .../System.Private.CoreLib/src/System/IO/FileSystemInfo.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs index 9cc658b8c2cac..3fe2b3b894feb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs @@ -131,10 +131,11 @@ public string? LinkTarget } } - [UnsupportedOSPlatform("windows")] public UnixFileMode UnixFileMode { + [UnsupportedOSPlatform("windows")] get => UnixFileModeCore; + [UnsupportedOSPlatform("windows")] set => UnixFileModeCore = value; } From 51386011d6a1ba4266a65f561f3236c9e8749b92 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Thu, 2 Jun 2022 13:31:55 +0200 Subject: [PATCH 04/42] Borrow a few things from SafeFileHandle API PR to this compiles. --- .../System.Private.CoreLib.Shared.projitems | 3 ++ .../src/System/IO/FileStatus.Unix.cs | 32 +++++++++++++------ .../src/System/IO/FileSystemInfo.Unix.cs | 4 +-- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 603e1f1310079..5b9ed5b962dda 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2008,6 +2008,9 @@ Common\Interop\Unix\System.Native\Interop.UnixFileSystemTypes.cs + + Common\Interop\Unix\System.Native\Interop.FChMod.cs + Common\Interop\Unix\System.Native\Interop.FLock.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs index 1b77d232ca894..97b2c4d94eb18 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs @@ -441,17 +441,27 @@ private void SetUnixFileMode(SafeFileHandle? handle, string? path, UnixFileMode InvalidateCaches(); } + internal void RefreshCaches(ReadOnlySpan path) + => RefreshCaches(handle: null, path); + // Tries to refresh the lstat cache (_fileCache). // This method should not throw. Instead, we store the results, and we will throw when the user attempts to access any of the properties when there was a failure - internal void RefreshCaches(ReadOnlySpan path) + internal void RefreshCaches(SafeFileHandle? handle, ReadOnlySpan path) { - path = Path.TrimEndingDirectorySeparator(path); - #if !TARGET_BROWSER _isReadOnlyCache = -1; #endif + int rv; + if (handle is not null) + { + rv = Interop.Sys.FStat(handle, out _fileCache); + } + else + { + path = Path.TrimEndingDirectorySeparator(path); + rv = Interop.Sys.LStat(path, out _fileCache); + } - int rv = Interop.Sys.LStat(path, out _fileCache); if (rv < 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); @@ -473,20 +483,24 @@ internal void RefreshCaches(ReadOnlySpan path) // Check if the main path is a directory, or a link to a directory. int fileType = _fileCache.Mode & Interop.Sys.FileTypes.S_IFMT; bool isDirectory = fileType == Interop.Sys.FileTypes.S_IFDIR || - (fileType == Interop.Sys.FileTypes.S_IFLNK - && Interop.Sys.Stat(path, out Interop.Sys.FileStatus target) == 0 - && (target.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR); + (handle is null && // Don't follow links for SafeHandle APIs. + fileType == Interop.Sys.FileTypes.S_IFLNK && + Interop.Sys.Stat(path, out Interop.Sys.FileStatus target) == 0 && + (target.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR); _state = isDirectory ? InitializedExistsDir : InitializedExistsFile; } + internal void EnsureCachesInitialized(ReadOnlySpan path, bool continueOnError = false) + => EnsureCachesInitialized(handle: null, path, continueOnError); + // Checks if the file cache is uninitialized and refreshes it's value. // If it failed, and continueOnError is set to true, this method will throw. - internal void EnsureCachesInitialized(ReadOnlySpan path, bool continueOnError = false) + internal void EnsureCachesInitialized(SafeFileHandle? handle, ReadOnlySpan path, bool continueOnError = false) { if (_state == Uninitialized) { - RefreshCaches(path); + RefreshCaches(handle, path); } if (!continueOnError) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs index a1113a1fd10e8..101beec05211a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs @@ -75,7 +75,7 @@ public void Refresh() _fileStatus.RefreshCaches(FullPath); } - internal static void ThrowNotFound(string path) + internal static void ThrowNotFound(string? path) { // 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 @@ -85,7 +85,7 @@ internal static void ThrowNotFound(string path) // being manipulated concurrently with these checks) is that we throw a // FileNotFoundException instead of DirectoryNotFoundException. - bool directoryError = !Directory.Exists(Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(path))); + bool directoryError = path is not null && !FileSystem.DirectoryExists(Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(path.AsSpan()))); throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(Interop.Error.ENOENT), path, directoryError); } From 4d6c524e0a16def9fef50c730c5602f308882283 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Thu, 2 Jun 2022 14:26:44 +0200 Subject: [PATCH 05/42] Fix System.IO.FileSystem.AccessControl compilation. --- .../src/System/IO/FileSystem.DirectoryCreation.Windows.cs | 5 ----- .../src/System/IO/FileSystem.Windows.cs | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs b/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs index 3850803846743..dba07a8fb4156 100644 --- a/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs +++ b/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs @@ -13,11 +13,6 @@ namespace System.IO { internal static partial class FileSystem { - public static void CreateDirectory(string fullPath, UnixFileMode unixCreateMode) - { - throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); - } - public static unsafe void CreateDirectory(string fullPath, byte[]? securityDescriptor = null) { // We can save a bunch of work if the directory we want to create already exists. This also diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs index 4ea6615d0c6fd..5938f5576debc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs @@ -10,6 +10,11 @@ namespace System.IO { internal static partial class FileSystem { + public static void CreateDirectory(string fullPath, UnixFileMode unixCreateMode) + { + throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); + } + public static void Encrypt(string path) { string fullPath = Path.GetFullPath(path); From 813e24ad29ff72fc7826a8784085aebcd533b15d Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Thu, 2 Jun 2022 14:31:02 +0200 Subject: [PATCH 06/42] Add xml docs. --- .../src/System/IO/Directory.cs | 23 ++++++++++ .../src/System/IO/File.cs | 44 +++++++++++++++++- .../src/System/IO/FileStreamOptions.cs | 1 + .../src/System/IO/FileSystemInfo.cs | 9 ++++ .../src/System/IO/UnixFileMode.cs | 45 ++++++++++++++++++- 5 files changed, 119 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs index f4fa955780ece..04d341feb1f04 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs @@ -35,11 +35,34 @@ public static DirectoryInfo CreateDirectory(string path) return new DirectoryInfo(path, fullPath, isNormalized: true); } + /// + /// Creates all directories and subdirectories in the specified path with the specified permissions unless they already exist. + /// + /// The directory to create. + /// Unix file mode used to create directories. + /// An object that represents the directory at the specified path. This object is returned regardless of whether a directory at the specified path already exists. + /// The directory specified by is a file. + /// -or- + /// The network name is not known. + /// The caller does not have the required permission. + /// is a zero-length string, contains only white space, or contains one or more invalid characters. You can query for invalid characters by using the method. + /// -or- + /// is prefixed with, or contains, only a colon character (:). + /// is . + /// The specified path, file name, or both exceed the system-defined maximum length. + /// The specified path is invalid (for example, it is on an unmapped drive). + /// contains a colon character (:) that is not part of a drive label ("C:\\"). + /// The caller attempts use an invalid file mode. [UnsupportedOSPlatform("windows")] public static DirectoryInfo CreateDirectory(string path, UnixFileMode unixCreateMode) { ArgumentException.ThrowIfNullOrEmpty(path); + if ((unixCreateMode & ~FileSystem.ValidUnixFileModes) != 0) + { + ThrowHelper.ArgumentOutOfRangeException_Enum_Value(); + } + string fullPath = Path.GetFullPath(path); FileSystem.CreateDirectory(fullPath, unixCreateMode); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index 8758b174dde50..7d5643660aaeb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -220,21 +220,61 @@ public static FileAttributes GetAttributes(string path) public static void SetAttributes(string path, FileAttributes fileAttributes) => FileSystem.SetAttributes(Path.GetFullPath(path), fileAttributes); + /// Gets the of the file on the path. + /// The path to the file. + /// The of the file on the path. [UnsupportedOSPlatform("windows")] public static UnixFileMode GetUnixFileMode(string path) => FileSystem.GetUnixFileMode(Path.GetFullPath(path)); + /// Gets the of the specified file handle. + /// The file handle. + /// The of the file handle. + /// The caller does not have the required permission. [UnsupportedOSPlatform("windows")] public static UnixFileMode GetUnixFileMode(SafeFileHandle fileHandle) => FileSystem.GetUnixFileMode(fileHandle); + /// Sets the specified of the file on the specified path. + /// The path to the file. + /// The unix file mode. + /// is empty, contains only white spaces, contains invalid characters, or the file mode is invalid. + /// The specified path, file name, or both exceed the system-defined maximum length. + /// is in an invalid format. + /// The specified path is invalid, (for example, it is on an unmapped drive). + /// The file cannot be found. + /// specified a file that is read-only. + /// -or- + /// This operation is not supported on the current platform. + /// -or- + /// specified a directory. + /// -or- + /// The caller does not have the required permission. [UnsupportedOSPlatform("windows")] public static void SetUnixFileMode(string path, UnixFileMode mode) - => FileSystem.SetUnixFileMode(Path.GetFullPath(path), mode); + { + if ((mode & ~FileSystem.ValidUnixFileModes) != 0) + { + ThrowHelper.ArgumentOutOfRangeException_Enum_Value(); + } + FileSystem.SetUnixFileMode(Path.GetFullPath(path), mode); + } + + /// Sets the specified of the specified file handle. + /// The file handle. + /// The unix file mode. + /// The file mode is invalid. [UnsupportedOSPlatform("windows")] public static void SetUnixFileMode(SafeFileHandle fileHandle, UnixFileMode mode) - => FileSystem.SetUnixFileMode(fileHandle, mode); + { + if ((mode & ~FileSystem.ValidUnixFileModes) != 0) + { + ThrowHelper.ArgumentOutOfRangeException_Enum_Value(); + } + + FileSystem.SetUnixFileMode(fileHandle, mode); + } public static FileStream OpenRead(string path) => new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs index c15f358dfefb2..f7d2d8bce812e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs @@ -116,6 +116,7 @@ public int BufferSize /// /// Unix file mode used when a new file is created. /// + /// When contains an invalid value. public UnixFileMode? UnixCreateMode { get diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs index 3fe2b3b894feb..a21c3f847f5ff 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs @@ -131,6 +131,15 @@ public string? LinkTarget } } + /// Gets or sets the Unix file mode for the current file or directory. + /// of the current . + /// The specified file doesn't exist. Only thrown when setting the property value. + /// The specified path is invalid. For example, it's on an unmapped drive. Only thrown when setting the property value. + /// The caller doesn't have the required permission. + /// The user attempts to set an attribute value but doesn't have write permission. + /// The specified path, file name, or both exceed the system-defined maximum length. + /// The caller attempts to set an invalid mode. + /// cannot initialize the data. public UnixFileMode UnixFileMode { [UnsupportedOSPlatform("windows")] diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/UnixFileMode.cs b/src/libraries/System.Private.CoreLib/src/System/IO/UnixFileMode.cs index af182066fbd9d..2454ef8b96516 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/UnixFileMode.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/UnixFileMode.cs @@ -3,20 +3,63 @@ namespace System.IO; +/// +/// Represents the Unix filesystem permissions. +/// This enumeration supports a bitwise combination of its member values. +/// [Flags] public enum UnixFileMode { + /// + /// No permissions. + /// None = 0, + /// + /// Execute permission for others. + /// OtherExecute = 1, + /// + /// Write permission for others. + /// OtherWrite = 2, + /// + /// Read permission for others. + /// OtherRead = 4, + /// + /// Execute permission for group. + /// GroupExecute = 8, + /// + /// Write permission for group. + /// GroupWrite = 16, + /// + /// Read permission for group. + /// GroupRead = 32, + /// + /// Execute permission for user. + /// UserExecute = 64, + /// + /// Write permission for user. + /// UserWrite = 128, + /// + /// Read permission for user. + /// UserRead = 256, + /// + /// Sticky bit permission. + /// StickyBit = 512, + /// + /// Set Group permission. + /// SetGroup = 1024, - SetUser = 2048 + /// + /// Set User permission. + /// + SetUser = 2048, } From 7bd2ae398a94c907965aad76e743c842b4110703 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Fri, 3 Jun 2022 09:12:45 +0200 Subject: [PATCH 07/42] Replace Interop.Sys.Permissions to System.IO.UnixFileMode. --- .../Unix/System.Native/Interop.Permissions.cs | 33 ------------------- .../src/System.Diagnostics.Process.csproj | 2 -- .../src/System/Diagnostics/Process.Unix.cs | 12 ++++--- .../src/System.Formats.Tar.csproj | 1 - .../src/System.IO.MemoryMappedFiles.csproj | 2 -- .../MemoryMappedFile.Unix.cs | 8 ++--- .../src/System.IO.Pipes.csproj | 2 -- .../System.Private.CoreLib.Shared.projitems | 3 -- .../src/System/IO/FileStatus.Unix.cs | 31 ++++++++--------- ...Cryptography.X509Certificates.Tests.csproj | 2 -- .../src/System.Security.Cryptography.csproj | 2 -- .../OpenSslDirectoryBasedStoreProvider.cs | 13 ++++---- 12 files changed, 34 insertions(+), 77 deletions(-) delete mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.Permissions.cs diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Permissions.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Permissions.cs deleted file mode 100644 index 8248077deaf50..0000000000000 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Permissions.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; - -internal static partial class Interop -{ - internal static partial class Sys - { - [Flags] - internal enum Permissions - { - Mask = S_IRWXU | S_IRWXG | S_IRWXO, - - S_IRWXU = S_IRUSR | S_IWUSR | S_IXUSR, - S_IRUSR = 0x100, - S_IWUSR = 0x80, - S_IXUSR = 0x40, - - S_IRWXG = S_IRGRP | S_IWGRP | S_IXGRP, - S_IRGRP = 0x20, - S_IWGRP = 0x10, - S_IXGRP = 0x8, - - S_IRWXO = S_IROTH | S_IWOTH | S_IXOTH, - S_IROTH = 0x4, - S_IWOTH = 0x2, - S_IXOTH = 0x1, - - S_IXUGO = S_IXUSR | S_IXGRP | S_IXOTH, - } - } -} diff --git a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index 3de13e8cdc662..527292b461da5 100644 --- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -276,8 +276,6 @@ Link="Common\Interop\Unix\System.Native\Interop.Access.cs" /> - - diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj b/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj index cd8e39c645a8f..9be9a376620fd 100644 --- a/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj +++ b/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj @@ -99,8 +99,6 @@ Link="Common\Interop\Unix\Interop.Open.cs" /> - - Common\Interop\Unix\System.Native\Interop.PathConf.cs - - Common\Interop\Unix\System.Native\Interop.Permissions.cs - Common\Interop\Unix\System.Native\Interop.PosixFAdvise.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs index 97b2c4d94eb18..6c96eec4db56a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs @@ -54,9 +54,9 @@ private bool HasReadOnlyFlag } #if TARGET_BROWSER - var mode = (Interop.Sys.Permissions)(_fileCache.Mode & (int)Interop.Sys.Permissions.Mask); - bool isUserReadOnly = (mode & Interop.Sys.Permissions.S_IRUSR) != 0 && // has read permission - (mode & Interop.Sys.Permissions.S_IWUSR) == 0; // but not write permission + var mode = (UnixFileMode)(_fileCache.Mode & (int)FileSystem.ValidUnixFileModes); + bool isUserReadOnly = (mode & UnixFileMode.UserRead) != 0 && // has read permission + (mode & UnixFileMode.UserWrite) == 0; // but not write permission return isUserReadOnly; #else if (_isReadOnlyCache == 0) @@ -84,14 +84,14 @@ private bool HasReadOnlyFlag private bool IsModeReadOnlyCore() { - var mode = (Interop.Sys.Permissions)(_fileCache.Mode & (int)Interop.Sys.Permissions.Mask); + var mode = (UnixFileMode)(_fileCache.Mode & (int)FileSystem.ValidUnixFileModes); - bool isUserReadOnly = (mode & Interop.Sys.Permissions.S_IRUSR) != 0 && // has read permission - (mode & Interop.Sys.Permissions.S_IWUSR) == 0; // but not write permission - bool isGroupReadOnly = (mode & Interop.Sys.Permissions.S_IRGRP) != 0 && // has read permission - (mode & Interop.Sys.Permissions.S_IWGRP) == 0; // but not write permission - bool isOtherReadOnly = (mode & Interop.Sys.Permissions.S_IROTH) != 0 && // has read permission - (mode & Interop.Sys.Permissions.S_IWOTH) == 0; // but not write permission + bool isUserReadOnly = (mode & UnixFileMode.UserRead) != 0 && // has read permission + (mode & UnixFileMode.UserWrite) == 0; // but not write permission + bool isGroupReadOnly = (mode & UnixFileMode.GroupRead) != 0 && // has read permission + (mode & UnixFileMode.GroupWrite) == 0; // but not write permission + bool isOtherReadOnly = (mode & UnixFileMode.OtherRead) != 0 && // has read permission + (mode & UnixFileMode.OtherWrite) == 0; // but not write permission // If they are all the same, no need to check user/group. if ((isUserReadOnly == isGroupReadOnly) && (isGroupReadOnly == isOtherReadOnly)) @@ -242,20 +242,21 @@ internal void SetAttributes(string path, FileAttributes attributes, bool asDirec // The only thing we can reasonably change is whether the file object is readonly by changing permissions. - int newMode = _fileCache.Mode; + int oldMode = _fileCache.Mode & (int)FileSystem.ValidUnixFileModes; + int newMode = oldMode; if ((attributes & FileAttributes.ReadOnly) != 0) { // Take away all write permissions from user/group/everyone - newMode &= ~(int)(Interop.Sys.Permissions.S_IWUSR | Interop.Sys.Permissions.S_IWGRP | Interop.Sys.Permissions.S_IWOTH); + newMode &= ~(int)(UnixFileMode.UserWrite | UnixFileMode.GroupWrite | UnixFileMode.OtherWrite); } - else if ((newMode & (int)Interop.Sys.Permissions.S_IRUSR) != 0) + else if ((newMode & (int)UnixFileMode.UserRead) != 0) { // Give write permission to the owner if the owner has read permission - newMode |= (int)Interop.Sys.Permissions.S_IWUSR; + newMode |= (int)UnixFileMode.UserWrite; } // Change the permissions on the file - if (newMode != _fileCache.Mode) + if (newMode != oldMode) { Interop.CheckIo(Interop.Sys.ChMod(path, newMode), path, asDirectory); } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj b/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj index bda7909c02c85..f19482755f041 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj @@ -103,8 +103,6 @@ Link="Common\Interop\Unix\Interop.GetPwUid.cs" /> - - Date: Fri, 3 Jun 2022 10:16:23 +0200 Subject: [PATCH 08/42] Throw PNSE immediately on Windows. --- .../System.Private.CoreLib.Shared.projitems | 4 ++++ .../src/System/IO/Directory.Unix.cs | 24 +++++++++++++++++++ .../src/System/IO/Directory.Windows.cs | 11 +++++++++ .../src/System/IO/Directory.cs | 15 +----------- .../src/System/IO/File.Unix.cs | 22 +++++++++++++++++ .../src/System/IO/File.Windows.cs | 22 +++++++++++++++++ .../src/System/IO/File.cs | 24 ++++--------------- .../src/System/IO/FileSystem.Windows.cs | 17 ------------- 8 files changed, 89 insertions(+), 50 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/IO/Directory.Unix.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/IO/Directory.Windows.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/IO/File.Unix.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/IO/File.Windows.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 093ad09911907..b3f35a853d7f6 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1889,8 +1889,10 @@ + + @@ -2191,6 +2193,8 @@ + + diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Unix.cs new file mode 100644 index 0000000000000..034c0e9582f5d --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Unix.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.IO +{ + public static partial class Directory + { + private static DirectoryInfo CreateDirectoryCore(string path, UnixFileMode unixCreateMode) + { + ArgumentException.ThrowIfNullOrEmpty(path); + + if ((unixCreateMode & ~FileSystem.ValidUnixFileModes) != 0) + { + ThrowHelper.ArgumentOutOfRangeException_Enum_Value(); + } + + string fullPath = Path.GetFullPath(path); + + FileSystem.CreateDirectory(fullPath, unixCreateMode); + + return new DirectoryInfo(path, fullPath, isNormalized: true); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Windows.cs new file mode 100644 index 0000000000000..0f96417902ffc --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Windows.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.IO +{ + public static partial class Directory + { + private static DirectoryInfo CreateDirectoryCore(string path, UnixFileMode unixCreateMode) + => throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs index 04d341feb1f04..bcf06e7c72144 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs @@ -55,20 +55,7 @@ public static DirectoryInfo CreateDirectory(string path) /// The caller attempts use an invalid file mode. [UnsupportedOSPlatform("windows")] public static DirectoryInfo CreateDirectory(string path, UnixFileMode unixCreateMode) - { - ArgumentException.ThrowIfNullOrEmpty(path); - - if ((unixCreateMode & ~FileSystem.ValidUnixFileModes) != 0) - { - ThrowHelper.ArgumentOutOfRangeException_Enum_Value(); - } - - string fullPath = Path.GetFullPath(path); - - FileSystem.CreateDirectory(fullPath, unixCreateMode); - - return new DirectoryInfo(path, fullPath, isNormalized: true); - } + => CreateDirectoryCore(path, unixCreateMode); // Tests if the given path refers to an existing DirectoryInfo on disk. public static bool Exists([NotNullWhen(true)] string? path) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.Unix.cs new file mode 100644 index 0000000000000..3a2f1cf1a4f1f --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.Unix.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Win32.SafeHandles; + +namespace System.IO +{ + public static partial class File + { + private static UnixFileMode GetUnixFileModeCore(string path) + => FileSystem.GetUnixFileMode(Path.GetFullPath(path)); + + private static UnixFileMode GetUnixFileModeCore(SafeFileHandle fileHandle) + => FileSystem.GetUnixFileMode(fileHandle); + + private static void SetUnixFileModeCore(string path, UnixFileMode mode) + => FileSystem.SetUnixFileMode(Path.GetFullPath(path), mode); + + private static void SetUnixFileModeCore(SafeFileHandle fileHandle, UnixFileMode mode) + => FileSystem.SetUnixFileMode(fileHandle, mode); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.Windows.cs new file mode 100644 index 0000000000000..70ee2e2c142c5 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.Windows.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Win32.SafeHandles; + +namespace System.IO +{ + public static partial class File + { + private static UnixFileMode GetUnixFileModeCore(string path) + => throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); + + private static UnixFileMode GetUnixFileModeCore(SafeFileHandle fileHandle) + => throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); + + private static void SetUnixFileModeCore(string path, UnixFileMode mode) + => throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); + + private static void SetUnixFileModeCore(SafeFileHandle fileHandle, UnixFileMode mode) + => throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index 7d5643660aaeb..a7ac388490f9f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -17,7 +17,7 @@ namespace System.IO { // Class for creating FileStream objects, and some basic file management // routines such as Delete, etc. - public static class File + public static partial class File { private const int ChunkSize = 8192; private static Encoding? s_UTF8NoBOM; @@ -225,7 +225,7 @@ public static void SetAttributes(string path, FileAttributes fileAttributes) /// The of the file on the path. [UnsupportedOSPlatform("windows")] public static UnixFileMode GetUnixFileMode(string path) - => FileSystem.GetUnixFileMode(Path.GetFullPath(path)); + => GetUnixFileModeCore(path); /// Gets the of the specified file handle. /// The file handle. @@ -233,7 +233,7 @@ public static UnixFileMode GetUnixFileMode(string path) /// The caller does not have the required permission. [UnsupportedOSPlatform("windows")] public static UnixFileMode GetUnixFileMode(SafeFileHandle fileHandle) - => FileSystem.GetUnixFileMode(fileHandle); + => GetUnixFileModeCore(fileHandle); /// Sets the specified of the file on the specified path. /// The path to the file. @@ -252,14 +252,7 @@ public static UnixFileMode GetUnixFileMode(SafeFileHandle fileHandle) /// The caller does not have the required permission. [UnsupportedOSPlatform("windows")] public static void SetUnixFileMode(string path, UnixFileMode mode) - { - if ((mode & ~FileSystem.ValidUnixFileModes) != 0) - { - ThrowHelper.ArgumentOutOfRangeException_Enum_Value(); - } - - FileSystem.SetUnixFileMode(Path.GetFullPath(path), mode); - } + => SetUnixFileModeCore(path, mode); /// Sets the specified of the specified file handle. /// The file handle. @@ -267,14 +260,7 @@ public static void SetUnixFileMode(string path, UnixFileMode mode) /// The file mode is invalid. [UnsupportedOSPlatform("windows")] public static void SetUnixFileMode(SafeFileHandle fileHandle, UnixFileMode mode) - { - if ((mode & ~FileSystem.ValidUnixFileModes) != 0) - { - ThrowHelper.ArgumentOutOfRangeException_Enum_Value(); - } - - FileSystem.SetUnixFileMode(fileHandle, mode); - } + => SetUnixFileModeCore(fileHandle, mode); public static FileStream OpenRead(string path) => new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs index 5938f5576debc..c9ecc55ca59c5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs @@ -10,11 +10,6 @@ namespace System.IO { internal static partial class FileSystem { - public static void CreateDirectory(string fullPath, UnixFileMode unixCreateMode) - { - throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); - } - public static void Encrypt(string path) { string fullPath = Path.GetFullPath(path); @@ -464,18 +459,6 @@ public static void SetLastAccessTime(string fullPath, DateTimeOffset time, bool public static void SetLastWriteTime(string fullPath, DateTimeOffset time, bool asDirectory) => SetFileTime(fullPath, asDirectory, lastWriteTime: time.ToFileTime()); - public static UnixFileMode GetUnixFileMode(string fullPath) - => throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); - - public static UnixFileMode GetUnixFileMode(SafeFileHandle fileHandle) - => throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); - - public static void SetUnixFileMode(string fullPath, UnixFileMode mode) - => throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); - - public static void SetUnixFileMode(SafeFileHandle fileHandle, UnixFileMode mode) - => throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); - public static string[] GetLogicalDrives() => DriveInfoInternal.GetLogicalDrives(); From 3841bfb0a72b67830b74c0a865ee2b1732690dc8 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Fri, 3 Jun 2022 10:18:15 +0200 Subject: [PATCH 09/42] Add ODE to xml docs of methods that accept a handle. --- src/libraries/System.Private.CoreLib/src/System/IO/File.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index a7ac388490f9f..4c0d1d651cd1c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -231,6 +231,7 @@ public static UnixFileMode GetUnixFileMode(string path) /// The file handle. /// The of the file handle. /// The caller does not have the required permission. + /// The file is closed. [UnsupportedOSPlatform("windows")] public static UnixFileMode GetUnixFileMode(SafeFileHandle fileHandle) => GetUnixFileModeCore(fileHandle); @@ -258,6 +259,7 @@ public static void SetUnixFileMode(string path, UnixFileMode mode) /// The file handle. /// The unix file mode. /// The file mode is invalid. + /// The file is closed. [UnsupportedOSPlatform("windows")] public static void SetUnixFileMode(SafeFileHandle fileHandle, UnixFileMode mode) => SetUnixFileModeCore(fileHandle, mode); From d1e7ea8929946431f467df603e763b63a843a19f Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Fri, 3 Jun 2022 10:21:40 +0200 Subject: [PATCH 10/42] Don't throw (PNSE) from FileSystemInfo.UnixFileMode getter on Windows. --- .../src/System/IO/FileSystemInfo.Windows.cs | 5 +---- .../System.Private.CoreLib/src/System/IO/FileSystemInfo.cs | 1 - src/libraries/System.Runtime/ref/System.Runtime.cs | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs index 61ed222498f28..0d1589877c77b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs @@ -133,10 +133,7 @@ internal long LengthCore #pragma warning disable CA1822 internal UnixFileMode UnixFileModeCore { - get - { - throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); - } + get => (UnixFileMode)(-1); set { throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs index a21c3f847f5ff..8f0cf0cc28d13 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs @@ -142,7 +142,6 @@ public string? LinkTarget /// cannot initialize the data. public UnixFileMode UnixFileMode { - [UnsupportedOSPlatform("windows")] get => UnixFileModeCore; [UnsupportedOSPlatform("windows")] set => UnixFileModeCore = value; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 42966c9329dcd..60f89031217af 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -9629,7 +9629,7 @@ protected FileSystemInfo(System.Runtime.Serialization.SerializationInfo info, Sy public System.DateTime LastWriteTimeUtc { get { throw null; } set { } } public string? LinkTarget { get { throw null; } } public abstract string Name { get; } - public System.IO.UnixFileMode UnixFileMode { [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] get { throw null; } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] set { } } + public System.IO.UnixFileMode UnixFileMode { get { throw null; } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] set { } } public void CreateAsSymbolicLink(string pathToTarget) { } public abstract void Delete(); public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } From f7db62632823a22314750bac8d423fe173332206 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Fri, 3 Jun 2022 10:29:41 +0200 Subject: [PATCH 11/42] Minor style fix. --- .../src/System/IO/FileSystemInfo.Windows.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs index 0d1589877c77b..9145e94dc0097 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Windows.cs @@ -134,10 +134,7 @@ internal long LengthCore internal UnixFileMode UnixFileModeCore { get => (UnixFileMode)(-1); - set - { - throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); - } + set => throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode); } #pragma warning restore CA1822 From 88828a42d17cf3f2577fb8f8a0a03fc6c068fab6 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Sun, 5 Jun 2022 10:46:14 +0200 Subject: [PATCH 12/42] Get rid of some casts. Co-authored-by: Eric Erhardt --- .../src/System/Diagnostics/Process.Unix.cs | 2 +- .../src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs index 455de7eb59e50..87c4113f1f4d6 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs @@ -765,7 +765,7 @@ private static bool IsExecutable(string fullPath) const UnixFileMode allExecute = UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; - UnixFileMode permissions = (UnixFileMode)(fileinfo.Mode & (int)allExecute); + UnixFileMode permissions = ((UnixFileMode)fileinfo.Mode) & allExecute; // Avoid checking user/group when permission. if (permissions == allExecute) diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs index 2ff0d4a5cbecd..01c78f243b088 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs @@ -345,7 +345,7 @@ private bool Init(string path, FileMode mode, FileAccess access, FileShare share } fileLength = status.Size; - filePermissions = (UnixFileMode)(status.Mode & (int)PermissionMask); + filePermissions = ((UnixFileMode)status.Mode) & PermissionMask; } IsAsync = (options & FileOptions.Asynchronous) != 0; From cd4104f976dcc58b39897a94a08909fab5a0fe98 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 7 Jun 2022 09:57:05 +0200 Subject: [PATCH 13/42] Add tests for creating a file/directory with UnixFileMode. --- .../CreateDirectory_UnixFileMode.Unix.cs | 40 ++++++++++++++++ .../CreateDirectory_UnixFileMode.Windows.cs | 17 +++++++ .../System.IO.FileSystem/tests/File/Open.cs | 12 +++++ .../tests/FileInfo/Open.cs | 12 +++++ .../tests/FileStream/ctor_options.cs | 47 ++++++++++++++++++ .../tests/FileSystemTest.Unix.cs | 48 +++++++++++++++++++ .../tests/System.IO.FileSystem.Tests.csproj | 2 + 7 files changed, 178 insertions(+) create mode 100644 src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs create mode 100644 src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Windows.cs diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs new file mode 100644 index 0000000000000..509a49dcb5e6c --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using Xunit; + +namespace System.IO.Tests +{ + public class CreateDirectoryWithUnixFileMode : Directory_CreateDirectory + { + // Runs base class tests using CreateDirectory method that takes a UnixFileMode. + public override DirectoryInfo Create(string path) + { + return Directory.CreateDirectory(path, AllAccess); + } + + [Theory] + [MemberData(nameof(TestUnixFileModes))] + public void CreateWithUnixFileMode(UnixFileMode mode) + { + string path = GetRandomDirPath(); + DirectoryInfo di = Directory.CreateDirectory(path, mode); + + // under Linux the created directory gets mode (mode & ~umask & 01777) + UnixFileMode expectedMode = mode & ~GetUmask() & (UnixFileMode)0b1_111_111_111; + Assert.Equal(expectedMode, di.UnixFileMode); + } + + [Fact] + public void CreateDoesntChangeExistingMode() + { + string path = GetRandomDirPath(); + DirectoryInfo di = Directory.CreateDirectory(path, AllAccess); + UnixFileMode initialMode = di.UnixFileMode; + + DirectoryInfo di2 = Directory.CreateDirectory(path, UnixFileMode.UserRead); + Assert.Equal(initialMode, di2.UnixFileMode); + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Windows.cs b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Windows.cs new file mode 100644 index 0000000000000..2645a83a4ef63 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Windows.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.IO.Tests +{ + public class CreateDirectoryWithUnixFileMode + { + [Fact] + public void ThrowsPNSE() + { + string path = GetRandomDirPath(); + Assert.Throws(() => Directory.CreateDirectory(path, UnixFileMode.UserRead)); + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/File/Open.cs b/src/libraries/System.IO.FileSystem/tests/File/Open.cs index bd9b38b121b88..91030270ce5f7 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/Open.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/Open.cs @@ -86,6 +86,18 @@ protected override FileStream CreateFileStream(string path, FileMode mode, FileA PreallocationSize = preallocationSize }); } + + protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize, UnixFileMode unixFileMode) + => File.Open(path, + new FileStreamOptions { + Mode = mode, + Access = access, + Share = share, + BufferSize = bufferSize, + Options = options, + PreallocationSize = preallocationSize, + UnixCreateMode = unixFileMode + }); } public class File_OpenSpecial : FileStream_ctor_str_fm_fa_fs diff --git a/src/libraries/System.IO.FileSystem/tests/FileInfo/Open.cs b/src/libraries/System.IO.FileSystem/tests/FileInfo/Open.cs index 36fd39a600056..eef2fcf372eae 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileInfo/Open.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileInfo/Open.cs @@ -118,6 +118,18 @@ protected override FileStream CreateFileStream(string path, FileMode mode, FileA PreallocationSize = preallocationSize }); } + + protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize, UnixFileMode unixFileMode) + => new FileInfo(path).Open( + new FileStreamOptions { + Mode = mode, + Access = access, + Share = share, + BufferSize = bufferSize, + Options = options, + PreallocationSize = preallocationSize, + UnixCreateMode = unixFileMode + }); } public class FileInfo_OpenSpecial : FileStream_ctor_str_fm_fa_fs diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs index bbae2fbb0e240..a00bb423979dc 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs @@ -54,6 +54,19 @@ protected virtual FileStream CreateFileStream(string path, FileMode mode, FileAc PreallocationSize = preallocationSize }); + protected virtual FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize, UnixFileMode unixFileMode) + => new FileStream(path, + new FileStreamOptions + { + Mode = mode, + Access = access, + Share = share, + BufferSize = bufferSize, + Options = options, + PreallocationSize = preallocationSize, + UnixCreateMode = unixFileMode + }); + [Fact] public virtual void NegativePreallocationSizeThrows() { @@ -165,5 +178,39 @@ public void WhenDiskIsFullTheErrorMessageContainsAllDetails(FileMode mode) } Assert.False(exists); } + + [PlatformSpecific(TestPlatforms.AnyUnix)] + [Theory] + [MemberData(nameof(TestUnixFileModes))] + public void CreateWithUnixFileMode(UnixFileMode mode) + { + // tmpfs gets mounted with nosuid for security. + // The filesystem will then filter out the setuid and setgid bits. + mode &= ~(UnixFileMode.SetUser | UnixFileMode.SetGroup); + + string filename = GetTestFilePath(); + FileStream fs = CreateFileStream(filename, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, mode); + fs.Dispose(); + + UnixFileMode expectedMode = mode & ~GetUmask(); + UnixFileMode actualMode = File.GetUnixFileMode(filename); + Assert.Equal(expectedMode, actualMode); + } + + [PlatformSpecific(TestPlatforms.AnyUnix)] + [Fact] + public void CreateDoesntChangeExistingMode() + { + // Create file as writable for user only. + const UnixFileMode mode = UnixFileMode.UserWrite; + string filename = GetTestFilePath(); + using FileStream fs = CreateFileStream(filename, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, mode); + fs.Dispose(); + + // Now open with AllAccess. + using FileStream fs2 = CreateFileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, AllAccess); + UnixFileMode actualMode = File.GetUnixFileMode(filename); + Assert.Equal(mode, actualMode); + } } } diff --git a/src/libraries/System.IO.FileSystem/tests/FileSystemTest.Unix.cs b/src/libraries/System.IO.FileSystem/tests/FileSystemTest.Unix.cs index 213f21525d002..ec7f8c9850b1a 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileSystemTest.Unix.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileSystemTest.Unix.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.InteropServices; using Xunit; @@ -13,5 +15,51 @@ public abstract partial class FileSystemTest [LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] protected static partial int mkfifo(string path, int mode); + + internal const UnixFileMode AllAccess = + UnixFileMode.UserRead | + UnixFileMode.UserWrite | + UnixFileMode.UserExecute | + UnixFileMode.GroupRead | + UnixFileMode.GroupWrite | + UnixFileMode.GroupExecute | + UnixFileMode.OtherRead | + UnixFileMode.OtherWrite | + UnixFileMode.OtherExecute; + + public static IEnumerable TestUnixFileModes + { + get + { + // Make combinations of the enum with 0, 1 and 2 bits set. + UnixFileMode[] modes = Enum.GetValues(); + for (int i = 0; i < modes.Length; i++) + { + for (int j = i; j < modes.Length; j++) + { + yield return new object[] { modes[i] | modes[j] }; + } + } + } + } + + private static UnixFileMode s_umask = (UnixFileMode)(-1); + + protected static UnixFileMode GetUmask() + { + if (s_umask == (UnixFileMode)(-1)) + { + // The umask can't be retrieved without changing it. + // We launch a child process to get its value. + using Process px = Process.Start(new ProcessStartInfo + { + FileName = "umask", + RedirectStandardOutput = true + }); + string stdout = px.StandardOutput.ReadToEnd().Trim(); + s_umask = (UnixFileMode)Convert.ToInt32(stdout, 8); + } + return s_umask; + } } } diff --git a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj index fbbe5ff070949..bcf32b8f06805 100644 --- a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj +++ b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj @@ -75,9 +75,11 @@ + + From c937c28c9d3ba281a0a02b3cda877e58fa349927 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 7 Jun 2022 15:43:56 +0200 Subject: [PATCH 14/42] Some CI envs don't have a umask exe, try retrieving via a shell builtin. --- .../System.IO.FileSystem/tests/FileSystemTest.Unix.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.IO.FileSystem/tests/FileSystemTest.Unix.cs b/src/libraries/System.IO.FileSystem/tests/FileSystemTest.Unix.cs index ec7f8c9850b1a..dd783e8e888b2 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileSystemTest.Unix.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileSystemTest.Unix.cs @@ -53,7 +53,8 @@ protected static UnixFileMode GetUmask() // We launch a child process to get its value. using Process px = Process.Start(new ProcessStartInfo { - FileName = "umask", + FileName = "/bin/sh", + ArgumentList = { "-c", "umask" }, RedirectStandardOutput = true }); string stdout = px.StandardOutput.ReadToEnd().Trim(); From a12832c653b224e6e12ead7e95a6b69fa9a87562 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 7 Jun 2022 15:55:03 +0200 Subject: [PATCH 15/42] Update expected test mode values. --- .../tests/Directory/CreateDirectory_UnixFileMode.Unix.cs | 4 +++- .../System.IO.FileSystem/tests/FileStream/ctor_options.cs | 7 ++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs index 509a49dcb5e6c..44cfdfd67d916 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs @@ -22,7 +22,9 @@ public void CreateWithUnixFileMode(UnixFileMode mode) DirectoryInfo di = Directory.CreateDirectory(path, mode); // under Linux the created directory gets mode (mode & ~umask & 01777) - UnixFileMode expectedMode = mode & ~GetUmask() & (UnixFileMode)0b1_111_111_111; + // under OSX, it seems to be (mode & ~umask & 01777). + UnixFileMode expectedMode = mode & ~GetUmask() & + (UnixFileMode)(PlatformDetection.IsBsdLike ? 0b111_111_111 : 0b1_111_111_111); Assert.Equal(expectedMode, di.UnixFileMode); } diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs index a00bb423979dc..0c4af724f2eb3 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs @@ -184,15 +184,12 @@ public void WhenDiskIsFullTheErrorMessageContainsAllDetails(FileMode mode) [MemberData(nameof(TestUnixFileModes))] public void CreateWithUnixFileMode(UnixFileMode mode) { - // tmpfs gets mounted with nosuid for security. - // The filesystem will then filter out the setuid and setgid bits. - mode &= ~(UnixFileMode.SetUser | UnixFileMode.SetGroup); - string filename = GetTestFilePath(); FileStream fs = CreateFileStream(filename, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, mode); fs.Dispose(); - UnixFileMode expectedMode = mode & ~GetUmask(); + UnixFileMode expectedMode = mode & ~GetUmask() & + ~(UnixFileMode)(PlatformDetection.IsBsdLike ? UnixFileMode.StickyBit : UnixFileMode.None);; UnixFileMode actualMode = File.GetUnixFileMode(filename); Assert.Equal(expectedMode, actualMode); } From d294651438002036bd4586198e887ba8be71fd84 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Wed, 8 Jun 2022 05:11:57 +0200 Subject: [PATCH 16/42] Fix OSX --- .../tests/Directory/CreateDirectory_UnixFileMode.Unix.cs | 8 ++++---- .../System.IO.FileSystem/tests/FileStream/ctor_options.cs | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs index 44cfdfd67d916..2f86fa96a205f 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs @@ -21,10 +21,10 @@ public void CreateWithUnixFileMode(UnixFileMode mode) string path = GetRandomDirPath(); DirectoryInfo di = Directory.CreateDirectory(path, mode); - // under Linux the created directory gets mode (mode & ~umask & 01777) - // under OSX, it seems to be (mode & ~umask & 01777). - UnixFileMode expectedMode = mode & ~GetUmask() & - (UnixFileMode)(PlatformDetection.IsBsdLike ? 0b111_111_111 : 0b1_111_111_111); + // under Linux the created directory gets mode (mode & ~umask & 01777). + // under OSX, it gets (mode & ~umask & 0777). + UnixFileMode platformFilter = UnixFileMode.SetGroup | UnixFileMode.SetUser | (PlatformDetection.IsBsdLike ? UnixFileMode.StickyBit : UnixFileMode.None); + UnixFileMode expectedMode = mode & ~GetUmask() & ~platformFilter; Assert.Equal(expectedMode, di.UnixFileMode); } diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs index 0c4af724f2eb3..b5d6c3b9f8db2 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs @@ -188,8 +188,10 @@ public void CreateWithUnixFileMode(UnixFileMode mode) FileStream fs = CreateFileStream(filename, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, mode); fs.Dispose(); - UnixFileMode expectedMode = mode & ~GetUmask() & - ~(UnixFileMode)(PlatformDetection.IsBsdLike ? UnixFileMode.StickyBit : UnixFileMode.None);; + UnixFileMode platformFilter = PlatformDetection.IsBsdLike + ? (UnixFileMode.SetGroup | UnixFileMode.SetUser | UnixFileMode.StickyBit) + : UnixFileMode.None; + UnixFileMode expectedMode = mode & ~GetUmask() & ~platformFilter; UnixFileMode actualMode = File.GetUnixFileMode(filename); Assert.Equal(expectedMode, actualMode); } From 3df946a6d2d950a1de7b06bea78fed873b7b7d42 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Wed, 8 Jun 2022 05:14:07 +0200 Subject: [PATCH 17/42] Fix Windows build. --- .../CreateDirectory_UnixFileMode.Windows.cs | 2 +- .../tests/FileStream/ctor_options.Unix.cs | 33 +++++++++++++++++++ .../tests/FileStream/ctor_options.cs | 33 ------------------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Windows.cs b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Windows.cs index 2645a83a4ef63..8c23d7a29a606 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Windows.cs @@ -8,7 +8,7 @@ namespace System.IO.Tests public class CreateDirectoryWithUnixFileMode { [Fact] - public void ThrowsPNSE() + public void NotSupported() { string path = GetRandomDirPath(); Assert.Throws(() => Directory.CreateDirectory(path, UnixFileMode.UserRead)); diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs index 60a2af22d4911..e6e95d5d88d6b 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs @@ -8,6 +8,39 @@ namespace System.IO.Tests { public partial class FileStream_ctor_options { + [PlatformSpecific(TestPlatforms.AnyUnix)] + [Theory] + [MemberData(nameof(TestUnixFileModes))] + public void CreateWithUnixFileMode(UnixFileMode mode) + { + string filename = GetTestFilePath(); + FileStream fs = CreateFileStream(filename, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, mode); + fs.Dispose(); + + UnixFileMode platformFilter = PlatformDetection.IsBsdLike + ? (UnixFileMode.SetGroup | UnixFileMode.SetUser | UnixFileMode.StickyBit) + : UnixFileMode.None; + UnixFileMode expectedMode = mode & ~GetUmask() & ~platformFilter; + UnixFileMode actualMode = File.GetUnixFileMode(filename); + Assert.Equal(expectedMode, actualMode); + } + + [PlatformSpecific(TestPlatforms.AnyUnix)] + [Fact] + public void CreateDoesntChangeExistingMode() + { + // Create file as writable for user only. + const UnixFileMode mode = UnixFileMode.UserWrite; + string filename = GetTestFilePath(); + using FileStream fs = CreateFileStream(filename, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, mode); + fs.Dispose(); + + // Now open with AllAccess. + using FileStream fs2 = CreateFileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, AllAccess); + UnixFileMode actualMode = File.GetUnixFileMode(filename); + Assert.Equal(mode, actualMode); + } + private static long GetAllocatedSize(FileStream fileStream) { bool isOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs index b5d6c3b9f8db2..99b6d246da646 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs @@ -178,38 +178,5 @@ public void WhenDiskIsFullTheErrorMessageContainsAllDetails(FileMode mode) } Assert.False(exists); } - - [PlatformSpecific(TestPlatforms.AnyUnix)] - [Theory] - [MemberData(nameof(TestUnixFileModes))] - public void CreateWithUnixFileMode(UnixFileMode mode) - { - string filename = GetTestFilePath(); - FileStream fs = CreateFileStream(filename, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, mode); - fs.Dispose(); - - UnixFileMode platformFilter = PlatformDetection.IsBsdLike - ? (UnixFileMode.SetGroup | UnixFileMode.SetUser | UnixFileMode.StickyBit) - : UnixFileMode.None; - UnixFileMode expectedMode = mode & ~GetUmask() & ~platformFilter; - UnixFileMode actualMode = File.GetUnixFileMode(filename); - Assert.Equal(expectedMode, actualMode); - } - - [PlatformSpecific(TestPlatforms.AnyUnix)] - [Fact] - public void CreateDoesntChangeExistingMode() - { - // Create file as writable for user only. - const UnixFileMode mode = UnixFileMode.UserWrite; - string filename = GetTestFilePath(); - using FileStream fs = CreateFileStream(filename, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, mode); - fs.Dispose(); - - // Now open with AllAccess. - using FileStream fs2 = CreateFileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, AllAccess); - UnixFileMode actualMode = File.GetUnixFileMode(filename); - Assert.Equal(mode, actualMode); - } } } From f3c55bfc06d1e3f8910376ff976544472323a593 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Wed, 8 Jun 2022 05:34:24 +0200 Subject: [PATCH 18/42] Add ArgumentException tests. --- .../CreateDirectory_UnixFileMode.Unix.cs | 15 ++++++++++++ .../tests/FileStream/FileStreamOptions.cs | 24 +++++++++++++++++++ .../tests/FileStream/ctor_options.Unix.cs | 1 + .../src/System/IO/Directory.Unix.cs | 2 +- .../src/System/IO/FileStatus.Unix.cs | 2 +- .../src/System/IO/FileStreamOptions.cs | 2 +- 6 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs index 2f86fa96a205f..446976c48ffeb 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs @@ -38,5 +38,20 @@ public void CreateDoesntChangeExistingMode() DirectoryInfo di2 = Directory.CreateDirectory(path, UnixFileMode.UserRead); Assert.Equal(initialMode, di2.UnixFileMode); } + + [Theory] + [InlineData((UnixFileMode)(1 << 12), false)] + [InlineData((UnixFileMode)(1 << 12), true)] + public void InvalidModeThrows(UnixFileMode mode, bool alreadyExists) + { + string path = GetRandomDirPath(); + + if (alreadyExists) + { + Directory.CreateDirectory(path); + } + + Assert.Throws(() => Directory.CreateDirectory(path, mode)); + } } } diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamOptions.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamOptions.cs index 8dd7f87b84340..fdde4a2561d5a 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamOptions.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamOptions.cs @@ -198,5 +198,29 @@ static void Validate(FileStream fs, string expectedPath, bool expectedAsync, boo } } } + + [PlatformSpecific(TestPlatforms.Windows)] + [Fact] + public void UnixCreateMode_Unsupported() + { + Assert.Null(new FileStreamOptions().UnixCreateMode); + + Assert.Throws(() => new FileStreamOptions { UnixCreateMode = null }); + Assert.Throws(() => new FileStreamOptions { UnixCreateMode = UnixFileMode.None }); + Assert.Throws(() => new FileStreamOptions { UnixCreateMode = UnixFileMode.UserRead }); + } + + [PlatformSpecific(TestPlatforms.AnyUnix)] + [Fact] + public void UnixCreateMode_Supported() + { + Assert.Null(new FileStreamOptions().UnixCreateMode); + + Assert.Null(new FileStreamOptions { UnixCreateMode = null }.UnixCreateMode); + Assert.Equal(UnixFileMode.None, new FileStreamOptions { UnixCreateMode = UnixFileMode.None }.UnixCreateMode); + Assert.Equal(UnixFileMode.UserRead, new FileStreamOptions { UnixCreateMode = UnixFileMode.UserRead }.UnixCreateMode); + + Assert.Throws(() => new FileStreamOptions { UnixCreateMode = (UnixFileMode)(1 << 12) }); + } } } diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs index e6e95d5d88d6b..03aa567a0b7ff 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; +using Xunit; namespace System.IO.Tests { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Unix.cs index 034c0e9582f5d..d874d1c8c9bc3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.Unix.cs @@ -11,7 +11,7 @@ private static DirectoryInfo CreateDirectoryCore(string path, UnixFileMode unixC if ((unixCreateMode & ~FileSystem.ValidUnixFileModes) != 0) { - ThrowHelper.ArgumentOutOfRangeException_Enum_Value(); + throw new ArgumentException(SR.Arg_InvalidUnixFileMode, nameof(unixCreateMode)); } string fullPath = Path.GetFullPath(path); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs index 6c96eec4db56a..7f7d9a7557c17 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs @@ -427,7 +427,7 @@ private void SetUnixFileMode(SafeFileHandle? handle, string? path, UnixFileMode { if ((mode & ~FileSystem.ValidUnixFileModes) != 0) { - throw new ArgumentException(SR.Arg_InvalidUnixFileMode, "UnixFileMode"); + throw new ArgumentException(SR.Arg_InvalidUnixFileMode, nameof(UnixFileMode)); } EnsureCachesInitialized(handle, path); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs index f7d2d8bce812e..d507cc4d5ccb5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs @@ -133,7 +133,7 @@ public UnixFileMode? UnixCreateMode if (value.HasValue && ((value & ~FileSystem.ValidUnixFileModes) != 0)) { - ThrowHelper.ArgumentOutOfRangeException_Enum_Value(); + throw new ArgumentException(SR.Arg_InvalidUnixFileMode, nameof(UnixCreateMode)); } _unixCreateMode = value; From 91e0891aee0746d0143e91821965c624dbe57a80 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Wed, 8 Jun 2022 08:10:41 +0200 Subject: [PATCH 19/42] Fix Windows build. --- .../tests/Directory/CreateDirectory_UnixFileMode.Windows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Windows.cs b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Windows.cs index 8c23d7a29a606..d20b94f0a0d1e 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Windows.cs @@ -5,7 +5,7 @@ namespace System.IO.Tests { - public class CreateDirectoryWithUnixFileMode + public class CreateDirectoryWithUnixFileMode : FileSystemTest { [Fact] public void NotSupported() From 6e74d98d5356bf771271191fbc93789341d709e7 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Wed, 8 Jun 2022 10:36:41 +0200 Subject: [PATCH 20/42] Add get/set tests. --- .../tests/Base/BaseGetSetUnixFileMode.cs | 181 ++++++++++++++++++ .../tests/DirectoryInfo/GetSetUnixFileMode.cs | 23 +++ .../tests/File/GetSetUnixFileMode.cs | 16 ++ .../File/GetSetUnixFileMode_SafeFileHandle.cs | 26 +++ .../tests/FileInfo/GetSetUnixFileMode.cs | 14 ++ .../tests/FileSystemTest.Unix.cs | 49 ----- .../tests/FileSystemTest.cs | 48 +++++ .../tests/System.IO.FileSystem.Tests.csproj | 5 + 8 files changed, 313 insertions(+), 49 deletions(-) create mode 100644 src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs create mode 100644 src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetUnixFileMode.cs create mode 100644 src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode.cs create mode 100644 src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode_SafeFileHandle.cs create mode 100644 src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetUnixFileMode.cs diff --git a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs new file mode 100644 index 0000000000000..229ce021aa0aa --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs @@ -0,0 +1,181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using Xunit; + +namespace System.IO.Tests +{ + public abstract class BaseGetSetUnixFileMode : FileSystemTest + { + protected abstract UnixFileMode GetMode(string path); + protected abstract void SetMode(string path, UnixFileMode mode); + + // All Set APIs always affect the target of the link. + // The Get API returns the link value, except for the FileSafeHandle APIs which return the target value. + protected virtual bool GetApiTargetsLink => true; + + // When false, the Get API returns (UnixFileMode)(-1) when the file doesn't exist. + protected virtual bool GetThrowsWhenDoesntExist => false; + + // The FileSafeHandle APIs require the file to be readable to create the handle. + protected virtual bool GetModeNeedsReadableFile => false; + + // When false, the Get API returns (UnixFileMode)(-1) instead of throwing. + protected virtual bool GetModeThrowsPNSE => true; + + protected virtual string CreateTestItem(string path = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) + { + path = path ?? GetTestFilePath(null, memberName, lineNumber); + File.Create(path).Dispose(); + return path; + } + + [PlatformSpecific(TestPlatforms.AnyUnix)] + [Theory] + [MemberData(nameof(TestUnixFileModes))] + public void SetThenGet(UnixFileMode mode) + { + if (GetModeNeedsReadableFile) + { + // Ensure the file remains readable. + mode |= UnixFileMode.UserRead; + } + + string path = CreateTestItem(); + + SetMode(path, mode); + + Assert.Equal(mode, GetMode(path)); + } + + [PlatformSpecific(TestPlatforms.AnyUnix)] + [Theory] + [MemberData(nameof(TestUnixFileModes))] + public void SetThenGet_SymbolicLink(UnixFileMode mode) + { + if (GetModeNeedsReadableFile) + { + // Ensure the file remains readable. + mode |= UnixFileMode.UserRead; + } + + string path = CreateTestItem(); + UnixFileMode initialMode = GetMode(path); + + string linkPath = GetTestFilePath(); + File.CreateSymbolicLink(linkPath, path); + + // SetMode always changes the target. + SetMode(linkPath, mode); + + Assert.Equal(mode, GetMode(path)); + + if (GetApiTargetsLink) + { + Assert.Equal(AllAccess, GetMode(linkPath)); + } + else + { + Assert.Equal(mode, GetMode(linkPath)); + } + } + + [PlatformSpecific(TestPlatforms.AnyUnix)] + [Fact] + public void FileDoesntExist() + { + string path = GetTestFilePath(); + + if (GetThrowsWhenDoesntExist) + { + Assert.Throws(() => GetMode(path)); + } + else + { + Assert.Equal((UnixFileMode)(-1), GetMode(path)); + } + Assert.Throws(() => SetMode(path, AllAccess)); + } + + [PlatformSpecific(TestPlatforms.AnyUnix)] + [Fact] + public void FileDoesntExist_SymbolicLink() + { + string path = GetTestFilePath(); + string linkPath = GetTestFilePath(); + File.CreateSymbolicLink(linkPath, path); + + if (GetModeNeedsReadableFile && !GetApiTargetsLink) + { + Assert.Throws(() => GetMode(linkPath)); + } + else + { + Assert.Equal(AllAccess, GetMode(linkPath)); + } + Assert.Throws(() => SetMode(linkPath, AllAccess)); + } + + [PlatformSpecific(TestPlatforms.AnyUnix)] + [Fact] + public void ParentDirDoesntExist() + { + string path = Path.Combine(GetTestFilePath(), "dir", "file"); + + if (GetThrowsWhenDoesntExist) + { + Assert.Throws(() => GetMode(path)); + } + else + { + Assert.Equal((UnixFileMode)(-1), GetMode(path)); + } + Assert.Throws(() => SetMode(path, AllAccess)); + } + + [PlatformSpecific(TestPlatforms.AnyUnix)] + [Fact] + public void NullPath() + { + Assert.Throws(() => GetMode(null)); + Assert.Throws(() => SetMode(null, AllAccess)); + } + + [PlatformSpecific(TestPlatforms.AnyUnix)] + [Fact] + public void InvalidPath() + { + Assert.Throws(() => GetMode(string.Empty)); + Assert.Throws(() => SetMode(string.Empty, AllAccess)); + } + + [PlatformSpecific(TestPlatforms.AnyUnix)] + [Theory] + [InlineData((UnixFileMode)(1 << 12))] + public void InvalidMode(UnixFileMode mode) + { + string path = CreateTestItem(); + + Assert.Throws(() => SetMode(path, mode)); + } + + [PlatformSpecific(TestPlatforms.Windows)] + [Fact] + public void Unsupported() + { + string path = CreateTestItem(); + + Assert.Throws(() => SetMode(path, AllAccess)); + + if (GetModeThrowsPNSE) + { + Assert.Throws(() => GetMode(path)); + } + else + { + Assert.Equal((UnixFileMode)(-1), GetMode(path)); + } + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetUnixFileMode.cs b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetUnixFileMode.cs new file mode 100644 index 0000000000000..0ac8e3d05795b --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetUnixFileMode.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System.IO.Tests +{ + public class DirectoryInfo_GetSetUnixFileMode : BaseGetSetUnixFileMode + { + protected override string CreateTestItem(string path = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) + { + path = path ?? GetTestFilePath(null, memberName, lineNumber); + Directory.CreateDirectory(path); + return path; + } + + protected override UnixFileMode GetMode(string path) + => new DirectoryInfo(path).UnixFileMode; + + protected override void SetMode(string path, UnixFileMode mode) + => new DirectoryInfo(path).UnixFileMode = mode; + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode.cs b/src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode.cs new file mode 100644 index 0000000000000..50366d6e208e0 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.IO.Tests +{ + public class File_GetSetUnixFileMode : BaseGetSetUnixFileMode + { + protected override bool GetThrowsWhenDoesntExist => true; + + protected override UnixFileMode GetMode(string path) + => File.GetUnixFileMode(path); + + protected override void SetMode(string path, UnixFileMode mode) + => File.SetUnixFileMode(path, mode); + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode_SafeFileHandle.cs b/src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode_SafeFileHandle.cs new file mode 100644 index 0000000000000..ad6750561126d --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode_SafeFileHandle.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Win32.SafeHandles; + +namespace System.IO.Tests +{ + public class File_GetSetUnixFileMode_SafeFileHandle : BaseGetSetUnixFileMode + { + protected override bool GetApiTargetsLink => false; + protected override bool GetThrowsWhenDoesntExist => true; + protected override bool GetModeNeedsReadableFile => true; + + protected override UnixFileMode GetMode(string path) + { + using SafeFileHandle fileHandle = File.OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + return File.GetUnixFileMode(fileHandle); + } + + protected override void SetMode(string path, UnixFileMode mode) + { + using SafeFileHandle fileHandle = File.OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + File.SetUnixFileMode(fileHandle, mode); + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetUnixFileMode.cs b/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetUnixFileMode.cs new file mode 100644 index 0000000000000..a3ce36952afe2 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetUnixFileMode.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.IO.Tests +{ + public class FileInfo_GetSetUnixFileMode : BaseGetSetUnixFileMode + { + protected override UnixFileMode GetMode(string path) + => new FileInfo(path).UnixFileMode; + + protected override void SetMode(string path, UnixFileMode mode) + => new FileInfo(path).UnixFileMode = mode; + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/FileSystemTest.Unix.cs b/src/libraries/System.IO.FileSystem/tests/FileSystemTest.Unix.cs index dd783e8e888b2..213f21525d002 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileSystemTest.Unix.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileSystemTest.Unix.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; -using System.Diagnostics; using System.Runtime.InteropServices; using Xunit; @@ -15,52 +13,5 @@ public abstract partial class FileSystemTest [LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] protected static partial int mkfifo(string path, int mode); - - internal const UnixFileMode AllAccess = - UnixFileMode.UserRead | - UnixFileMode.UserWrite | - UnixFileMode.UserExecute | - UnixFileMode.GroupRead | - UnixFileMode.GroupWrite | - UnixFileMode.GroupExecute | - UnixFileMode.OtherRead | - UnixFileMode.OtherWrite | - UnixFileMode.OtherExecute; - - public static IEnumerable TestUnixFileModes - { - get - { - // Make combinations of the enum with 0, 1 and 2 bits set. - UnixFileMode[] modes = Enum.GetValues(); - for (int i = 0; i < modes.Length; i++) - { - for (int j = i; j < modes.Length; j++) - { - yield return new object[] { modes[i] | modes[j] }; - } - } - } - } - - private static UnixFileMode s_umask = (UnixFileMode)(-1); - - protected static UnixFileMode GetUmask() - { - if (s_umask == (UnixFileMode)(-1)) - { - // The umask can't be retrieved without changing it. - // We launch a child process to get its value. - using Process px = Process.Start(new ProcessStartInfo - { - FileName = "/bin/sh", - ArgumentList = { "-c", "umask" }, - RedirectStandardOutput = true - }); - string stdout = px.StandardOutput.ReadToEnd().Trim(); - s_umask = (UnixFileMode)Convert.ToInt32(stdout, 8); - } - return s_umask; - } } } diff --git a/src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs b/src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs index 3d309194df649..cc12a3f26d907 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.Diagnostics; using Microsoft.DotNet.XUnitExtensions; using Xunit; @@ -108,5 +109,52 @@ protected static bool GetIsCaseSensitiveByProbing(string probingDirectory) return !File.Exists(lowerCased); } } + + protected const UnixFileMode AllAccess = + UnixFileMode.UserRead | + UnixFileMode.UserWrite | + UnixFileMode.UserExecute | + UnixFileMode.GroupRead | + UnixFileMode.GroupWrite | + UnixFileMode.GroupExecute | + UnixFileMode.OtherRead | + UnixFileMode.OtherWrite | + UnixFileMode.OtherExecute; + + public static IEnumerable TestUnixFileModes + { + get + { + // Make combinations of the enum with 0, 1 and 2 bits set. + UnixFileMode[] modes = Enum.GetValues(); + for (int i = 0; i < modes.Length; i++) + { + for (int j = i; j < modes.Length; j++) + { + yield return new object[] { modes[i] | modes[j] }; + } + } + } + } + + private static UnixFileMode s_umask = (UnixFileMode)(-1); + + protected static UnixFileMode GetUmask() + { + if (s_umask == (UnixFileMode)(-1)) + { + // The umask can't be retrieved without changing it. + // We launch a child process to get its value. + using Process px = Process.Start(new ProcessStartInfo + { + FileName = "/bin/sh", + ArgumentList = { "-c", "umask" }, + RedirectStandardOutput = true + }); + string stdout = px.StandardOutput.ReadToEnd().Trim(); + s_umask = (UnixFileMode)Convert.ToInt32(stdout, 8); + } + return s_umask; + } } } diff --git a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj index bcf32b8f06805..1db5a016e3685 100644 --- a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj +++ b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj @@ -10,6 +10,7 @@ + @@ -21,7 +22,9 @@ + + @@ -32,6 +35,8 @@ + + From 757c1e5cb20aafb78764427b58a67e03af4e9dcb Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Wed, 8 Jun 2022 10:40:17 +0200 Subject: [PATCH 21/42] Update test for Windows. --- .../tests/DirectoryInfo/GetSetUnixFileMode.cs | 2 ++ .../System.IO.FileSystem/tests/FileInfo/GetSetUnixFileMode.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetUnixFileMode.cs b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetUnixFileMode.cs index 0ac8e3d05795b..6009861586fc8 100644 --- a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetUnixFileMode.cs +++ b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetUnixFileMode.cs @@ -7,6 +7,8 @@ namespace System.IO.Tests { public class DirectoryInfo_GetSetUnixFileMode : BaseGetSetUnixFileMode { + protected override bool GetModeThrowsPNSE => false; + protected override string CreateTestItem(string path = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) { path = path ?? GetTestFilePath(null, memberName, lineNumber); diff --git a/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetUnixFileMode.cs b/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetUnixFileMode.cs index a3ce36952afe2..1ca1d6040a05a 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetUnixFileMode.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetUnixFileMode.cs @@ -5,6 +5,8 @@ namespace System.IO.Tests { public class FileInfo_GetSetUnixFileMode : BaseGetSetUnixFileMode { + protected override bool GetModeThrowsPNSE => false; + protected override UnixFileMode GetMode(string path) => new FileInfo(path).UnixFileMode; From 53539ec49f3b6663c925db3b0f667fae1b197ee7 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Wed, 8 Jun 2022 16:57:45 +0200 Subject: [PATCH 22/42] Make setters target link instead of link target. --- .../Unix/System.Native/Interop.LChMod.cs | 14 ++++ .../tests/Base/BaseGetSetUnixFileMode.cs | 68 +++++++++++++------ .../File/GetSetUnixFileMode_SafeFileHandle.cs | 2 +- .../System.Private.CoreLib.Shared.projitems | 3 + .../src/System/IO/FileStatus.Unix.cs | 2 +- src/native/libs/System.Native/entrypoints.c | 1 + src/native/libs/System.Native/pal_io.c | 7 ++ src/native/libs/System.Native/pal_io.h | 7 ++ 8 files changed, 83 insertions(+), 21 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.LChMod.cs diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.LChMod.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.LChMod.cs new file mode 100644 index 0000000000000..631252cf9b431 --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.LChMod.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LChMod", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] + internal static partial int LChMod(string path, int mode); + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs index 229ce021aa0aa..9445b87f49e6f 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs @@ -11,19 +11,21 @@ public abstract class BaseGetSetUnixFileMode : FileSystemTest protected abstract UnixFileMode GetMode(string path); protected abstract void SetMode(string path, UnixFileMode mode); - // All Set APIs always affect the target of the link. - // The Get API returns the link value, except for the FileSafeHandle APIs which return the target value. - protected virtual bool GetApiTargetsLink => true; + // When true, the APIs follow the link for Get and Set. + protected virtual bool ApiFollowsLink => false; // When false, the Get API returns (UnixFileMode)(-1) when the file doesn't exist. protected virtual bool GetThrowsWhenDoesntExist => false; - // The FileSafeHandle APIs require the file to be readable to create the handle. + // The FileSafeHandle APIs require a readable file to open the handle. protected virtual bool GetModeNeedsReadableFile => false; - // When false, the Get API returns (UnixFileMode)(-1) instead of throwing. + // When false, the Get API returns (UnixFileMode)(-1) when the platform is not supported. protected virtual bool GetModeThrowsPNSE => true; + // Linux doesn't support setting UnixFileMode on links. + private bool SetModeSupportsLink => PlatformDetection.IsBsdLike; + protected virtual string CreateTestItem(string path = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) { path = path ?? GetTestFilePath(null, memberName, lineNumber); @@ -61,23 +63,31 @@ public void SetThenGet_SymbolicLink(UnixFileMode mode) } string path = CreateTestItem(); - UnixFileMode initialMode = GetMode(path); string linkPath = GetTestFilePath(); File.CreateSymbolicLink(linkPath, path); - // SetMode always changes the target. - SetMode(linkPath, mode); - - Assert.Equal(mode, GetMode(path)); - - if (GetApiTargetsLink) + if (ApiFollowsLink) { - Assert.Equal(AllAccess, GetMode(linkPath)); + SetMode(linkPath, mode); + + Assert.Equal(mode, GetMode(linkPath)); + Assert.Equal(mode, GetMode(path)); } else { + if (!SetModeSupportsLink) + { + Assert.Throws(() => SetMode(linkPath, mode)); + return; + } + + UnixFileMode initialMode = GetMode(path); + + SetMode(linkPath, mode); + Assert.Equal(mode, GetMode(linkPath)); + Assert.Equal(initialMode, GetMode(path)); } } @@ -99,22 +109,42 @@ public void FileDoesntExist() } [PlatformSpecific(TestPlatforms.AnyUnix)] - [Fact] - public void FileDoesntExist_SymbolicLink() + [Theory] + [InlineData(UnixFileMode.UserRead)] + public void FileDoesntExist_SymbolicLink(UnixFileMode mode) { string path = GetTestFilePath(); string linkPath = GetTestFilePath(); File.CreateSymbolicLink(linkPath, path); - if (GetModeNeedsReadableFile && !GetApiTargetsLink) + if (ApiFollowsLink) { - Assert.Throws(() => GetMode(linkPath)); + Assert.Throws(() => SetMode(linkPath, AllAccess)); + + if (GetThrowsWhenDoesntExist) + { + Assert.Throws(() => GetMode(path)); + } + else + { + Assert.Equal((UnixFileMode)(-1), GetMode(path)); + } } else { - Assert.Equal(AllAccess, GetMode(linkPath)); + if (!SetModeSupportsLink) + { + Assert.Throws(() => SetMode(linkPath, AllAccess)); + return; + } + + UnixFileMode initialMode = GetMode(path); + + SetMode(linkPath, mode); + + Assert.Equal(mode, GetMode(linkPath)); + Assert.Equal(initialMode, GetMode(path)); } - Assert.Throws(() => SetMode(linkPath, AllAccess)); } [PlatformSpecific(TestPlatforms.AnyUnix)] diff --git a/src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode_SafeFileHandle.cs b/src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode_SafeFileHandle.cs index ad6750561126d..ad83b2d2fe4e2 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode_SafeFileHandle.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode_SafeFileHandle.cs @@ -7,7 +7,7 @@ namespace System.IO.Tests { public class File_GetSetUnixFileMode_SafeFileHandle : BaseGetSetUnixFileMode { - protected override bool GetApiTargetsLink => false; + protected override bool ApiFollowsLink => true; protected override bool GetThrowsWhenDoesntExist => true; protected override bool GetModeNeedsReadableFile => true; diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index b3f35a853d7f6..8f0706b805d73 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2061,6 +2061,9 @@ Common\Interop\Unix\System.Native\Interop.LChflags.cs + + Common\Interop\Unix\System.Native\Interop.LChMod.cs + Common\Interop\Unix\System.Native\Interop.Link.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs index 7f7d9a7557c17..164d929fb266d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs @@ -436,7 +436,7 @@ private void SetUnixFileMode(SafeFileHandle? handle, string? path, UnixFileMode FileSystemInfo.ThrowNotFound(path); int rv = handle is not null ? Interop.Sys.FChMod(handle, (int)mode) : - Interop.Sys.ChMod(path!, (int)mode); + Interop.Sys.LChMod(path!, (int)mode); Interop.CheckIo(rv, path); InvalidateCaches(); diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index 6a960a42ae14c..cbab2166188d1 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -81,6 +81,7 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_ChDir) DllImportEntry(SystemNative_Access) DllImportEntry(SystemNative_LSeek) + DllImportEntry(SystemNative_LChMod) DllImportEntry(SystemNative_Link) DllImportEntry(SystemNative_SymLink) DllImportEntry(SystemNative_MkNod) diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c index a0714a778f196..a50d5b2856f84 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -706,6 +706,13 @@ int32_t SystemNative_FChMod(intptr_t fd, int32_t mode) return result; } +int32_t SystemNative_LChMod(const char* path, int32_t mode) +{ + int32_t result; + while ((result = fchmodat(AT_FDCWD, path, (mode_t)mode, AT_SYMLINK_NOFOLLOW)) < 0 && errno == EINTR); + return result; +} + int32_t SystemNative_FSync(intptr_t fd) { int fileDescriptor = ToFileDescriptor(fd); diff --git a/src/native/libs/System.Native/pal_io.h b/src/native/libs/System.Native/pal_io.h index 1ace89303642a..9790cab9ac068 100644 --- a/src/native/libs/System.Native/pal_io.h +++ b/src/native/libs/System.Native/pal_io.h @@ -490,6 +490,13 @@ PALEXPORT int32_t SystemNative_ChMod(const char* path, int32_t mode); */ PALEXPORT int32_t SystemNative_FChMod(intptr_t fd, int32_t mode); +/** +* Change permissions of a file. Implemented as a shim to lchmod(2). +* +* Returns 0 for success, -1 for failure. Sets errno for failure. +*/ +PALEXPORT int32_t SystemNative_LChMod(const char* path, int32_t mode); + /** * Flushes all modified data and attribtues of the specified File Descriptor to the storage medium. * From 873660e6c25ae1b216cf9a1f171b5530e172ce37 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Fri, 10 Jun 2022 06:22:15 +0200 Subject: [PATCH 23/42] Linux: fix SetUnixFileMode --- .../System.Private.CoreLib.Shared.projitems | 2 ++ .../IO/FileStatus.SetUnixFileMode.Linux.cs | 25 +++++++++++++++++++ .../IO/FileStatus.SetUnixFileMode.NonLinux.cs | 11 ++++++++ .../src/System/IO/FileStatus.Unix.cs | 7 +----- 4 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.Linux.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 8f0706b805d73..0d2beeba0e79b 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2199,6 +2199,8 @@ + + diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.Linux.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.Linux.cs new file mode 100644 index 0000000000000..b3d545d278528 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.Linux.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.IO +{ + internal partial struct FileStatus + { + private int ChModNoFollowLink(string path, int mode) + { + // Linux doesn't support file modes on links. + // lchmod is not implemented and returns ENOTSUP even for non-links. + // To support changing mode on non-links, we first check if the file + // is a link. If it isn't, we use ChMod (which would follow links) + // to change the mode. + + EnsureCachesInitialized(path); + + if (!EntryExists) + FileSystemInfo.ThrowNotFound(path); + + return HasSymbolicLinkFlag ? Interop.Sys.LChMod(path!, mode) + : Interop.Sys.ChMod(path, mode); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs new file mode 100644 index 0000000000000..7e487c5b6b4aa --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.IO +{ + internal partial struct FileStatus + { + private int ChModNoFollowLink(string path, int mode) => + Interop.Sys.LChMod(path!, mode); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs index 164d929fb266d..e39b3ad15a588 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs @@ -430,13 +430,8 @@ private void SetUnixFileMode(SafeFileHandle? handle, string? path, UnixFileMode throw new ArgumentException(SR.Arg_InvalidUnixFileMode, nameof(UnixFileMode)); } - EnsureCachesInitialized(handle, path); - - if (!EntryExists) - FileSystemInfo.ThrowNotFound(path); - int rv = handle is not null ? Interop.Sys.FChMod(handle, (int)mode) : - Interop.Sys.LChMod(path!, (int)mode); + ChModNoFollowLink(path!, (int)mode); Interop.CheckIo(rv, path); InvalidateCaches(); From d9c7789593b65e9ce0c102c1dde16bf6dd486f75 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Fri, 10 Jun 2022 06:44:49 +0200 Subject: [PATCH 24/42] Fix OSX compilation. --- .../src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs index 7e487c5b6b4aa..ed9267bb9d3f4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs @@ -5,7 +5,7 @@ namespace System.IO { internal partial struct FileStatus { - private int ChModNoFollowLink(string path, int mode) => + private static int ChModNoFollowLink(string path, int mode) => Interop.Sys.LChMod(path!, mode); } } From 3dba8085667d5c204c50af5f311f36e65459202b Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Fri, 10 Jun 2022 17:20:25 +0200 Subject: [PATCH 25/42] Try make all tests pass in CI. --- .../tests/Base/BaseGetSetUnixFileMode.cs | 32 ++++++++++++++++--- .../tests/DirectoryInfo/GetSetUnixFileMode.cs | 7 +--- .../IO/FileStatus.SetUnixFileMode.NonLinux.cs | 11 +++++-- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs index 9445b87f49e6f..c81f4f28bf9d6 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs @@ -23,13 +23,30 @@ public abstract class BaseGetSetUnixFileMode : FileSystemTest // When false, the Get API returns (UnixFileMode)(-1) when the platform is not supported. protected virtual bool GetModeThrowsPNSE => true; + // Determines if the derived Test class is for directories or files. + protected virtual bool IsDirectory => false; + // Linux doesn't support setting UnixFileMode on links. private bool SetModeSupportsLink => PlatformDetection.IsBsdLike; - protected virtual string CreateTestItem(string path = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) + // On OSX, directories created under /tmp have same group as /tmp. + // Because that group is different from the test user's group, chmod + // returns EPERM when trying to setgid on directories, and for files + // chmod filters out the bit. + // We skip the tests with setgid. + private bool CanSetGroup => !PlatformDetection.IsBsdLike; + + private string CreateTestItem(string path = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) { path = path ?? GetTestFilePath(null, memberName, lineNumber); - File.Create(path).Dispose(); + if (IsDirectory) + { + Directory.CreateDirectory(path); + } + else + { + File.Create(path).Dispose(); + } return path; } @@ -38,6 +55,10 @@ protected virtual string CreateTestItem(string path = null, [CallerMemberName] s [MemberData(nameof(TestUnixFileModes))] public void SetThenGet(UnixFileMode mode) { + if (!CanSetGroup && (mode & UnixFileMode.SetGroup) != 0) + { + return; // Skip + } if (GetModeNeedsReadableFile) { // Ensure the file remains readable. @@ -56,6 +77,10 @@ public void SetThenGet(UnixFileMode mode) [MemberData(nameof(TestUnixFileModes))] public void SetThenGet_SymbolicLink(UnixFileMode mode) { + if (!CanSetGroup && (mode & UnixFileMode.SetGroup) != 0) + { + return; // Skip + } if (GetModeNeedsReadableFile) { // Ensure the file remains readable. @@ -138,12 +163,9 @@ public void FileDoesntExist_SymbolicLink(UnixFileMode mode) return; } - UnixFileMode initialMode = GetMode(path); - SetMode(linkPath, mode); Assert.Equal(mode, GetMode(linkPath)); - Assert.Equal(initialMode, GetMode(path)); } } diff --git a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetUnixFileMode.cs b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetUnixFileMode.cs index 6009861586fc8..1a0d6fd1dbbe1 100644 --- a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetUnixFileMode.cs +++ b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetUnixFileMode.cs @@ -9,12 +9,7 @@ public class DirectoryInfo_GetSetUnixFileMode : BaseGetSetUnixFileMode { protected override bool GetModeThrowsPNSE => false; - protected override string CreateTestItem(string path = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) - { - path = path ?? GetTestFilePath(null, memberName, lineNumber); - Directory.CreateDirectory(path); - return path; - } + protected override bool IsDirectory => true; protected override UnixFileMode GetMode(string path) => new DirectoryInfo(path).UnixFileMode; diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs index ed9267bb9d3f4..6c6d265a111d4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs @@ -5,7 +5,14 @@ namespace System.IO { internal partial struct FileStatus { - private static int ChModNoFollowLink(string path, int mode) => - Interop.Sys.LChMod(path!, mode); + private int ChModNoFollowLink(string path, int mode) + { + EnsureCachesInitialized(path); + + if (!EntryExists) + FileSystemInfo.ThrowNotFound(path); + + return Interop.Sys.LChMod(path, mode); + } } } From 33d3e6f877e363f767e38579f23dc66b199c9025 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Fri, 17 Jun 2022 06:09:02 +0200 Subject: [PATCH 26/42] For link, operate on target permissions. --- .../Unix/System.Native/Interop.LChMod.cs | 14 ----- .../tests/Base/AllGetSetAttributes.cs | 11 +++- .../tests/Base/BaseGetSetUnixFileMode.cs | 62 ++++--------------- .../File/GetSetUnixFileMode_SafeFileHandle.cs | 1 - .../System.Private.CoreLib.Shared.projitems | 5 -- .../IO/FileStatus.SetUnixFileMode.Linux.cs | 25 -------- .../IO/FileStatus.SetUnixFileMode.NonLinux.cs | 18 ------ .../src/System/IO/FileStatus.Unix.cs | 40 +++++++++--- src/native/libs/System.Native/entrypoints.c | 1 - src/native/libs/System.Native/pal_io.c | 7 --- src/native/libs/System.Native/pal_io.h | 7 --- 11 files changed, 51 insertions(+), 140 deletions(-) delete mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.LChMod.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.Linux.cs delete mode 100644 src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.LChMod.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.LChMod.cs deleted file mode 100644 index 631252cf9b431..0000000000000 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.LChMod.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Runtime.InteropServices; - -internal static partial class Interop -{ - internal static partial class Sys - { - [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LChMod", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] - internal static partial int LChMod(string path, int mode); - } -} diff --git a/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs b/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs index ab667d4ebe7e6..aab50ac7e65da 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs @@ -48,7 +48,7 @@ public void SymLinksAreReparsePoints() } [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] - public void SymLinksReflectSymLinkAttributes() + public void SymLinksReflectTargetReadOnly() { string path = CreateItem(); string linkPath = GetRandomLinkPath(); @@ -59,7 +59,14 @@ public void SymLinksReflectSymLinkAttributes() try { Assert.Equal(FileAttributes.ReadOnly, FileAttributes.ReadOnly & GetAttributes(path)); - Assert.NotEqual(FileAttributes.ReadOnly, FileAttributes.ReadOnly & GetAttributes(linkPath)); + if (OperatingSystem.IsWindows()) + { + Assert.NotEqual(FileAttributes.ReadOnly, FileAttributes.ReadOnly & GetAttributes(linkPath)); + } + else + { + Assert.Equal(FileAttributes.ReadOnly, FileAttributes.ReadOnly & GetAttributes(linkPath)); + } } finally { diff --git a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs index c81f4f28bf9d6..ba1c63b6648c0 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs @@ -11,24 +11,18 @@ public abstract class BaseGetSetUnixFileMode : FileSystemTest protected abstract UnixFileMode GetMode(string path); protected abstract void SetMode(string path, UnixFileMode mode); - // When true, the APIs follow the link for Get and Set. - protected virtual bool ApiFollowsLink => false; - // When false, the Get API returns (UnixFileMode)(-1) when the file doesn't exist. protected virtual bool GetThrowsWhenDoesntExist => false; // The FileSafeHandle APIs require a readable file to open the handle. protected virtual bool GetModeNeedsReadableFile => false; - // When false, the Get API returns (UnixFileMode)(-1) when the platform is not supported. + // When false, the Get API returns (UnixFileMode)(-1) when the platform is not supported (Windows). protected virtual bool GetModeThrowsPNSE => true; // Determines if the derived Test class is for directories or files. protected virtual bool IsDirectory => false; - // Linux doesn't support setting UnixFileMode on links. - private bool SetModeSupportsLink => PlatformDetection.IsBsdLike; - // On OSX, directories created under /tmp have same group as /tmp. // Because that group is different from the test user's group, chmod // returns EPERM when trying to setgid on directories, and for files @@ -92,28 +86,10 @@ public void SetThenGet_SymbolicLink(UnixFileMode mode) string linkPath = GetTestFilePath(); File.CreateSymbolicLink(linkPath, path); - if (ApiFollowsLink) - { - SetMode(linkPath, mode); - - Assert.Equal(mode, GetMode(linkPath)); - Assert.Equal(mode, GetMode(path)); - } - else - { - if (!SetModeSupportsLink) - { - Assert.Throws(() => SetMode(linkPath, mode)); - return; - } - - UnixFileMode initialMode = GetMode(path); + SetMode(linkPath, mode); - SetMode(linkPath, mode); - - Assert.Equal(mode, GetMode(linkPath)); - Assert.Equal(initialMode, GetMode(path)); - } + Assert.Equal(mode, GetMode(linkPath)); + Assert.Equal(mode, GetMode(path)); } [PlatformSpecific(TestPlatforms.AnyUnix)] @@ -134,38 +110,22 @@ public void FileDoesntExist() } [PlatformSpecific(TestPlatforms.AnyUnix)] - [Theory] - [InlineData(UnixFileMode.UserRead)] - public void FileDoesntExist_SymbolicLink(UnixFileMode mode) + [Fact] + public void FileDoesntExist_SymbolicLink() { string path = GetTestFilePath(); string linkPath = GetTestFilePath(); File.CreateSymbolicLink(linkPath, path); - if (ApiFollowsLink) + Assert.Throws(() => SetMode(linkPath, AllAccess)); + + if (GetThrowsWhenDoesntExist) { - Assert.Throws(() => SetMode(linkPath, AllAccess)); - - if (GetThrowsWhenDoesntExist) - { - Assert.Throws(() => GetMode(path)); - } - else - { - Assert.Equal((UnixFileMode)(-1), GetMode(path)); - } + Assert.Throws(() => GetMode(path)); } else { - if (!SetModeSupportsLink) - { - Assert.Throws(() => SetMode(linkPath, AllAccess)); - return; - } - - SetMode(linkPath, mode); - - Assert.Equal(mode, GetMode(linkPath)); + Assert.Equal((UnixFileMode)(-1), GetMode(path)); } } diff --git a/src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode_SafeFileHandle.cs b/src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode_SafeFileHandle.cs index ad83b2d2fe4e2..6aaf607caea6a 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode_SafeFileHandle.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode_SafeFileHandle.cs @@ -7,7 +7,6 @@ namespace System.IO.Tests { public class File_GetSetUnixFileMode_SafeFileHandle : BaseGetSetUnixFileMode { - protected override bool ApiFollowsLink => true; protected override bool GetThrowsWhenDoesntExist => true; protected override bool GetModeNeedsReadableFile => true; diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 0d2beeba0e79b..b3f35a853d7f6 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2061,9 +2061,6 @@ Common\Interop\Unix\System.Native\Interop.LChflags.cs - - Common\Interop\Unix\System.Native\Interop.LChMod.cs - Common\Interop\Unix\System.Native\Interop.Link.cs @@ -2199,8 +2196,6 @@ - - diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.Linux.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.Linux.cs deleted file mode 100644 index b3d545d278528..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.Linux.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.IO -{ - internal partial struct FileStatus - { - private int ChModNoFollowLink(string path, int mode) - { - // Linux doesn't support file modes on links. - // lchmod is not implemented and returns ENOTSUP even for non-links. - // To support changing mode on non-links, we first check if the file - // is a link. If it isn't, we use ChMod (which would follow links) - // to change the mode. - - EnsureCachesInitialized(path); - - if (!EntryExists) - FileSystemInfo.ThrowNotFound(path); - - return HasSymbolicLinkFlag ? Interop.Sys.LChMod(path!, mode) - : Interop.Sys.ChMod(path, mode); - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs deleted file mode 100644 index 6c6d265a111d4..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetUnixFileMode.NonLinux.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.IO -{ - internal partial struct FileStatus - { - private int ChModNoFollowLink(string path, int mode) - { - EnsureCachesInitialized(path); - - if (!EntryExists) - FileSystemInfo.ThrowNotFound(path); - - return Interop.Sys.LChMod(path, mode); - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs index e39b3ad15a588..eda97730889b7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs @@ -11,6 +11,7 @@ internal partial struct FileStatus { private const int NanosecondsPerTick = 100; + private const int InitializedExistsBrokenLink = -4; // target is link with no target. private const int InitializedExistsDir = -3; // target is directory. private const int InitializedExistsFile = -2; // target is file. private const int InitializedNotExists = -1; // entry does not exist. @@ -30,6 +31,8 @@ internal partial struct FileStatus private bool IsDir => _state == InitializedExistsDir; + private bool IsBrokenLink => _state == InitializedExistsBrokenLink; + // Check if the main path (without following symlinks) has the hidden attribute set. private bool HasHiddenFlag { @@ -48,7 +51,7 @@ private bool HasReadOnlyFlag { Debug.Assert(_state != Uninitialized); // Use this after EnsureCachesInitialized has been called. - if (!EntryExists) + if (!EntryExists || IsBrokenLink) { return false; } @@ -411,7 +414,7 @@ private UnixFileMode GetUnixFileMode(SafeFileHandle? handle, ReadOnlySpan { EnsureCachesInitialized(handle, path, continueOnError); - if (!EntryExists) + if (!EntryExists || IsBrokenLink) return (UnixFileMode)(-1); return (UnixFileMode)(_fileCache.Mode & (int)FileSystem.ValidUnixFileModes); @@ -430,8 +433,15 @@ private void SetUnixFileMode(SafeFileHandle? handle, string? path, UnixFileMode throw new ArgumentException(SR.Arg_InvalidUnixFileMode, nameof(UnixFileMode)); } - int rv = handle is not null ? Interop.Sys.FChMod(handle, (int)mode) : - ChModNoFollowLink(path!, (int)mode); + EnsureCachesInitialized(path); + + if (!EntryExists || IsBrokenLink) + FileSystemInfo.ThrowNotFound(path); + + // Linux does not support link permissions. + // To have consistent cross-platform behavior we operate on the link target. + int rv = handle is not null ? Interop.Sys.FChMod(handle, (int)mode) + : Interop.Sys.ChMod(path!, (int)mode); Interop.CheckIo(rv, path); InvalidateCaches(); @@ -478,11 +488,23 @@ internal void RefreshCaches(SafeFileHandle? handle, ReadOnlySpan path) // Check if the main path is a directory, or a link to a directory. int fileType = _fileCache.Mode & Interop.Sys.FileTypes.S_IFMT; - bool isDirectory = fileType == Interop.Sys.FileTypes.S_IFDIR || - (handle is null && // Don't follow links for SafeHandle APIs. - fileType == Interop.Sys.FileTypes.S_IFLNK && - Interop.Sys.Stat(path, out Interop.Sys.FileStatus target) == 0 && - (target.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR); + bool isDirectory = fileType == Interop.Sys.FileTypes.S_IFDIR; + + if (fileType == Interop.Sys.FileTypes.S_IFLNK) + { + if (Interop.Sys.Stat(path, out Interop.Sys.FileStatus target) == 0) + { + isDirectory = (target.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR; + + // Make GetUnixFileMode return target permissions. + _fileCache.Mode = Interop.Sys.FileTypes.S_IFLNK | (target.Mode & (int)FileSystem.ValidUnixFileModes); + } + else + { + _state = InitializedExistsBrokenLink; + return; + } + } _state = isDirectory ? InitializedExistsDir : InitializedExistsFile; } diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index cbab2166188d1..6a960a42ae14c 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -81,7 +81,6 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_ChDir) DllImportEntry(SystemNative_Access) DllImportEntry(SystemNative_LSeek) - DllImportEntry(SystemNative_LChMod) DllImportEntry(SystemNative_Link) DllImportEntry(SystemNative_SymLink) DllImportEntry(SystemNative_MkNod) diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c index a50d5b2856f84..a0714a778f196 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -706,13 +706,6 @@ int32_t SystemNative_FChMod(intptr_t fd, int32_t mode) return result; } -int32_t SystemNative_LChMod(const char* path, int32_t mode) -{ - int32_t result; - while ((result = fchmodat(AT_FDCWD, path, (mode_t)mode, AT_SYMLINK_NOFOLLOW)) < 0 && errno == EINTR); - return result; -} - int32_t SystemNative_FSync(intptr_t fd) { int fileDescriptor = ToFileDescriptor(fd); diff --git a/src/native/libs/System.Native/pal_io.h b/src/native/libs/System.Native/pal_io.h index 9790cab9ac068..1ace89303642a 100644 --- a/src/native/libs/System.Native/pal_io.h +++ b/src/native/libs/System.Native/pal_io.h @@ -490,13 +490,6 @@ PALEXPORT int32_t SystemNative_ChMod(const char* path, int32_t mode); */ PALEXPORT int32_t SystemNative_FChMod(intptr_t fd, int32_t mode); -/** -* Change permissions of a file. Implemented as a shim to lchmod(2). -* -* Returns 0 for success, -1 for failure. Sets errno for failure. -*/ -PALEXPORT int32_t SystemNative_LChMod(const char* path, int32_t mode); - /** * Flushes all modified data and attribtues of the specified File Descriptor to the storage medium. * From 18e70c9be4a0efe12f258ac69f05c21b26aaf927 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Fri, 17 Jun 2022 06:13:01 +0200 Subject: [PATCH 27/42] Skip tests on Browser. --- .../System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs index ba1c63b6648c0..6674614333b75 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs @@ -44,7 +44,7 @@ private string CreateTestItem(string path = null, [CallerMemberName] string memb return path; } - [PlatformSpecific(TestPlatforms.AnyUnix)] + [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser)] [Theory] [MemberData(nameof(TestUnixFileModes))] public void SetThenGet(UnixFileMode mode) @@ -66,7 +66,7 @@ public void SetThenGet(UnixFileMode mode) Assert.Equal(mode, GetMode(path)); } - [PlatformSpecific(TestPlatforms.AnyUnix)] + [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser)] [Theory] [MemberData(nameof(TestUnixFileModes))] public void SetThenGet_SymbolicLink(UnixFileMode mode) From b2423c6559822b9a4f5b8fd7ea7afaed41393518 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Fri, 17 Jun 2022 06:43:16 +0200 Subject: [PATCH 28/42] Add tests for 'Get' that doesn't use a 'Set' first. --- .../tests/Base/BaseGetSetUnixFileMode.cs | 21 +++++++++++++++++++ .../src/System/IO/FileStatus.Unix.cs | 11 +++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs index 6674614333b75..c6aa7ed750954 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs @@ -44,6 +44,27 @@ private string CreateTestItem(string path = null, [CallerMemberName] string memb return path; } + [PlatformSpecific(TestPlatforms.AnyUnix)] + [Fact] + public void Get() + { + string path = CreateTestItem(); + + UnixFileMode mode = GetMode(path); // Doesn't throw. + + Assert.NotEqual((UnixFileMode)(-1), mode); + + UnixFileMode required = UnixFileMode.UserRead | UnixFileMode.UserWrite; + if (IsDirectory) + { + required = UnixFileMode.UserExecute; + } + Assert.True((mode & required) == required); + + // The file should not be writable by others. + Assert.Equal(UnixFileMode.None, mode & UnixFileMode.OtherWrite); + } + [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser)] [Theory] [MemberData(nameof(TestUnixFileModes))] diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs index eda97730889b7..d94cbc25d2481 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs @@ -433,10 +433,13 @@ private void SetUnixFileMode(SafeFileHandle? handle, string? path, UnixFileMode throw new ArgumentException(SR.Arg_InvalidUnixFileMode, nameof(UnixFileMode)); } - EnsureCachesInitialized(path); + if (path is not null) + { + EnsureCachesInitialized(path); - if (!EntryExists || IsBrokenLink) - FileSystemInfo.ThrowNotFound(path); + if (!EntryExists || IsBrokenLink) + FileSystemInfo.ThrowNotFound(path); + } // Linux does not support link permissions. // To have consistent cross-platform behavior we operate on the link target. @@ -454,6 +457,8 @@ internal void RefreshCaches(ReadOnlySpan path) // This method should not throw. Instead, we store the results, and we will throw when the user attempts to access any of the properties when there was a failure internal void RefreshCaches(SafeFileHandle? handle, ReadOnlySpan path) { + Debug.Assert(handle is not null || path.Length > 0); + #if !TARGET_BROWSER _isReadOnlyCache = -1; #endif From 777b77d66975dc100d2193719750a3623caa65c1 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Fri, 17 Jun 2022 06:56:26 +0200 Subject: [PATCH 29/42] Don't perform exist check for handles. --- .../System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs index d94cbc25d2481..32ab84548d763 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs @@ -433,7 +433,8 @@ private void SetUnixFileMode(SafeFileHandle? handle, string? path, UnixFileMode throw new ArgumentException(SR.Arg_InvalidUnixFileMode, nameof(UnixFileMode)); } - if (path is not null) + // Use ThrowNotFound to throw the appropriate exception when the file doesn't exist. + if (handle is null && path is not null) { EnsureCachesInitialized(path); From 4e93e111b82423b018882680695ecc0f11f8e0f0 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Fri, 17 Jun 2022 10:08:16 +0200 Subject: [PATCH 30/42] Fix Get test for wasm. --- .../tests/Base/BaseGetSetUnixFileMode.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs index c6aa7ed750954..b8270fb3a6341 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs @@ -61,8 +61,11 @@ public void Get() } Assert.True((mode & required) == required); - // The file should not be writable by others. - Assert.Equal(UnixFileMode.None, mode & UnixFileMode.OtherWrite); + if (!PlatformDetection.IsBrowser) + { + // The umask should prevent this file from being writable by others. + Assert.Equal(UnixFileMode.None, mode & UnixFileMode.OtherWrite); + } } [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser)] From 60d24a75dd2a6bc82ae2b582ef0869cd4240a54c Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Fri, 17 Jun 2022 15:03:48 +0200 Subject: [PATCH 31/42] Review xml comments. --- .../src/System/IO/Directory.cs | 17 +++++------- .../src/System/IO/File.cs | 26 ++++++++++--------- .../src/System/IO/FileStreamOptions.cs | 2 +- .../src/System/IO/FileSystemInfo.cs | 15 +++++++---- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs index bcf06e7c72144..0087ecfef8f9b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs @@ -41,18 +41,13 @@ public static DirectoryInfo CreateDirectory(string path) /// The directory to create. /// Unix file mode used to create directories. /// An object that represents the directory at the specified path. This object is returned regardless of whether a directory at the specified path already exists. - /// The directory specified by is a file. - /// -or- - /// The network name is not known. - /// The caller does not have the required permission. - /// is a zero-length string, contains only white space, or contains one or more invalid characters. You can query for invalid characters by using the method. - /// -or- - /// is prefixed with, or contains, only a colon character (:). + /// is a zero-length string, or contains one or more invalid characters. You can query for invalid characters by using the method. /// is . - /// The specified path, file name, or both exceed the system-defined maximum length. - /// The specified path is invalid (for example, it is on an unmapped drive). - /// contains a colon character (:) that is not part of a drive label ("C:\\"). - /// The caller attempts use an invalid file mode. + /// The caller attempts to use an invalid file mode. + /// The caller does not have the required permission. + /// The specified path exceeds the system-defined maximum length. + /// is a file. + /// A component of the is not a directory. [UnsupportedOSPlatform("windows")] public static DirectoryInfo CreateDirectory(string path, UnixFileMode unixCreateMode) => CreateDirectoryCore(path, unixCreateMode); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index 4c0d1d651cd1c..5ae7b7a60122b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -223,6 +223,12 @@ public static void SetAttributes(string path, FileAttributes fileAttributes) /// Gets the of the file on the path. /// The path to the file. /// The of the file on the path. + /// is a zero-length string, or contains one or more invalid characters. You can query for invalid characters by using the method. + /// is . + /// The caller does not have the required permission. + /// The specified path exceeds the system-defined maximum length. + /// A component of the is not a directory. + /// The file cannot be found. [UnsupportedOSPlatform("windows")] public static UnixFileMode GetUnixFileMode(string path) => GetUnixFileModeCore(path); @@ -239,18 +245,13 @@ public static UnixFileMode GetUnixFileMode(SafeFileHandle fileHandle) /// Sets the specified of the file on the specified path. /// The path to the file. /// The unix file mode. - /// is empty, contains only white spaces, contains invalid characters, or the file mode is invalid. - /// The specified path, file name, or both exceed the system-defined maximum length. - /// is in an invalid format. - /// The specified path is invalid, (for example, it is on an unmapped drive). + /// is a zero-length string, or contains one or more invalid characters. You can query for invalid characters by using the method. + /// is . + /// The caller attempts to use an invalid file mode. + /// The caller does not have the required permission. + /// The specified path exceeds the system-defined maximum length. + /// A component of the is not a directory. /// The file cannot be found. - /// specified a file that is read-only. - /// -or- - /// This operation is not supported on the current platform. - /// -or- - /// specified a directory. - /// -or- - /// The caller does not have the required permission. [UnsupportedOSPlatform("windows")] public static void SetUnixFileMode(string path, UnixFileMode mode) => SetUnixFileModeCore(path, mode); @@ -258,7 +259,8 @@ public static void SetUnixFileMode(string path, UnixFileMode mode) /// Sets the specified of the specified file handle. /// The file handle. /// The unix file mode. - /// The file mode is invalid. + /// The caller attempts to use an invalid file mode. + /// The caller does not have the required permission. /// The file is closed. [UnsupportedOSPlatform("windows")] public static void SetUnixFileMode(SafeFileHandle fileHandle, UnixFileMode mode) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs index d507cc4d5ccb5..56f5c545b0ef6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs @@ -116,7 +116,7 @@ public int BufferSize /// /// Unix file mode used when a new file is created. /// - /// When contains an invalid value. + /// When is an invalid file mode. public UnixFileMode? UnixCreateMode { get diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs index 8f0cf0cc28d13..b5ddf7282c72b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs @@ -133,13 +133,18 @@ public string? LinkTarget /// Gets or sets the Unix file mode for the current file or directory. /// of the current . + /// The caller attempts to set an invalid file mode. + /// The caller does not have the required permission. + /// The specified path exceeds the system-defined maximum length. + /// The specified path is invalid. Only thrown when setting the property value. /// The specified file doesn't exist. Only thrown when setting the property value. - /// The specified path is invalid. For example, it's on an unmapped drive. Only thrown when setting the property value. - /// The caller doesn't have the required permission. - /// The user attempts to set an attribute value but doesn't have write permission. - /// The specified path, file name, or both exceed the system-defined maximum length. - /// The caller attempts to set an invalid mode. /// cannot initialize the data. + /// + /// + /// The value may be cached when either the value itself or other properties are accessed. To get the latest value, call the method. + /// + /// If the path doesn't exist as of the last cached state, the return value is `(UnixFileMode)(-1)`. or can only be thrown when setting the value. + /// public UnixFileMode UnixFileMode { get => UnixFileModeCore; From 8f8ff2d230029817e1a3df9c82e34317be298fe4 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Fri, 17 Jun 2022 15:06:23 +0200 Subject: [PATCH 32/42] Add comment to test. --- .../System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs b/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs index aab50ac7e65da..e58a3f89fe25f 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs @@ -48,7 +48,7 @@ public void SymLinksAreReparsePoints() } [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] - public void SymLinksReflectTargetReadOnly() + public void SymLinksReflectSymLinkAttributes() { string path = CreateItem(); string linkPath = GetRandomLinkPath(); @@ -65,6 +65,7 @@ public void SymLinksReflectTargetReadOnly() } else { + // On Unix, Get/SetAttributes FileAttributes.ReadOnly operates on the target of the link. Assert.Equal(FileAttributes.ReadOnly, FileAttributes.ReadOnly & GetAttributes(linkPath)); } } From 97df035bd9f34698b18962e1ae75761968b521d9 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Sat, 18 Jun 2022 08:37:07 +0200 Subject: [PATCH 33/42] GetUnixFileMode for handle won't throw UnauthorizedAccessException. --- src/libraries/System.Private.CoreLib/src/System/IO/File.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index 5ae7b7a60122b..9c8916f8090f2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -236,7 +236,6 @@ public static UnixFileMode GetUnixFileMode(string path) /// Gets the of the specified file handle. /// The file handle. /// The of the file handle. - /// The caller does not have the required permission. /// The file is closed. [UnsupportedOSPlatform("windows")] public static UnixFileMode GetUnixFileMode(SafeFileHandle fileHandle) From 7bfa541a4eb5bd82b42616a61f2472d60f5144bd Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 21 Jun 2022 08:21:34 +0200 Subject: [PATCH 34/42] Apply suggestions from code review Co-authored-by: Eric Erhardt --- .../System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs index 32ab84548d763..f82250ea62a5d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs @@ -57,7 +57,7 @@ private bool HasReadOnlyFlag } #if TARGET_BROWSER - var mode = (UnixFileMode)(_fileCache.Mode & (int)FileSystem.ValidUnixFileModes); + var mode = ((UnixFileMode)_fileCache.Mode & FileSystem.ValidUnixFileModes); bool isUserReadOnly = (mode & UnixFileMode.UserRead) != 0 && // has read permission (mode & UnixFileMode.UserWrite) == 0; // but not write permission return isUserReadOnly; @@ -87,7 +87,7 @@ private bool HasReadOnlyFlag private bool IsModeReadOnlyCore() { - var mode = (UnixFileMode)(_fileCache.Mode & (int)FileSystem.ValidUnixFileModes); + var mode = ((UnixFileMode)_fileCache.Mode & FileSystem.ValidUnixFileModes); bool isUserReadOnly = (mode & UnixFileMode.UserRead) != 0 && // has read permission (mode & UnixFileMode.UserWrite) == 0; // but not write permission From 01f6ff39fb4b2c8eaaed7593da196447d10e1d28 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 21 Jun 2022 08:21:54 +0200 Subject: [PATCH 35/42] PR feedback. --- .../src/System/Diagnostics/Process.Unix.cs | 6 +++--- .../src/System/IO/FileStatus.Unix.cs | 15 ++++----------- .../src/System/IO/FileSystemInfo.Unix.cs | 4 ++-- .../OpenSslDirectoryBasedStoreProvider.cs | 14 +++++++------- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs index 87c4113f1f4d6..2593cd5e3e568 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs @@ -763,12 +763,12 @@ private static bool IsExecutable(string fullPath) return false; } - const UnixFileMode allExecute = UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; + const UnixFileMode AllExecute = UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; - UnixFileMode permissions = ((UnixFileMode)fileinfo.Mode) & allExecute; + UnixFileMode permissions = ((UnixFileMode)fileinfo.Mode) & AllExecute; // Avoid checking user/group when permission. - if (permissions == allExecute) + if (permissions == AllExecute) { return true; } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs index f82250ea62a5d..0621f3f82cf6e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs @@ -18,7 +18,7 @@ internal partial struct FileStatus private const int Uninitialized = 0; // uninitialized, '0' to make default(FileStatus) uninitialized. // Tracks the initialization state. - // < 0 : initialized succesfully. Value is InitializedNotExists, InitializedExistsFile or InitializedExistsDir. + // < 0 : initialized succesfully. Value is InitializedNotExists, InitializedExistsFile, InitializedExistsDir or InitializedExistsBrokenLink. // 0 : uninitialized. // > 0 : initialized with error. Value is raw errno. private int _state; @@ -463,16 +463,9 @@ internal void RefreshCaches(SafeFileHandle? handle, ReadOnlySpan path) #if !TARGET_BROWSER _isReadOnlyCache = -1; #endif - int rv; - if (handle is not null) - { - rv = Interop.Sys.FStat(handle, out _fileCache); - } - else - { - path = Path.TrimEndingDirectorySeparator(path); - rv = Interop.Sys.LStat(path, out _fileCache); - } + int rv = handle is not null ? + Interop.Sys.FStat(handle, out _fileCache) : + Interop.Sys.LStat(Path.TrimEndingDirectorySeparator(path), out _fileCache); if (rv < 0) { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs index 101beec05211a..a1113a1fd10e8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.Unix.cs @@ -75,7 +75,7 @@ public void Refresh() _fileStatus.RefreshCaches(FullPath); } - internal static void ThrowNotFound(string? path) + internal static void ThrowNotFound(string path) { // 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 @@ -85,7 +85,7 @@ internal static void ThrowNotFound(string? path) // being manipulated concurrently with these checks) is that we throw a // FileNotFoundException instead of DirectoryNotFoundException. - bool directoryError = path is not null && !FileSystem.DirectoryExists(Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(path.AsSpan()))); + bool directoryError = !Directory.Exists(Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(path))); throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(Interop.Error.ENOENT), path, directoryError); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslDirectoryBasedStoreProvider.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslDirectoryBasedStoreProvider.cs index ba268f10e8e6e..6ba9d59abcf4e 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslDirectoryBasedStoreProvider.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslDirectoryBasedStoreProvider.cs @@ -372,10 +372,10 @@ private static void EnsureDirectoryPermissions(string path, uint userId) private static void EnsureFilePermissions(FileStream stream, uint userId) { // Verify that we're creating files with u+rw and g-rw, o-rw. - const UnixFileMode requiredPermissions = + const UnixFileMode RequiredPermissions = UnixFileMode.UserRead | UnixFileMode.UserWrite; - const UnixFileMode forbiddenPermissions = + const UnixFileMode ForbiddenPermissions = UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.OtherRead | UnixFileMode.OtherWrite; @@ -393,10 +393,10 @@ private static void EnsureFilePermissions(FileStream stream, uint userId) throw new CryptographicException(SR.Format(SR.Cryptography_OwnerNotCurrentUser, stream.Name)); } - if ((stat.Mode & (int)requiredPermissions) != (int)requiredPermissions || - (stat.Mode & (int)forbiddenPermissions) != 0) + if ((stat.Mode & (int)RequiredPermissions) != (int)RequiredPermissions || + (stat.Mode & (int)ForbiddenPermissions) != 0) { - if (Interop.Sys.FChMod(stream.SafeFileHandle, (int)requiredPermissions) < 0) + if (Interop.Sys.FChMod(stream.SafeFileHandle, (int)RequiredPermissions) < 0) { Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); throw new CryptographicException( @@ -413,8 +413,8 @@ private static void EnsureFilePermissions(FileStream stream, uint userId) new IOException(error.GetErrorMessage(), error.RawErrno)); } - if ((stat.Mode & (int)requiredPermissions) != (int)requiredPermissions || - (stat.Mode & (int)forbiddenPermissions) != 0) + if ((stat.Mode & (int)RequiredPermissions) != (int)RequiredPermissions || + (stat.Mode & (int)ForbiddenPermissions) != 0) { throw new CryptographicException(SR.Format(SR.Cryptography_InvalidFilePermissions, stream.Name)); } From 6e91cf12addfcc524ceda8697aa911cf7a34aab3 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 21 Jun 2022 09:42:48 +0200 Subject: [PATCH 36/42] Update enum doc to say 'owner' instead of 'user'. --- .../System.Private.CoreLib/src/System/IO/UnixFileMode.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/UnixFileMode.cs b/src/libraries/System.Private.CoreLib/src/System/IO/UnixFileMode.cs index 2454ef8b96516..457f1cff529c5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/UnixFileMode.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/UnixFileMode.cs @@ -39,15 +39,15 @@ public enum UnixFileMode /// GroupRead = 32, /// - /// Execute permission for user. + /// Execute permission for owner. /// UserExecute = 64, /// - /// Write permission for user. + /// Write permission for owner. /// UserWrite = 128, /// - /// Read permission for user. + /// Read permission for owner. /// UserRead = 256, /// From 4cb6316af772b92691d1a6add5a9e8ae43a7afd9 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 21 Jun 2022 10:24:04 +0200 Subject: [PATCH 37/42] Use UnixFileMode in library. --- .../src/System/Formats/Tar/TarEntry.Unix.cs | 5 +- ...Extensions.ZipArchiveEntry.Extract.Unix.cs | 4 +- .../OpenSslDirectoryBasedStoreProvider.cs | 95 ++++++------------- 3 files changed, 35 insertions(+), 69 deletions(-) diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Unix.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Unix.cs index bd492019fbc53..994427747c2ec 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Unix.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Unix.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using Microsoft.Win32.SafeHandles; +using System.IO; namespace System.Formats.Tar { @@ -53,7 +54,9 @@ partial void SetModeOnFile(SafeFileHandle handle, string destinationFileName) // If the permissions weren't set at all, don't write the file's permissions. if (permissions != 0) { - Interop.CheckIo(Interop.Sys.FChMod(handle, permissions), destinationFileName); +#pragma warning disable CA1416 // Validate platform compatibility + File.SetUnixFileMode(handle, (UnixFileMode)permissions); +#pragma warning disable CA1416 } } } diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs index 4da8b87b43ea3..1b2d2b365a80c 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Unix.cs @@ -22,7 +22,9 @@ static partial void ExtractExternalAttributes(FileStream fs, ZipArchiveEntry ent // include the permissions, or was made on Windows. if (permissions != 0) { - Interop.CheckIo(Interop.Sys.FChMod(fs.SafeFileHandle, permissions), fs.Name); +#pragma warning disable CA1416 // Validate platform compatibility + File.SetUnixFileMode(fs.SafeFileHandle, (UnixFileMode)permissions); +#pragma warning restore CA1416 } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslDirectoryBasedStoreProvider.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslDirectoryBasedStoreProvider.cs index 6ba9d59abcf4e..1029e7f7bfd03 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslDirectoryBasedStoreProvider.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslDirectoryBasedStoreProvider.cs @@ -170,13 +170,28 @@ private void AddCertToStore(ICertificatePal certPal) } } + const UnixFileMode UserReadWrite = UnixFileMode.UserRead | UnixFileMode.UserWrite; + string destinationFilename; - FileMode mode = FileMode.CreateNew; + FileStreamOptions options = new() + { + Mode = FileMode.CreateNew, + UnixCreateMode = UserReadWrite + }; if (existingFilename != null) { destinationFilename = existingFilename; - mode = FileMode.Create; + options.Mode = FileMode.Create; + + // Before we open the file for writing the certificate, + // ensure it is only accessible to the owner. + try + { + File.SetUnixFileMode(existingFilename, UserReadWrite); + } + catch (IOException) // Ignore errors. We verify permissions when we've opened the file. + { } } else if (findOpenSlot) { @@ -187,9 +202,15 @@ private void AddCertToStore(ICertificatePal certPal) destinationFilename = Path.Combine(_storePath, thumbprint + PfxExtension); } - using (FileStream stream = new FileStream(destinationFilename, mode)) + using (FileStream stream = new FileStream(destinationFilename, options)) { - EnsureFilePermissions(stream, userId); + // Verify the file can only be read/written to by the owner. + UnixFileMode actualMode = File.GetUnixFileMode(stream.SafeFileHandle); + if (actualMode != UserReadWrite) + { + throw new CryptographicException(SR.Format(SR.Cryptography_InvalidFilePermissions, stream.Name)); + } + byte[] pkcs12 = copy.Export(X509ContentType.Pkcs12)!; stream.Write(pkcs12, 0, pkcs12.Length); } @@ -353,74 +374,14 @@ private static void EnsureDirectoryPermissions(string path, uint userId) throw new CryptographicException(SR.Format(SR.Cryptography_OwnerNotCurrentUser, path)); } - const int userReadWriteExecute = (int)(UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); - if ((dirStat.Mode & userReadWriteExecute) != userReadWriteExecute) + const UnixFileMode UserReadWriteExecute = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute; + UnixFileMode permissions = File.GetUnixFileMode(path); + if ((permissions & UserReadWriteExecute) != UserReadWriteExecute) { throw new CryptographicException(SR.Format(SR.Cryptography_InvalidDirectoryPermissions, path)); } } - /// - /// Checks the file has the correct permissions and attempts to modify them if they're inappropriate. - /// - /// - /// The file stream to check. - /// - /// - /// The current userId from GetEUid(). - /// - private static void EnsureFilePermissions(FileStream stream, uint userId) - { - // Verify that we're creating files with u+rw and g-rw, o-rw. - const UnixFileMode RequiredPermissions = - UnixFileMode.UserRead | UnixFileMode.UserWrite; - - const UnixFileMode ForbiddenPermissions = - UnixFileMode.GroupRead | UnixFileMode.GroupWrite | - UnixFileMode.OtherRead | UnixFileMode.OtherWrite; - - Interop.Sys.FileStatus stat; - if (Interop.Sys.FStat(stream.SafeFileHandle, out stat) != 0) - { - Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); - throw new CryptographicException( - SR.Cryptography_FileStatusError, - new IOException(error.GetErrorMessage(), error.RawErrno)); - } - - if (stat.Uid != userId) - { - throw new CryptographicException(SR.Format(SR.Cryptography_OwnerNotCurrentUser, stream.Name)); - } - - if ((stat.Mode & (int)RequiredPermissions) != (int)RequiredPermissions || - (stat.Mode & (int)ForbiddenPermissions) != 0) - { - if (Interop.Sys.FChMod(stream.SafeFileHandle, (int)RequiredPermissions) < 0) - { - Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); - throw new CryptographicException( - SR.Format(SR.Cryptography_InvalidFilePermissions, stream.Name), - new IOException(error.GetErrorMessage(), error.RawErrno)); - } - - // Verify the chmod applied. - if (Interop.Sys.FStat(stream.SafeFileHandle, out stat) != 0) - { - Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); - throw new CryptographicException( - SR.Cryptography_FileStatusError, - new IOException(error.GetErrorMessage(), error.RawErrno)); - } - - if ((stat.Mode & (int)RequiredPermissions) != (int)RequiredPermissions || - (stat.Mode & (int)ForbiddenPermissions) != 0) - { - throw new CryptographicException(SR.Format(SR.Cryptography_InvalidFilePermissions, stream.Name)); - } - } - } - internal static IStorePal OpenDisallowedStore(OpenFlags openFlags) { string storePath = GetStorePath(X509Store.DisallowedStoreName); From 7046ea9d3af31386e9dd526180f9dfad9e58fc9e Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 21 Jun 2022 11:02:12 +0200 Subject: [PATCH 38/42] Use UnixFileMode in library tests. --- .../tests/ProcessTests.Unix.cs | 22 ++++++++----------- .../tests/ProcessTests.Windows.cs | 3 --- .../tests/ProcessTests.cs | 2 +- ...Cryptography.X509Certificates.Tests.csproj | 2 -- .../tests/X509StoreTests.Unix.cs | 2 +- 5 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs index 1039f7a4c2427..b06a6335ec3dd 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs @@ -22,6 +22,10 @@ public partial class ProcessTests : ProcessTestBase { private static bool IsRemoteExecutorSupportedAndOnUnixAndSuperUser => RemoteExecutor.IsSupported && PlatformDetection.IsUnixAndSuperUser; + const UnixFileMode ExecutablePermissions = UnixFileMode.UserRead | UnixFileMode.UserExecute | UnixFileMode.UserWrite | + UnixFileMode.GroupRead | UnixFileMode.GroupExecute | + UnixFileMode.GroupRead | UnixFileMode.GroupExecute; + [Fact] private void TestWindowApisUnix() { @@ -167,7 +171,7 @@ public void ProcessNameMatchesScriptName() string scriptName = GetTestFileName(); string filename = Path.Combine(TestDirectory, scriptName); File.WriteAllText(filename, $"#!/bin/sh\nsleep 600\n"); // sleep 10 min. - ChMod(filename, "744"); // set x-bit + File.SetUnixFileMode(filename, ExecutablePermissions); using (var process = Process.Start(new ProcessStartInfo { FileName = filename })) { @@ -235,7 +239,7 @@ public void ProcessStart_UseShellExecute_OnUnix_FallsBackWhenNotRealExecutable() // Create a file that has the x-bit set, but which isn't a valid script. string filename = WriteScriptFile(TestDirectory, GetTestFileName(), returnValue: 0); File.WriteAllText(filename, $"not a script"); - ChMod(filename, "744"); // set x-bit + File.SetUnixFileMode(filename, ExecutablePermissions); RemoteInvokeOptions options = new RemoteInvokeOptions(); options.StartInfo.EnvironmentVariables["PATH"] = path; @@ -508,7 +512,7 @@ public void TestStartOnUnixWithBadPermissions() { string path = GetTestFilePath(); File.Create(path).Dispose(); - ChMod(path, "644"); + File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.UserWrite); Win32Exception e = Assert.Throws(() => Process.Start(path)); Assert.NotEqual(0, e.NativeErrorCode); @@ -520,7 +524,7 @@ public void TestStartOnUnixWithBadFormat() { string path = GetTestFilePath(); File.Create(path).Dispose(); - ChMod(path, "744"); // set x-bit + File.SetUnixFileMode(path, ExecutablePermissions); Win32Exception e = Assert.Throws(() => Process.Start(path)); Assert.NotEqual(0, e.NativeErrorCode); @@ -936,14 +940,6 @@ private static int GetWaitStateReferenceCount(object waitState) return (int)referenCountField.GetValue(waitState); } - [DllImport("libc")] - private static extern int chmod(string path, int mode); - - private static void ChMod(string filename, string mode) - { - Assert.Equal(0, chmod(filename, Convert.ToInt32(mode, 8))); - } - [DllImport("libc")] private static extern uint geteuid(); [DllImport("libc")] @@ -1001,7 +997,7 @@ private string WriteScriptFile(string directory, string name, int returnValue) { string filename = Path.Combine(directory, name); File.WriteAllText(filename, $"#!/bin/sh\nexit {returnValue}\n"); - ChMod(filename, "744"); // set x-bit + File.SetUnixFileMode(filename, ExecutablePermissions); return filename; } diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Windows.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Windows.cs index 5a8a0dc448bba..6fb91c0f4e113 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Windows.cs @@ -15,8 +15,5 @@ private string WriteScriptFile(string directory, string name, int returnValue) File.WriteAllText(filename, $"exit {returnValue}"); return filename; } - - private static void ChMod(string filename, string mode) - => throw new PlatformNotSupportedException(); } } diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs index 3d6500fc7eb07..bcf3e6f8e2dd0 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs @@ -2193,7 +2193,7 @@ public void LongProcessNamesAreSupported() // Instead of using sleep directly, we wrap it with a script. sleepPath = GetTestFilePath(); File.WriteAllText(sleepPath, $"#!/bin/sh\nsleep 600\n"); // sleep 10 min. - ChMod(sleepPath, "744"); + File.SetUnixFileMode(sleepPath, ExecutablePermissions); } else { diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj b/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj index f19482755f041..7bb25bc54147f 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj @@ -101,8 +101,6 @@ Link="Common\Interop\Unix\Interop.GetEUid.cs" /> - Date: Tue, 21 Jun 2022 12:02:13 +0200 Subject: [PATCH 39/42] Fix Windows build. --- .../System.Diagnostics.Process/tests/ProcessTests.Unix.cs | 4 ---- .../System.Diagnostics.Process/tests/ProcessTests.cs | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs index b06a6335ec3dd..3e5c4c2450b8e 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs @@ -22,10 +22,6 @@ public partial class ProcessTests : ProcessTestBase { private static bool IsRemoteExecutorSupportedAndOnUnixAndSuperUser => RemoteExecutor.IsSupported && PlatformDetection.IsUnixAndSuperUser; - const UnixFileMode ExecutablePermissions = UnixFileMode.UserRead | UnixFileMode.UserExecute | UnixFileMode.UserWrite | - UnixFileMode.GroupRead | UnixFileMode.GroupExecute | - UnixFileMode.GroupRead | UnixFileMode.GroupExecute; - [Fact] private void TestWindowApisUnix() { diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs index bcf3e6f8e2dd0..6a6ffef0d7c57 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs @@ -23,6 +23,10 @@ namespace System.Diagnostics.Tests { public partial class ProcessTests : ProcessTestBase { + const UnixFileMode ExecutablePermissions = UnixFileMode.UserRead | UnixFileMode.UserExecute | UnixFileMode.UserWrite | + UnixFileMode.GroupRead | UnixFileMode.GroupExecute | + UnixFileMode.GroupRead | UnixFileMode.GroupExecute; + private class FinalizingProcess : Process { public static volatile bool WasFinalized; From bc7383bbb13fe595e79b065bcdebb91d92566c08 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 21 Jun 2022 14:58:48 +0200 Subject: [PATCH 40/42] Fix missing FileAccess when changing to FileStreamOptions API. --- .../X509Certificates/OpenSslDirectoryBasedStoreProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslDirectoryBasedStoreProvider.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslDirectoryBasedStoreProvider.cs index 1029e7f7bfd03..71bf0736ba3ee 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslDirectoryBasedStoreProvider.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/OpenSslDirectoryBasedStoreProvider.cs @@ -176,7 +176,8 @@ private void AddCertToStore(ICertificatePal certPal) FileStreamOptions options = new() { Mode = FileMode.CreateNew, - UnixCreateMode = UserReadWrite + UnixCreateMode = UserReadWrite, + Access = FileAccess.Write }; if (existingFilename != null) From d1f043d27d34b8ccc3d50eff5f114769ebafdbfa Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Wed, 22 Jun 2022 11:17:58 +0200 Subject: [PATCH 41/42] PR feedback. --- .../src/System/Formats/Tar/TarEntry.Unix.cs | 2 - .../tests/Base/BaseGetSetUnixFileMode.cs | 4 +- .../CreateDirectory_UnixFileMode.Unix.cs | 12 +++--- .../tests/FileStream/FileStreamOptions.cs | 10 +++-- .../tests/FileStream/ctor_options.Unix.cs | 39 +++++++++++++++---- .../src/Resources/Strings.resx | 3 ++ .../src/System/IO/FileStream.cs | 9 +++++ 7 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Unix.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Unix.cs index 994427747c2ec..73f15a4996a7c 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Unix.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Unix.cs @@ -54,9 +54,7 @@ partial void SetModeOnFile(SafeFileHandle handle, string destinationFileName) // If the permissions weren't set at all, don't write the file's permissions. if (permissions != 0) { -#pragma warning disable CA1416 // Validate platform compatibility File.SetUnixFileMode(handle, (UnixFileMode)permissions); -#pragma warning disable CA1416 } } } diff --git a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs index b8270fb3a6341..2429777390c2a 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs @@ -14,7 +14,7 @@ public abstract class BaseGetSetUnixFileMode : FileSystemTest // When false, the Get API returns (UnixFileMode)(-1) when the file doesn't exist. protected virtual bool GetThrowsWhenDoesntExist => false; - // The FileSafeHandle APIs require a readable file to open the handle. + // The SafeFileHandle APIs require a readable file to open the handle. protected virtual bool GetModeNeedsReadableFile => false; // When false, the Get API returns (UnixFileMode)(-1) when the platform is not supported (Windows). @@ -57,7 +57,7 @@ public void Get() UnixFileMode required = UnixFileMode.UserRead | UnixFileMode.UserWrite; if (IsDirectory) { - required = UnixFileMode.UserExecute; + required |= UnixFileMode.UserExecute; } Assert.True((mode & required) == required); diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs index 446976c48ffeb..602483c349144 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs @@ -19,24 +19,24 @@ public override DirectoryInfo Create(string path) public void CreateWithUnixFileMode(UnixFileMode mode) { string path = GetRandomDirPath(); - DirectoryInfo di = Directory.CreateDirectory(path, mode); + DirectoryInfo dir = Directory.CreateDirectory(path, mode); // under Linux the created directory gets mode (mode & ~umask & 01777). // under OSX, it gets (mode & ~umask & 0777). UnixFileMode platformFilter = UnixFileMode.SetGroup | UnixFileMode.SetUser | (PlatformDetection.IsBsdLike ? UnixFileMode.StickyBit : UnixFileMode.None); UnixFileMode expectedMode = mode & ~GetUmask() & ~platformFilter; - Assert.Equal(expectedMode, di.UnixFileMode); + Assert.Equal(expectedMode, dir.UnixFileMode); } [Fact] public void CreateDoesntChangeExistingMode() { string path = GetRandomDirPath(); - DirectoryInfo di = Directory.CreateDirectory(path, AllAccess); - UnixFileMode initialMode = di.UnixFileMode; + DirectoryInfo dir = Directory.CreateDirectory(path, AllAccess); + UnixFileMode initialMode = dir.UnixFileMode; - DirectoryInfo di2 = Directory.CreateDirectory(path, UnixFileMode.UserRead); - Assert.Equal(initialMode, di2.UnixFileMode); + DirectoryInfo sameDir = Directory.CreateDirectory(path, UnixFileMode.UserRead); + Assert.Equal(initialMode, sameDir.UnixFileMode); } [Theory] diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamOptions.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamOptions.cs index fdde4a2561d5a..2ad939d9933c5 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamOptions.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamOptions.cs @@ -199,12 +199,16 @@ static void Validate(FileStream fs, string expectedPath, bool expectedAsync, boo } } - [PlatformSpecific(TestPlatforms.Windows)] [Fact] - public void UnixCreateMode_Unsupported() + public void UnixCreateModeDefaultsToNull() { Assert.Null(new FileStreamOptions().UnixCreateMode); + } + [PlatformSpecific(TestPlatforms.Windows)] + [Fact] + public void UnixCreateMode_Unsupported() + { Assert.Throws(() => new FileStreamOptions { UnixCreateMode = null }); Assert.Throws(() => new FileStreamOptions { UnixCreateMode = UnixFileMode.None }); Assert.Throws(() => new FileStreamOptions { UnixCreateMode = UnixFileMode.UserRead }); @@ -214,8 +218,6 @@ public void UnixCreateMode_Unsupported() [Fact] public void UnixCreateMode_Supported() { - Assert.Null(new FileStreamOptions().UnixCreateMode); - Assert.Null(new FileStreamOptions { UnixCreateMode = null }.UnixCreateMode); Assert.Equal(UnixFileMode.None, new FileStreamOptions { UnixCreateMode = UnixFileMode.None }.UnixCreateMode); Assert.Equal(UnixFileMode.UserRead, new FileStreamOptions { UnixCreateMode = UnixFileMode.UserRead }.UnixCreateMode); diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs index 03aa567a0b7ff..f2555faaacf93 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs @@ -9,7 +9,6 @@ namespace System.IO.Tests { public partial class FileStream_ctor_options { - [PlatformSpecific(TestPlatforms.AnyUnix)] [Theory] [MemberData(nameof(TestUnixFileModes))] public void CreateWithUnixFileMode(UnixFileMode mode) @@ -26,22 +25,48 @@ public void CreateWithUnixFileMode(UnixFileMode mode) Assert.Equal(expectedMode, actualMode); } - [PlatformSpecific(TestPlatforms.AnyUnix)] - [Fact] - public void CreateDoesntChangeExistingMode() + [Theory] + [InlineData(FileMode.Append)] + [InlineData(FileMode.Create)] + [InlineData(FileMode.OpenOrCreate)] + public void CreateDoesntChangeExistingUnixFileMode(FileMode fileMode) { // Create file as writable for user only. const UnixFileMode mode = UnixFileMode.UserWrite; string filename = GetTestFilePath(); - using FileStream fs = CreateFileStream(filename, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, mode); - fs.Dispose(); + CreateFileStream(filename, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, mode).Dispose(); // Now open with AllAccess. - using FileStream fs2 = CreateFileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, AllAccess); + using FileStream fs = CreateFileStream(filename, fileMode, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, AllAccess); UnixFileMode actualMode = File.GetUnixFileMode(filename); Assert.Equal(mode, actualMode); } + [Theory] + [InlineData(FileMode.Append, true)] + [InlineData(FileMode.Create, true)] + [InlineData(FileMode.CreateNew, true)] + [InlineData(FileMode.OpenOrCreate, true)] + [InlineData(FileMode.Truncate, false)] + [InlineData(FileMode.Open, false)] + public void UnixCreateModeThrowsForNonCreatingFileModes(FileMode fileMode, bool canSetUnixCreateMode) + { + const UnixFileMode unixMode = UnixFileMode.UserWrite; + string filename = GetTestFilePath(); + + if (canSetUnixCreateMode) + { + CreateFileStream(filename, fileMode, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, unixMode).Dispose(); + + UnixFileMode actualMode = File.GetUnixFileMode(filename); + Assert.Equal(unixMode, actualMode); + } + else + { + Assert.Throws(() => CreateFileStream(filename, fileMode, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, unixMode)); + } + } + private static long GetAllocatedSize(FileStream fileStream) { bool isOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 71c84bd4cf0df..b50fdc019c153 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -1098,6 +1098,9 @@ Preallocation size can be requested only for new files. + + UnixCreateMode can be requested only for modes that can new files. + Type of argument is not compatible with the generic comparer. diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs index 23ea5bf333303..d2b0e4e5aba83 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs @@ -200,6 +200,15 @@ public FileStream(string path, FileStreamOptions options) FileStreamHelpers.ValidateArgumentsForPreallocation(options.Mode, options.Access); } + if (options.UnixCreateMode.HasValue) + { + // Only allow UnixCreateMode for file modes that can create a new file. + if (options.Mode == FileMode.Truncate || options.Mode == FileMode.Open) + { + throw new ArgumentException(SR.Argument_InvalidUnixCreateMode, nameof(options)); + } + } + FileStreamHelpers.SerializationGuard(options.Access); _strategy = FileStreamHelpers.ChooseStrategy( From 168bdf5e5d9982065edcc198ffecd04913444aa0 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Wed, 22 Jun 2022 11:56:34 +0200 Subject: [PATCH 42/42] Fix Argument_InvalidUnixCreateMode message. Co-authored-by: Adam Sitnik --- src/libraries/System.Private.CoreLib/src/Resources/Strings.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index b50fdc019c153..82ee05dc37d3f 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -1099,7 +1099,7 @@ Preallocation size can be requested only for new files. - UnixCreateMode can be requested only for modes that can new files. + UnixCreateMode can be requested only for modes that can create new files. Type of argument is not compatible with the generic comparer.