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 6 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 @@ -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 Expand Up @@ -2007,6 +2008,9 @@
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.UnixFileSystemTypes.cs">
<Link>Common\Interop\Unix\System.Native\Interop.UnixFileSystemTypes.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.FChMod.cs">
<Link>Common\Interop\Unix\System.Native\Interop.FChMod.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.FLock.cs">
<Link>Common\Interop\Unix\System.Native\Interop.FLock.cs</Link>
</Compile>
Expand Down
36 changes: 36 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,41 @@ public static DirectoryInfo CreateDirectory(string path)
return new DirectoryInfo(path, fullPath, isNormalized: true);
}

/// <summary>
/// Creates all directories and subdirectories in the specified path with the specified permissions unless they already exist.
/// </summary>
/// <param name="path">The directory to create.</param>
/// <param name="unixCreateMode">Unix file mode used to create directories.</param>
/// <returns>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.</returns>
/// <exception cref="T:System.IO.IOException">The directory specified by <paramref name="path" /> is a file.
/// -or-
/// The network name is not known.</exception>
/// <exception cref="T:System.UnauthorizedAccessException">The caller does not have the required permission.</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="path" /> 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 <see cref="M:System.IO.Path.GetInvalidPathChars" /> method.
/// -or-
/// <paramref name="path" /> is prefixed with, or contains, only a colon character (:).</exception>
/// <exception cref="T:System.ArgumentNullException"><paramref name="path" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.IO.PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length.</exception>
/// <exception cref="T:System.IO.DirectoryNotFoundException">The specified path is invalid (for example, it is on an unmapped drive).</exception>
/// <exception cref="T:System.NotSupportedException"><paramref name="path" /> contains a colon character (:) that is not part of a drive label ("C:\\").</exception>
/// <exception cref="T:System.ArgumentException">The caller attempts use an invalid file mode.</exception>
tmds marked this conversation as resolved.
Show resolved Hide resolved
[UnsupportedOSPlatform("windows")]
public static DirectoryInfo CreateDirectory(string path, UnixFileMode unixCreateMode)
tmds marked this conversation as resolved.
Show resolved Hide resolved
{
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);
}

// Tests if the given path refers to an existing DirectoryInfo on disk.
public static bool Exists([NotNullWhen(true)] string? path)
{
Expand Down
56 changes: 56 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,62 @@ public static FileAttributes GetAttributes(string path)
public static void SetAttributes(string path, FileAttributes fileAttributes)
=> FileSystem.SetAttributes(Path.GetFullPath(path), fileAttributes);

/// <summary>Gets the <see cref="T:System.IO.UnixFileMode" /> of the file on the path.</summary>
/// <param name="path">The path to the file.</param>
/// <returns>The <see cref="T:System.IO.UnixFileMode" /> of the file on the path.</returns>
[UnsupportedOSPlatform("windows")]
public static UnixFileMode GetUnixFileMode(string path)
=> FileSystem.GetUnixFileMode(Path.GetFullPath(path));

/// <summary>Gets the <see cref="T:System.IO.UnixFileMode" /> of the specified file handle.</summary>
/// <param name="fileHandle">The file handle.</param>
/// <returns>The <see cref="T:System.IO.UnixFileMode" /> of the file handle.</returns>
/// <exception cref="T:System.UnauthorizedAccessException">The caller does not have the required permission.</exception>
tmds marked this conversation as resolved.
Show resolved Hide resolved
[UnsupportedOSPlatform("windows")]
public static UnixFileMode GetUnixFileMode(SafeFileHandle fileHandle)
=> FileSystem.GetUnixFileMode(fileHandle);

/// <summary>Sets the specified <see cref="T:System.IO.UnixFileMode" /> of the file on the specified path.</summary>
/// <param name="path">The path to the file.</param>
/// <param name="mode">The unix file mode.</param>
/// <exception cref="T:System.ArgumentException"><paramref name="path" /> is empty, contains only white spaces, contains invalid characters, or the file mode is invalid.</exception>
/// <exception cref="T:System.IO.PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length. </exception>
/// <exception cref="T:System.NotSupportedException"><paramref name="path" /> is in an invalid format.</exception>
/// <exception cref="T:System.IO.DirectoryNotFoundException">The specified path is invalid, (for example, it is on an unmapped drive).</exception>
/// <exception cref="T:System.IO.FileNotFoundException">The file cannot be found.</exception>
/// <exception cref="T:System.UnauthorizedAccessException"><paramref name="path" /> specified a file that is read-only.
/// -or-
/// This operation is not supported on the current platform.
/// -or-
/// <paramref name="path" /> specified a directory.
/// -or-
/// The caller does not have the required permission.</exception>
[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);
}

/// <summary>Sets the specified <see cref="T:System.IO.UnixFileMode" /> of the specified file handle.</summary>
/// <param name="fileHandle">The file handle.</param>
/// <param name="mode">The unix file mode.</param>
/// <exception cref="T:System.ArgumentException">The file mode is invalid.</exception>
[UnsupportedOSPlatform("windows")]
public static void SetUnixFileMode(SafeFileHandle fileHandle, UnixFileMode mode)
{
if ((mode & ~FileSystem.ValidUnixFileModes) != 0)
{
ThrowHelper.ArgumentOutOfRangeException_Enum_Value();
}

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
Contributor

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
Loading