Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement UnixFileMode APIs #69980

Merged
merged 42 commits into from
Jun 23, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
11c2422
Implement UnixFileMode APIs on Unix.
tmds May 30, 2022
b182b4d
Throw PNSE on Windows, add UnsupportedOSPlatform.
tmds Jun 2, 2022
f143499
Fix API compat issue.
tmds Jun 2, 2022
5138601
Borrow a few things from SafeFileHandle API PR to this compiles.
tmds Jun 2, 2022
4d6c524
Fix System.IO.FileSystem.AccessControl compilation.
tmds Jun 2, 2022
813e24a
Add xml docs.
tmds Jun 2, 2022
7bd2ae3
Replace Interop.Sys.Permissions to System.IO.UnixFileMode.
tmds Jun 3, 2022
d67d738
Throw PNSE immediately on Windows.
tmds Jun 3, 2022
3841bfb
Add ODE to xml docs of methods that accept a handle.
tmds Jun 3, 2022
d1e7ea8
Don't throw (PNSE) from FileSystemInfo.UnixFileMode getter on Windows.
tmds Jun 3, 2022
f7db626
Minor style fix.
tmds Jun 3, 2022
88828a4
Get rid of some casts.
tmds Jun 5, 2022
cd4104f
Add tests for creating a file/directory with UnixFileMode.
tmds Jun 7, 2022
c937c28
Some CI envs don't have a umask exe, try retrieving via a shell builtin.
tmds Jun 7, 2022
a12832c
Update expected test mode values.
tmds Jun 7, 2022
d294651
Fix OSX
tmds Jun 8, 2022
3df946a
Fix Windows build.
tmds Jun 8, 2022
f3c55bf
Add ArgumentException tests.
tmds Jun 8, 2022
91e0891
Fix Windows build.
tmds Jun 8, 2022
6e74d98
Add get/set tests.
tmds Jun 8, 2022
757c1e5
Update test for Windows.
tmds Jun 8, 2022
53539ec
Make setters target link instead of link target.
tmds Jun 8, 2022
873660e
Linux: fix SetUnixFileMode
tmds Jun 10, 2022
d9c7789
Fix OSX compilation.
tmds Jun 10, 2022
3dba808
Try make all tests pass in CI.
tmds Jun 10, 2022
33d3e6f
For link, operate on target permissions.
tmds Jun 17, 2022
18e70c9
Skip tests on Browser.
tmds Jun 17, 2022
b2423c6
Add tests for 'Get' that doesn't use a 'Set' first.
tmds Jun 17, 2022
777b77d
Don't perform exist check for handles.
tmds Jun 17, 2022
4e93e11
Fix Get test for wasm.
tmds Jun 17, 2022
60d24a7
Review xml comments.
tmds Jun 17, 2022
8f8ff2d
Add comment to test.
tmds Jun 17, 2022
97df035
GetUnixFileMode for handle won't throw UnauthorizedAccessException.
tmds Jun 18, 2022
7bfa541
Apply suggestions from code review
tmds Jun 21, 2022
01f6ff3
PR feedback.
tmds Jun 21, 2022
6e91cf1
Update enum doc to say 'owner' instead of 'user'.
tmds Jun 21, 2022
4cb6316
Use UnixFileMode in library.
tmds Jun 21, 2022
7046ea9
Use UnixFileMode in library tests.
tmds Jun 21, 2022
e55011d
Fix Windows build.
tmds Jun 21, 2022
bc7383b
Fix missing FileAccess when changing to FileStreamOptions API.
tmds Jun 21, 2022
d1f043d
PR feedback.
tmds Jun 22, 2022
168bdf5
Fix Argument_InvalidUnixCreateMode message.
tmds Jun 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,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 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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,
tmds marked this conversation as resolved.
Show resolved Hide resolved
Func<Interop.ErrorInfo, Interop.Sys.OpenFlags, string, Exception?>? 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<Interop.ErrorInfo, Interop.Sys.OpenFlags, string, Exception?>? createOpenException = null)
{
// Translate the arguments into arguments for an open call.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
tmds marked this conversation as resolved.
Show resolved Hide resolved
tmds marked this conversation as resolved.
Show resolved Hide resolved
}

IsAsync = (options & FileOptions.Asynchronous) != 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ 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)
{
Debug.Assert(!unixCreateMode.HasValue);
tmds marked this conversation as resolved.
Show resolved Hide resolved

using (DisableMediaInsertionPrompt.Create())
{
// we don't use NtCreateFile as there is no public and reliable way
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3088,6 +3088,9 @@
<data name="PlatformNotSupported_StrongNameSigning" xml:space="preserve">
<value>Strong-name signing is not supported on this platform.</value>
</data>
<data name="PlatformNotSupported_UnixFileMode" xml:space="preserve">
<value>Unix file modes are not supported on this platform.</value>
</data>
<data name="PlatformNotSupported_OverlappedIO" xml:space="preserve">
<value>This API is specific to the way in which Windows handles asynchronous I/O, and is not supported on this platform.</value>
</data>
Expand Down Expand Up @@ -3748,6 +3751,9 @@
<data name="Arg_InvalidFileAttrs" xml:space="preserve">
<value>Invalid File or Directory attributes value.</value>
</data>
<data name="Arg_InvalidUnixFileMode" xml:space="preserve">
<value>Invalid UnixFileMode value.</value>
</data>
<data name="Arg_Path2IsRooted" xml:space="preserve">
<value>Second path fragment must not be a drive or UNC name.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\IO\StringWriter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\TextReader.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\TextWriter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\UnixFileMode.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\UnmanagedMemoryAccessor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\UnmanagedMemoryStream.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\UnmanagedMemoryStreamWrapper.cs" />
Expand Down
13 changes: 13 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Enumeration;
using System.Runtime.Versioning;

namespace System.IO
{
Expand Down Expand Up @@ -34,6 +35,18 @@ public static DirectoryInfo CreateDirectory(string path)
return new DirectoryInfo(path, fullPath, isNormalized: true);
}

[UnsupportedOSPlatform("windows")]
public static DirectoryInfo CreateDirectory(string path, UnixFileMode unixCreateMode)
tmds marked this conversation as resolved.
Show resolved Hide resolved
{
ArgumentException.ThrowIfNullOrEmpty(path);

string fullPath = Path.GetFullPath(path);

FileSystem.CreateDirectory(fullPath, unixCreateMode);

return new DirectoryInfo(path, fullPath, isNormalized: true);
}

// Tests if the given path refers to an existing DirectoryInfo on disk.
public static bool Exists([NotNullWhen(true)] string? path)
{
Expand Down
16 changes: 16 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/IO/File.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,22 @@ 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);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these methods throw PlatformNotSupportedException on Windows, I think that it would be good to include this information in the docs.

/// <exception cref="PlatformNotSupportedException">Not supported on Windows.</exception>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it need to be added when it is marked with the UnsupportedOSPlatformAttribute?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@buyaa-n what is the guidance for such scenarios?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idk what is the guidance for documenting, though I know that the attribute would showed up on the API doc like this:
image
https://docs.microsoft.com/en-us/dotnet/api/system.console.in?view=net-6.0

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm only seeing 11 places that do this in the current codebase, but we throw PNSE in many, many other places. So I'm not sure this is strictly necessary.

public static FileStream OpenRead(string path)
=> new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace System.IO
{
Expand Down Expand Up @@ -399,6 +400,47 @@ internal long GetLength(ReadOnlySpan<char> path, bool continueOnError = false)
return EntryExists ? _fileCache.Size : 0;
}

internal UnixFileMode GetUnixFileMode(ReadOnlySpan<char> 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<char> path, bool continueOnError = false)
{
EnsureCachesInitialized(handle, path, continueOnError);

if (!EntryExists)
return (UnixFileMode)(-1);
tmds marked this conversation as resolved.
Show resolved Hide resolved

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)
{
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<char> path)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
tmds marked this conversation as resolved.
Show resolved Hide resolved
}

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.")]
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,6 +13,7 @@ public sealed class FileStreamOptions
private FileOptions _options;
private long _preallocationSize;
private int _bufferSize = FileStream.DefaultBufferSize;
private UnixFileMode? _unixCreateMode;

/// <summary>
/// One of the enumeration values that determines how to open or create the file.
Expand Down Expand Up @@ -109,5 +112,31 @@ public int BufferSize
get => _bufferSize;
set => _bufferSize = value >= 0 ? value : throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
}

/// <summary>
/// Unix file mode used when a new file is created.
/// </summary>
public UnixFileMode? UnixCreateMode
tmds marked this conversation as resolved.
Show resolved Hide resolved
{
get
{
return _unixCreateMode;
}
[UnsupportedOSPlatform("windows")]
set
{
if (OperatingSystem.IsWindows())
{
throw new PlatformNotSupportedException(SR.PlatformNotSupported_UnixFileMode);
}

if (value.HasValue && ((value & ~FileSystem.ValidUnixFileModes) != 0))
{
ThrowHelper.ArgumentOutOfRangeException_Enum_Value();
}

_unixCreateMode = value;
}
}
}
}
Loading