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" />
-
(() => Process.Start(path));
Assert.NotEqual(0, e.NativeErrorCode);
@@ -520,7 +520,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 +936,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 +993,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..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;
@@ -2193,7 +2197,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.Formats.Tar/src/System.Formats.Tar.csproj b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj
index 867940f737573..f6f39c8cd9853 100644
--- a/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj
+++ b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj
@@ -57,13 +57,11 @@
-
-
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..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
@@ -3,6 +3,7 @@
using System.Diagnostics;
using Microsoft.Win32.SafeHandles;
+using System.IO;
namespace System.Formats.Tar
{
@@ -53,7 +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)
{
- Interop.CheckIo(Interop.Sys.FChMod(handle, permissions), destinationFileName);
+ File.SetUnixFileMode(handle, (UnixFileMode)permissions);
}
}
}
diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj b/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj
index f00f15bf5a445..3287498e587c1 100644
--- a/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj
+++ b/src/libraries/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj
@@ -31,8 +31,6 @@
Link="Common\Interop\Unix\Interop.Libraries.cs" />
-
-
diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs
index 91b6b4baf5e32..2bca01e0c5c00 100644
--- a/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs
+++ b/src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Unix.cs
@@ -100,7 +100,7 @@ private static string[] CreateFiles(string folderPath, string[] testPermissions)
string filename = Path.Combine(folderPath, $"{permissions}.txt");
File.WriteAllText(filename, "contents");
- Assert.Equal(0, Interop.Sys.ChMod(filename, Convert.ToInt32(permissions, 8)));
+ File.SetUnixFileMode(filename, (UnixFileMode)Convert.ToInt32(permissions, 8));
// In some environments, the file mode may be modified by the OS.
// See the Rationale section of https://linux.die.net/man/3/chmod.
diff --git a/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs b/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs
index ab667d4ebe7e6..e58a3f89fe25f 100644
--- a/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs
+++ b/src/libraries/System.IO.FileSystem/tests/Base/AllGetSetAttributes.cs
@@ -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
{
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..2429777390c2a
--- /dev/null
+++ b/src/libraries/System.IO.FileSystem/tests/Base/BaseGetSetUnixFileMode.cs
@@ -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 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).
+ 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(() => 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);
+
+ Assert.Throws(() => SetMode(linkPath, AllAccess));
+
+ if (GetThrowsWhenDoesntExist)
+ {
+ Assert.Throws(() => 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(() => 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/Directory/CreateDirectory_UnixFileMode.Unix.cs b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs
new file mode 100644
index 0000000000000..602483c349144
--- /dev/null
+++ b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs
@@ -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 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, dir.UnixFileMode);
+ }
+
+ [Fact]
+ public void CreateDoesntChangeExistingMode()
+ {
+ string path = GetRandomDirPath();
+ DirectoryInfo dir = Directory.CreateDirectory(path, AllAccess);
+ UnixFileMode initialMode = dir.UnixFileMode;
+
+ DirectoryInfo sameDir = Directory.CreateDirectory(path, UnixFileMode.UserRead);
+ Assert.Equal(initialMode, sameDir.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/Directory/CreateDirectory_UnixFileMode.Windows.cs b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Windows.cs
new file mode 100644
index 0000000000000..d20b94f0a0d1e
--- /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 : FileSystemTest
+ {
+ [Fact]
+ public void NotSupported()
+ {
+ string path = GetRandomDirPath();
+ Assert.Throws(() => Directory.CreateDirectory(path, UnixFileMode.UserRead));
+ }
+ }
+}
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..1a0d6fd1dbbe1
--- /dev/null
+++ b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/GetSetUnixFileMode.cs
@@ -0,0 +1,20 @@
+// 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 bool GetModeThrowsPNSE => false;
+
+ protected override bool IsDirectory => true;
+
+ 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..6aaf607caea6a
--- /dev/null
+++ b/src/libraries/System.IO.FileSystem/tests/File/GetSetUnixFileMode_SafeFileHandle.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.
+
+using Microsoft.Win32.SafeHandles;
+
+namespace System.IO.Tests
+{
+ public class File_GetSetUnixFileMode_SafeFileHandle : BaseGetSetUnixFileMode
+ {
+ 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/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/GetSetUnixFileMode.cs b/src/libraries/System.IO.FileSystem/tests/FileInfo/GetSetUnixFileMode.cs
new file mode 100644
index 0000000000000..1ca1d6040a05a
--- /dev/null
+++ b/src/libraries/System.IO.FileSystem/tests/FileInfo/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 FileInfo_GetSetUnixFileMode : BaseGetSetUnixFileMode
+ {
+ protected override bool GetModeThrowsPNSE => false;
+
+ 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/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/FileStreamOptions.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamOptions.cs
index 8dd7f87b84340..2ad939d9933c5 100644
--- a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamOptions.cs
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamOptions.cs
@@ -198,5 +198,31 @@ static void Validate(FileStream fs, string expectedPath, bool expectedAsync, boo
}
}
}
+
+ [Fact]
+ 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 });
+ }
+
+ [PlatformSpecific(TestPlatforms.AnyUnix)]
+ [Fact]
+ public void UnixCreateMode_Supported()
+ {
+ 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 60a2af22d4911..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
@@ -3,11 +3,70 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
+using Xunit;
namespace System.IO.Tests
{
public partial class FileStream_ctor_options
{
+ [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);
+ }
+
+ [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();
+ CreateFileStream(filename, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0, mode).Dispose();
+
+ // Now open with 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.IO.FileSystem/tests/FileStream/ctor_options.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs
index bbae2fbb0e240..99b6d246da646 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()
{
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