diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Unix.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Unix.cs index e31fbeafc545ba..75ea18a2e6ddd4 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Unix.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Unix.cs @@ -1,15 +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 Microsoft.DotNet.RemoteExecutor; using Microsoft.DotNet.XUnitExtensions; using Microsoft.Win32.SafeHandles; +using System.Runtime.InteropServices; using System.Threading.Tasks; using Xunit; namespace System.IO.Tests { [PlatformSpecific(TestPlatforms.AnyUnix)] - public class SafeFileHandle_GetFileType_Unix : FileSystemTest + public partial class SafeFileHandle_GetFileType_Unix : FileSystemTest { [Fact] public void GetFileType_Directory() @@ -94,5 +96,53 @@ public void GetFileType_BlockDevice() throw new SkipTestException("Insufficient privileges to open block device"); } } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [PlatformSpecific(TestPlatforms.AnyUnix)] + public void WriteCanUseFileDescriptorAboveCurrentLimit() + { + RemoteExecutor.Invoke(() => + { + const ulong MaximumFileDescriptorLimit = 4_096; + + Assert.Equal(0, Interop.Sys.GetRLimit(Interop.Sys.RlimitResources.RLIMIT_NOFILE, out Interop.Sys.RLimit limits)); + + limits.CurrentLimit = Math.Min(limits.CurrentLimit, MaximumFileDescriptorLimit); + Assert.InRange(limits.CurrentLimit, 2UL, (ulong)int.MaxValue); + Assert.Equal(0, Interop.Sys.SetRLimit(Interop.Sys.RlimitResources.RLIMIT_NOFILE, ref limits)); + + int maxAllowedFileDescriptor = checked((int)limits.CurrentLimit - 1); + string filePath = Path.GetTempFileName(); + + try + { + using (SafeFileHandle fileHandle = File.OpenHandle(filePath, FileMode.Open, FileAccess.ReadWrite)) + { + Assert.Equal(maxAllowedFileDescriptor, dup2((int)fileHandle.DangerousGetHandle(), maxAllowedFileDescriptor)); + + limits.CurrentLimit--; + Assert.Equal(0, Interop.Sys.SetRLimit(Interop.Sys.RlimitResources.RLIMIT_NOFILE, ref limits)); + + using (SafeFileHandle duplicatedHandle = new(maxAllowedFileDescriptor, ownsHandle: true)) + { + RandomAccess.Write(duplicatedHandle, new byte[] { 1 }, fileOffset: 0); + } + + byte[] buffer = new byte[1]; + Assert.Equal(1, RandomAccess.Read(fileHandle, buffer, fileOffset: 0)); + Assert.Equal(new byte[] { 1 }, buffer); + } + } + finally + { + File.Delete(filePath); + } + + return RemoteExecutor.SuccessExitCode; + }).Dispose(); + } + + [LibraryImport("libc", SetLastError = true)] + private static partial int dup2(int oldFileDescriptor, int newFileDescriptor); } } diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj index 94332ce80eae1a..b84fae4a64b063 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj @@ -103,6 +103,8 @@ + diff --git a/src/native/libs/Common/pal_utilities.h b/src/native/libs/Common/pal_utilities.h index a1a57c8ed0e8ce..2fa9a78ac4dbc7 100644 --- a/src/native/libs/Common/pal_utilities.h +++ b/src/native/libs/Common/pal_utilities.h @@ -79,8 +79,9 @@ inline static int ToFileDescriptorUnchecked(intptr_t fd) inline static int ToFileDescriptor(intptr_t fd) { #ifndef TARGET_WASI // the valid range of file descriptors is probably INT32_MIN <= fd && fd <= INT32_MAX, the negative handles are valid for console. - assert(0 <= fd && fd < sysconf(_SC_OPEN_MAX)); + assert(0 <= fd); #endif + assert(fd <= INT_MAX); return ToFileDescriptorUnchecked(fd); }