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
Changes from 33 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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -276,8 +276,6 @@
Link="Common\Interop\Unix\System.Native\Interop.Access.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs"
Link="Common\Interop\Unix\Interop.Stat.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Permissions.cs"
Link="Common\Interop\Unix\Interop.Permissions.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetEUid.cs"
Link="Common\Interop\Unix\Interop.GetEUid.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.IsMemberOfGroup.cs"
Original file line number Diff line number Diff line change
@@ -763,10 +763,12 @@ private static bool IsExecutable(string fullPath)
return false;
}

Interop.Sys.Permissions permissions = ((Interop.Sys.Permissions)fileinfo.Mode) & Interop.Sys.Permissions.S_IXUGO;
const UnixFileMode allExecute = UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute;

UnixFileMode permissions = ((UnixFileMode)fileinfo.Mode) & allExecute;

// Avoid checking user/group when permission.
if (permissions == Interop.Sys.Permissions.S_IXUGO)
if (permissions == allExecute)
{
return true;
}
@@ -785,11 +787,11 @@ private static bool IsExecutable(string fullPath)
if (euid == fileinfo.Uid)
{
// We own the file.
return (permissions & Interop.Sys.Permissions.S_IXUSR) != 0;
return (permissions & UnixFileMode.UserExecute) != 0;
}

bool groupCanExecute = (permissions & Interop.Sys.Permissions.S_IXGRP) != 0;
bool otherCanExecute = (permissions & Interop.Sys.Permissions.S_IXOTH) != 0;
bool groupCanExecute = (permissions & UnixFileMode.GroupExecute) != 0;
bool otherCanExecute = (permissions & UnixFileMode.OtherExecute) != 0;

// Avoid group check when group and other have same permissions.
if (groupCanExecute == otherCanExecute)
Original file line number Diff line number Diff line change
@@ -59,7 +59,6 @@
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.FChMod.cs" Link="Common\Interop\Unix\System.Native\Interop.FChMod.cs" />
Copy link
Member

Choose a reason for hiding this comment

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

Interop.FChMod.cs

Can the Interop.FChMod.cs file also be deleted from this .csproj?

<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Link.cs" Link="Common\Interop\Unix\System.Native\Interop.Link.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MkFifo.cs" Link="Common\Interop\Unix\System.Native\Interop.MkFifo.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Permissions.cs" Link="Common\Interop\Unix\Interop.Permissions.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs" Link="Common\Interop\Unix\Interop.Stat.cs" />
<Compile Include="$(CommonPath)System\IO\Archiving.Utils.Unix.cs" Link="Common\System\IO\Archiving.Utils.Unix.cs" />
</ItemGroup>
Original file line number Diff line number Diff line change
@@ -59,7 +59,15 @@ 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
{
// On Unix, Get/SetAttributes FileAttributes.ReadOnly operates on the target of the link.
Assert.Equal(FileAttributes.ReadOnly, FileAttributes.ReadOnly & GetAttributes(linkPath));
}
}
finally
{
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
// 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);

// 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 (Windows).
protected virtual bool GetModeThrowsPNSE => true;

// Determines if the derived Test class is for directories or files.
protected virtual bool IsDirectory => false;

// 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);
if (IsDirectory)
{
Directory.CreateDirectory(path);
}
else
{
File.Create(path).Dispose();
}
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);

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)]
[Theory]
[MemberData(nameof(TestUnixFileModes))]
public void SetThenGet(UnixFileMode mode)
{
if (!CanSetGroup && (mode & UnixFileMode.SetGroup) != 0)
{
return; // Skip
}
if (GetModeNeedsReadableFile)
{
// Ensure the file remains readable.
mode |= UnixFileMode.UserRead;
}

string path = CreateTestItem();

SetMode(path, mode);

Assert.Equal(mode, GetMode(path));
}

[PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser)]
[Theory]
[MemberData(nameof(TestUnixFileModes))]
public void SetThenGet_SymbolicLink(UnixFileMode mode)
{
if (!CanSetGroup && (mode & UnixFileMode.SetGroup) != 0)
{
return; // Skip
}
if (GetModeNeedsReadableFile)
{
// Ensure the file remains readable.
mode |= UnixFileMode.UserRead;
}

string path = CreateTestItem();

string linkPath = GetTestFilePath();
File.CreateSymbolicLink(linkPath, path);

SetMode(linkPath, mode);

Assert.Equal(mode, GetMode(linkPath));
Assert.Equal(mode, GetMode(path));
}

[PlatformSpecific(TestPlatforms.AnyUnix)]
[Fact]
public void FileDoesntExist()
{
string path = GetTestFilePath();

if (GetThrowsWhenDoesntExist)
{
Assert.Throws<FileNotFoundException>(() => GetMode(path));
}
else
{
Assert.Equal((UnixFileMode)(-1), GetMode(path));
}
Assert.Throws<FileNotFoundException>(() => SetMode(path, AllAccess));
}

[PlatformSpecific(TestPlatforms.AnyUnix)]
[Fact]
public void FileDoesntExist_SymbolicLink()
{
string path = GetTestFilePath();
string linkPath = GetTestFilePath();
File.CreateSymbolicLink(linkPath, path);

Assert.Throws<FileNotFoundException>(() => SetMode(linkPath, AllAccess));

if (GetThrowsWhenDoesntExist)
{
Assert.Throws<FileNotFoundException>(() => GetMode(path));
}
else
{
Assert.Equal((UnixFileMode)(-1), GetMode(path));
}
}

[PlatformSpecific(TestPlatforms.AnyUnix)]
[Fact]
public void ParentDirDoesntExist()
{
string path = Path.Combine(GetTestFilePath(), "dir", "file");

if (GetThrowsWhenDoesntExist)
{
Assert.Throws<DirectoryNotFoundException>(() => GetMode(path));
}
else
{
Assert.Equal((UnixFileMode)(-1), GetMode(path));
}
Assert.Throws<DirectoryNotFoundException>(() => SetMode(path, AllAccess));
}

[PlatformSpecific(TestPlatforms.AnyUnix)]
[Fact]
public void NullPath()
{
Assert.Throws<ArgumentNullException>(() => GetMode(null));
Assert.Throws<ArgumentNullException>(() => SetMode(null, AllAccess));
}

[PlatformSpecific(TestPlatforms.AnyUnix)]
[Fact]
public void InvalidPath()
{
Assert.Throws<ArgumentException>(() => GetMode(string.Empty));
Assert.Throws<ArgumentException>(() => SetMode(string.Empty, AllAccess));
}

[PlatformSpecific(TestPlatforms.AnyUnix)]
[Theory]
[InlineData((UnixFileMode)(1 << 12))]
public void InvalidMode(UnixFileMode mode)
{
string path = CreateTestItem();

Assert.Throws<ArgumentException>(() => SetMode(path, mode));
}

[PlatformSpecific(TestPlatforms.Windows)]
[Fact]
public void Unsupported()
{
string path = CreateTestItem();

Assert.Throws<PlatformNotSupportedException>(() => SetMode(path, AllAccess));

if (GetModeThrowsPNSE)
{
Assert.Throws<PlatformNotSupportedException>(() => GetMode(path));
}
else
{
Assert.Equal((UnixFileMode)(-1), GetMode(path));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 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).
// 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);
}

[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);
}

[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<ArgumentException>(() => Directory.CreateDirectory(path, mode));
}
}
}
Original file line number Diff line number Diff line change
@@ -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 : FileSystemTest
{
[Fact]
public void NotSupported()
{
string path = GetRandomDirPath();
Assert.Throws<PlatformNotSupportedException>(() => Directory.CreateDirectory(path, UnixFileMode.UserRead));
}
}
}
Loading