From c01e51fabbd6048f753821a84091dea5afda9454 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:20:04 +0000 Subject: [PATCH 1/7] Initial plan From e103ef64ef7128d393b13e0e5f20f14308cb036e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:51:26 +0000 Subject: [PATCH 2/7] Fix ESPIPE error when accessing SafeFileHandle on pseudofiles Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../IO/Strategies/FileStreamHelpers.Unix.cs | 22 +++++++++++++++++-- .../IO/Strategies/OSFileStreamStrategy.cs | 2 +- .../IO/Strategies/UnixFileStreamStrategy.cs | 18 +++++++++++++++ .../FileStream/SafeFileHandle.cs | 22 +++++++++++++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs index 15da21326a9451..40a76476e6d9af 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs @@ -30,8 +30,26 @@ internal static long CheckFileCall(long result, string? path, bool ignoreNotSupp return result; } - internal static long Seek(SafeFileHandle handle, long offset, SeekOrigin origin) => - CheckFileCall(Interop.Sys.LSeek(handle, offset, (Interop.Sys.SeekWhence)(int)origin), handle.Path); // SeekOrigin values are the same as Interop.libc.SeekWhence values + internal static long Seek(SafeFileHandle handle, long offset, SeekOrigin origin, bool ignoreSeekErrors = false) + { + long result = Interop.Sys.LSeek(handle, offset, (Interop.Sys.SeekWhence)(int)origin); // SeekOrigin values are the same as Interop.libc.SeekWhence values + if (result < 0) + { + Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + + // Some files, such as certain pseudofiles on AzureLinux, may report CanSeek = true + // but still fail with ESPIPE when attempting to seek. When ignoreSeekErrors is true, + // we ignore this specific error to match the behavior in RandomAccess where we tolerate seek failures. + if (ignoreSeekErrors && errorInfo.Error == Interop.Error.ESPIPE) + { + return offset; // return the requested offset as if the seek succeeded + } + + throw Interop.GetExceptionForIoErrno(errorInfo, handle.Path); + } + + return result; + } internal static void ThrowInvalidArgument(SafeFileHandle handle) => throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(Interop.Error.EINVAL), handle.Path); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs index bd7f9fef10ed28..81c5370f725de7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs @@ -93,7 +93,7 @@ public sealed override long Position internal sealed override bool IsClosed => _fileHandle.IsClosed; // Flushing is the responsibility of BufferedFileStreamStrategy - internal sealed override SafeFileHandle SafeFileHandle + internal override SafeFileHandle SafeFileHandle { get { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs index 0ab7d5cb7f0863..caf2a8b9783857 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs @@ -15,5 +15,23 @@ internal UnixFileStreamStrategy(string path, FileMode mode, FileAccess access, F base(path, mode, access, share, options, preallocationSize, unixCreateMode) { } + + internal override SafeFileHandle SafeFileHandle + { + get + { + if (CanSeek) + { + // Update the file offset before exposing it since it's possible that + // in memory position is out-of-sync with the actual file position. + // Some files, such as certain pseudofiles on AzureLinux, may report CanSeek = true + // but still fail with ESPIPE when attempting to seek. We ignore this specific error + // to match the behavior in RandomAccess where we tolerate seek failures. + FileStreamHelpers.Seek(_fileHandle, _filePosition, SeekOrigin.Begin, ignoreSeekErrors: true); + } + + return _fileHandle; + } + } } } diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileStream/SafeFileHandle.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileStream/SafeFileHandle.cs index 8bc995a3cc2fb7..3037e7fda9ce0e 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileStream/SafeFileHandle.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileStream/SafeFileHandle.cs @@ -64,5 +64,27 @@ public void AccessFlushesFileClosesHandle() Assert.Equal(TestBuffer.Length, fsr.Length); } } + + [Theory] + [InlineData("/proc/net/route")] + [InlineData("/proc/version")] + [PlatformSpecific(TestPlatforms.Linux)] + public void SafeFileHandle_PseudoFile_DoesNotThrow(string path) + { + // On some Linux distributions (e.g., AzureLinux 3), pseudofiles may report CanSeek = true + // but fail when attempting to seek. Accessing SafeFileHandle should not throw in these cases. + if (!File.Exists(path)) + { + return; // Skip if the file doesn't exist + } + + using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + + // This should not throw even if the file reports CanSeek = true but doesn't support seeking + SafeFileHandle handle = fs.SafeFileHandle; + + Assert.NotNull(handle); + Assert.False(handle.IsClosed); + } } } From 445ea4e6458f60a71048071b1dba2198f4d7b797 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:59:31 +0000 Subject: [PATCH 3/7] Fix return value when seek fails with ESPIPE Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../src/System/IO/Strategies/FileStreamHelpers.Unix.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs index 40a76476e6d9af..22b0d5a17c85da 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs @@ -42,7 +42,7 @@ internal static long Seek(SafeFileHandle handle, long offset, SeekOrigin origin, // we ignore this specific error to match the behavior in RandomAccess where we tolerate seek failures. if (ignoreSeekErrors && errorInfo.Error == Interop.Error.ESPIPE) { - return offset; // return the requested offset as if the seek succeeded + return -1; // Indicate failure without throwing } throw Interop.GetExceptionForIoErrno(errorInfo, handle.Path); From 2ed623980e17012a0ee3cd21d73628ba934bfa91 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Oct 2025 15:05:00 +0000 Subject: [PATCH 4/7] Clarify return value handling when ignoring seek errors Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../src/System/IO/Strategies/FileStreamHelpers.Unix.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs index 22b0d5a17c85da..4250e341204899 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs @@ -40,9 +40,10 @@ internal static long Seek(SafeFileHandle handle, long offset, SeekOrigin origin, // Some files, such as certain pseudofiles on AzureLinux, may report CanSeek = true // but still fail with ESPIPE when attempting to seek. When ignoreSeekErrors is true, // we ignore this specific error to match the behavior in RandomAccess where we tolerate seek failures. + // The return value in this case is not meaningful and should not be used by the caller. if (ignoreSeekErrors && errorInfo.Error == Interop.Error.ESPIPE) { - return -1; // Indicate failure without throwing + return 0; // Return value is not used when ignoreSeekErrors is true } throw Interop.GetExceptionForIoErrno(errorInfo, handle.Path); From 5ff7d4fb8a1e536076d2d339fa081cbef61d5ac2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:38:39 +0000 Subject: [PATCH 5/7] Address review feedback - inline ESPIPE handling and improve test Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../IO/Strategies/FileStreamHelpers.Unix.cs | 23 ++----------------- .../IO/Strategies/UnixFileStreamStrategy.cs | 20 +++++++++++----- .../FileStream/SafeFileHandle.cs | 7 ++++-- 3 files changed, 21 insertions(+), 29 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs index 4250e341204899..15da21326a9451 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs @@ -30,27 +30,8 @@ internal static long CheckFileCall(long result, string? path, bool ignoreNotSupp return result; } - internal static long Seek(SafeFileHandle handle, long offset, SeekOrigin origin, bool ignoreSeekErrors = false) - { - long result = Interop.Sys.LSeek(handle, offset, (Interop.Sys.SeekWhence)(int)origin); // SeekOrigin values are the same as Interop.libc.SeekWhence values - if (result < 0) - { - Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); - - // Some files, such as certain pseudofiles on AzureLinux, may report CanSeek = true - // but still fail with ESPIPE when attempting to seek. When ignoreSeekErrors is true, - // we ignore this specific error to match the behavior in RandomAccess where we tolerate seek failures. - // The return value in this case is not meaningful and should not be used by the caller. - if (ignoreSeekErrors && errorInfo.Error == Interop.Error.ESPIPE) - { - return 0; // Return value is not used when ignoreSeekErrors is true - } - - throw Interop.GetExceptionForIoErrno(errorInfo, handle.Path); - } - - return result; - } + internal static long Seek(SafeFileHandle handle, long offset, SeekOrigin origin) => + CheckFileCall(Interop.Sys.LSeek(handle, offset, (Interop.Sys.SeekWhence)(int)origin), handle.Path); // SeekOrigin values are the same as Interop.libc.SeekWhence values internal static void ThrowInvalidArgument(SafeFileHandle handle) => throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(Interop.Error.EINVAL), handle.Path); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs index caf2a8b9783857..663c82049da543 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs @@ -22,12 +22,20 @@ internal override SafeFileHandle SafeFileHandle { if (CanSeek) { - // Update the file offset before exposing it since it's possible that - // in memory position is out-of-sync with the actual file position. - // Some files, such as certain pseudofiles on AzureLinux, may report CanSeek = true - // but still fail with ESPIPE when attempting to seek. We ignore this specific error - // to match the behavior in RandomAccess where we tolerate seek failures. - FileStreamHelpers.Seek(_fileHandle, _filePosition, SeekOrigin.Begin, ignoreSeekErrors: true); + // The base SafeFileHandle getter synchronizes the file offset before exposing the handle + // since the in-memory position may be out-of-sync with the actual file position. + // However, some files such as certain pseudofiles on AzureLinux may report CanSeek = true + // but still fail with ESPIPE when attempting to seek. We tolerate this specific error + // to match the behavior in RandomAccess where we ignore seek failures on certain file types. + long result = Interop.Sys.LSeek(_fileHandle, _filePosition, Interop.Sys.SeekWhence.SEEK_SET); + if (result < 0) + { + Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + if (errorInfo.Error != Interop.Error.ESPIPE) + { + throw Interop.GetExceptionForIoErrno(errorInfo, _fileHandle.Path); + } + } } return _fileHandle; diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileStream/SafeFileHandle.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileStream/SafeFileHandle.cs index 3037e7fda9ce0e..7e3076aa1977ae 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileStream/SafeFileHandle.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileStream/SafeFileHandle.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 Microsoft.DotNet.XUnitExtensions; using Microsoft.Win32.SafeHandles; using System; using System.IO; @@ -65,7 +66,7 @@ public void AccessFlushesFileClosesHandle() } } - [Theory] + [ConditionalTheory(nameof(File_Exists))] [InlineData("/proc/net/route")] [InlineData("/proc/version")] [PlatformSpecific(TestPlatforms.Linux)] @@ -75,7 +76,7 @@ public void SafeFileHandle_PseudoFile_DoesNotThrow(string path) // but fail when attempting to seek. Accessing SafeFileHandle should not throw in these cases. if (!File.Exists(path)) { - return; // Skip if the file doesn't exist + throw new SkipTestException($"File {path} does not exist"); } using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); @@ -86,5 +87,7 @@ public void SafeFileHandle_PseudoFile_DoesNotThrow(string path) Assert.NotNull(handle); Assert.False(handle.IsClosed); } + + private static bool File_Exists => File.Exists("/proc/net/route") || File.Exists("/proc/version"); } } From 4d5ee35daaedfc1bbf5cc016db20d223cc7d663c Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Wed, 15 Oct 2025 22:29:41 +0200 Subject: [PATCH 6/7] Apply suggestions from code review --- .../src/System/IO/Strategies/UnixFileStreamStrategy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs index 663c82049da543..40208c0615a945 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs @@ -26,7 +26,7 @@ internal override SafeFileHandle SafeFileHandle // since the in-memory position may be out-of-sync with the actual file position. // However, some files such as certain pseudofiles on AzureLinux may report CanSeek = true // but still fail with ESPIPE when attempting to seek. We tolerate this specific error - // to match the behavior in RandomAccess where we ignore seek failures on certain file types. + // to match the behavior in RandomAccess where we ignore pwrite and pread failures on certain file types. long result = Interop.Sys.LSeek(_fileHandle, _filePosition, Interop.Sys.SeekWhence.SEEK_SET); if (result < 0) { From 324d86d139e2169128e64b07a01f02bd7ecec0c6 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 20 Oct 2025 15:47:48 +0200 Subject: [PATCH 7/7] Apply suggestions from code review --- .../IO/Strategies/UnixFileStreamStrategy.cs | 1 + .../FileStream/SafeFileHandle.cs | 18 +++++++----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs index 40208c0615a945..f3539a0de77492 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs @@ -35,6 +35,7 @@ internal override SafeFileHandle SafeFileHandle { throw Interop.GetExceptionForIoErrno(errorInfo, _fileHandle.Path); } + _fileHandle.SupportsRandomAccess = false; } } diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileStream/SafeFileHandle.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileStream/SafeFileHandle.cs index 7e3076aa1977ae..7cdbbb661270a9 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileStream/SafeFileHandle.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileStream/SafeFileHandle.cs @@ -66,28 +66,24 @@ public void AccessFlushesFileClosesHandle() } } - [ConditionalTheory(nameof(File_Exists))] - [InlineData("/proc/net/route")] - [InlineData("/proc/version")] + [ConditionalFact] [PlatformSpecific(TestPlatforms.Linux)] - public void SafeFileHandle_PseudoFile_DoesNotThrow(string path) + public void SafeFileHandle_PseudoFile_DoesNotThrow() { // On some Linux distributions (e.g., AzureLinux 3), pseudofiles may report CanSeek = true // but fail when attempting to seek. Accessing SafeFileHandle should not throw in these cases. - if (!File.Exists(path)) - { - throw new SkipTestException($"File {path} does not exist"); - } + string path = File.Exists("/proc/net/route") + ? "/proc/net/route" + : File.Exists("/proc/version") + ? "/proc/version" + : throw new SkipTestException("Can't find a pseudofile to test."); using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - // This should not throw even if the file reports CanSeek = true but doesn't support seeking SafeFileHandle handle = fs.SafeFileHandle; Assert.NotNull(handle); Assert.False(handle.IsClosed); } - - private static bool File_Exists => File.Exists("/proc/net/route") || File.Exists("/proc/version"); } }