Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4b6ca3e
Add File.Open(FileStreamOptions), FileStreamOptions, and UnixFileMode…
Copilot Apr 16, 2026
d8d1bc9
Add File.GetUnixFileMode, File.SetUnixFileMode, Directory.CreateDirec…
Copilot Apr 16, 2026
1547be1
Use SkippableFact instead of #if PLATFORM_WINDOWS guards in Unix-only…
Copilot Apr 16, 2026
ff5342c
Dispose stream on throw, use field keyword, extract NativeMethods class
Copilot Apr 16, 2026
be9f4f8
Extract NativeMethods in File.cs, add UnsupportedOSPlatform, remove r…
Copilot Apr 16, 2026
c4a7d04
Update FileTests.cs
Tyrrrz Apr 16, 2026
3c3ec1a
Update DirectoryTests.cs
Tyrrrz Apr 16, 2026
12336bf
Update FileTests.cs
Tyrrrz Apr 16, 2026
238bbe1
Update File.cs
Tyrrrz Apr 16, 2026
73ede09
Make BufferSize and PreallocationSize setters arrow-bodied one-liners
Copilot Apr 16, 2026
d16a0ad
Add [UnsupportedOSPlatform("windows")] to Directory.CreateDirectory(p…
Copilot Apr 16, 2026
3c28e53
Potential fix for pull request finding
Tyrrrz Apr 16, 2026
16a5da0
Fix File.Open: only apply UnixCreateMode when file is newly created
Copilot Apr 16, 2026
20e99db
Update File.cs
Tyrrrz Apr 16, 2026
b494632
Simplify File.Open pre-check: existed = File.Exists(path)
Copilot Apr 16, 2026
7b422eb
Update Directory.cs
Tyrrrz Apr 16, 2026
9538da5
Update File.cs
Tyrrrz Apr 16, 2026
ad182b9
Potential fix for pull request finding
Tyrrrz Apr 16, 2026
8a0bf70
Fix formatting in Net60/File.cs: move class inside NETSTANDARD guard
Copilot Apr 16, 2026
92e84b3
Update File.cs
Tyrrrz Apr 16, 2026
1160e48
Apply umask-respecting chmod in File.Open UnixCreateMode handling
Copilot Apr 16, 2026
4f08d31
Merge branch 'prime' into copilot/add-polyfill-file-open-methods
Tyrrrz Apr 16, 2026
64ee3ef
Move FileStreamOptions.UnixCreateMode to Net70 extension member (poly…
Copilot Apr 16, 2026
76ce4f5
Potential fix for pull request finding
Tyrrrz Apr 16, 2026
79aaeb1
Update File.cs
Tyrrrz Apr 16, 2026
6827c94
Update Directory.cs
Tyrrrz Apr 16, 2026
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
128 changes: 128 additions & 0 deletions PolyShim.Tests/Net60/FileTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using System;
using System.IO;
using System.Runtime.Versioning;
using FluentAssertions;
using PolyShim.Tests.Utils.Extensions;
using Xunit;

namespace PolyShim.Tests.Net60;

public class FileTests
{
[Fact]
public void Open_Test()
{
// Arrange
var tempFilePath = Path.GetTempFileName();
File.WriteAllBytes(tempFilePath, [0x00, 0x01, 0x02]);

try
{
var options = new FileStreamOptions();

// Act
using var stream = File.Open(tempFilePath, options);

// Assert
stream.CanRead.Should().BeTrue();
stream.Length.Should().Be(3);
}
finally
{
File.TryDelete(tempFilePath);
}
}

[Fact]
public void Open_Write_Test()
{
// Arrange
var tempFilePath = Path.GetTempFileName();

try
{
var options = new FileStreamOptions
{
Mode = FileMode.Create,
Access = FileAccess.Write,
Share = FileShare.None,
};

// Act
using var stream = File.Open(tempFilePath, options);
stream.Write([0x0A, 0x0B, 0x0C], 0, 3);

// Assert
stream.CanWrite.Should().BeTrue();
stream.Position.Should().Be(3);
}
finally
{
File.TryDelete(tempFilePath);
}
}

[Fact]
public void Open_ReadWrite_Test()
{
// Arrange
var tempFilePath = Path.GetTempFileName();
File.WriteAllBytes(tempFilePath, [0x00, 0x01, 0x02]);

try
{
var options = new FileStreamOptions
{
Mode = FileMode.Open,
Access = FileAccess.ReadWrite,
Share = FileShare.None,
};

// Act
using var stream = File.Open(tempFilePath, options);

// Assert
stream.CanRead.Should().BeTrue();
stream.CanWrite.Should().BeTrue();
}
finally
{
File.TryDelete(tempFilePath);
}
}

[SkippableFact]
[UnsupportedOSPlatform("windows")]
public void Open_UnixFileMode_Test()
{
Skip.If(OperatingSystem.IsWindows());

// Arrange
var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

try
{
var expectedMode = UnixFileMode.UserRead | UnixFileMode.UserWrite;

var options = new FileStreamOptions
{
Mode = FileMode.Create,
Access = FileAccess.Write,
Share = FileShare.None,
UnixCreateMode = expectedMode,
};

// Act
using (var stream = File.Open(tempFilePath, options))
stream.Write([0x0A, 0x0B], 0, 2);

// Assert
var actualMode = File.GetUnixFileMode(tempFilePath);
actualMode.Should().Be(expectedMode);
}
Comment thread
Tyrrrz marked this conversation as resolved.
finally
{
File.TryDelete(tempFilePath);
}
}
}
39 changes: 39 additions & 0 deletions PolyShim.Tests/Net70/DirectoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.IO;
using System.Runtime.Versioning;
using FluentAssertions;
using Xunit;

namespace PolyShim.Tests.Net70;

public class DirectoryTests
{
[SkippableFact]
[UnsupportedOSPlatform("windows")]
public void CreateDirectory_UnixFileMode_Test()
{
Skip.If(OperatingSystem.IsWindows());

// Arrange
var tempDirPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

try
{
var expectedMode =
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute;

// Act
var info = Directory.CreateDirectory(tempDirPath, expectedMode);

// Assert
info.Should().NotBeNull();
info.Exists.Should().BeTrue();
File.GetUnixFileMode(tempDirPath).Should().Be(expectedMode);
}
Comment thread
Tyrrrz marked this conversation as resolved.
finally
{
if (Directory.Exists(tempDirPath))
Directory.Delete(tempDirPath);
}
}
}
28 changes: 28 additions & 0 deletions PolyShim.Tests/Net70/FileTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using FluentAssertions;
using PolyShim.Tests.Utils.Extensions;
Expand Down Expand Up @@ -32,4 +34,30 @@ public async Task ReadLinesAsync_Test()
File.TryDelete(tempFilePath);
}
}

[SkippableFact]
[UnsupportedOSPlatform("windows")]
public void SetUnixFileMode_Test()
{
Skip.If(OperatingSystem.IsWindows());

// Arrange
var tempFilePath = Path.GetTempFileName();

try
{
var expectedMode = UnixFileMode.UserRead | UnixFileMode.UserWrite;

// Act
File.SetUnixFileMode(tempFilePath, expectedMode);

// Assert
var actualMode = File.GetUnixFileMode(tempFilePath);
actualMode.Should().Be(expectedMode);
Comment thread
Tyrrrz marked this conversation as resolved.
}
finally
{
File.TryDelete(tempFilePath);
}
}
}
62 changes: 62 additions & 0 deletions PolyShim/Net60/File.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#if (NETCOREAPP && !NET6_0_OR_GREATER) || (NETFRAMEWORK) || (NETSTANDARD)
#nullable enable
#pragma warning disable CS0436

// No file I/O on .NET Standard prior to 1.3
#if !NETSTANDARD || NETSTANDARD1_3_OR_GREATER

using System;
using System.IO;
using System.Diagnostics.CodeAnalysis;

#if !POLYSHIM_INCLUDE_COVERAGE
[ExcludeFromCodeCoverage]
#endif
internal static class MemberPolyfills_Net60_File
{
extension(File)
{
// https://learn.microsoft.com/dotnet/api/system.io.file.open#system-io-file-open(system-string-system-io-filestreamoptions)
public static FileStream Open(string path, FileStreamOptions options)
{
var existed = File.Exists(path);

var stream = new FileStream(
path,
options.Mode,
options.Access,
options.Share,
options.BufferSize,
options.Options
);

try
{
#if !NETFRAMEWORK || NET40_OR_GREATER
if (
!existed
&& !OperatingSystem.IsWindows()
&& options.UnixCreateMode is { } unixCreateMode
&& options.Mode
is FileMode.CreateNew
or FileMode.Create
or FileMode.OpenOrCreate
or FileMode.Append
)
{
File.SetUnixFileMode(path, unixCreateMode & File.GetUnixFileMode(path));
}
#endif
}
catch
{
stream.Dispose();
throw;
}

return stream;
}
}
}
#endif
#endif
41 changes: 41 additions & 0 deletions PolyShim/Net60/FileStreamOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#if (NETCOREAPP && !NET6_0_OR_GREATER) || (NETFRAMEWORK) || (NETSTANDARD)
#nullable enable
#pragma warning disable CS0436

// No file I/O on .NET Standard prior to 1.3
#if !NETSTANDARD || NETSTANDARD1_3_OR_GREATER

using System;
using System.Diagnostics.CodeAnalysis;

namespace System.IO;

// https://learn.microsoft.com/dotnet/api/system.io.filestreamoptions
#if !POLYSHIM_INCLUDE_COVERAGE
[ExcludeFromCodeCoverage]
#endif
internal class FileStreamOptions
{
public FileMode Mode { get; set; } = FileMode.Open;

public FileAccess Access { get; set; } = FileAccess.Read;

public FileShare Share { get; set; } = FileShare.Read;

public int BufferSize
{
get;
set => field = value >= 0 ? value : throw new ArgumentOutOfRangeException(nameof(value));
} = 4096;

public FileOptions Options { get; set; } = FileOptions.None;

public long PreallocationSize
{
get;
set => field = value >= 0 ? value : throw new ArgumentOutOfRangeException(nameof(value));
}
}

#endif
#endif
54 changes: 54 additions & 0 deletions PolyShim/Net70/Directory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#if (NETCOREAPP && !NET7_0_OR_GREATER) || (NETFRAMEWORK) || (NETSTANDARD)
#nullable enable
#pragma warning disable CS0436

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Diagnostics.CodeAnalysis;

// No file I/O on .NET Standard prior to 1.3
#if !NETSTANDARD || NETSTANDARD1_3_OR_GREATER

file static class NativeMethods
{
[DllImport("libc", EntryPoint = "chmod", SetLastError = true)]
public static extern int Chmod(string path, uint mode);
}

#if !POLYSHIM_INCLUDE_COVERAGE
[ExcludeFromCodeCoverage]
#endif
internal static class MemberPolyfills_Net70_Directory
{
extension(Directory)
{
// https://learn.microsoft.com/dotnet/api/system.io.directory.createdirectory#system-io-directory-createdirectory(system-string-system-io-unixfilemode)
[UnsupportedOSPlatform("windows")]
public static DirectoryInfo CreateDirectory(string path, UnixFileMode unixCreateMode)
Comment thread
Tyrrrz marked this conversation as resolved.
{
if (OperatingSystem.IsWindows())
throw new PlatformNotSupportedException();

var existed = Directory.Exists(path);
var info = Directory.CreateDirectory(path);

if (!existed)
{
var effectiveMode = unixCreateMode & File.GetUnixFileMode(info.FullName);
if (NativeMethods.Chmod(info.FullName, (uint)effectiveMode) != 0)
{
throw new IOException(
$"Could not set Unix file mode for '{path}' (errno={Marshal.GetLastWin32Error()})."
);
}
}

return info;
}
}
}

#endif
#endif
Loading
Loading