From 9ceefd61fbcb24137380a7f2e81af7b7bec3760f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Feb 2026 17:06:21 +0000
Subject: [PATCH 01/10] Initial plan
From 6f4ff5afa068292a1d254ebc6f8b453e3dc48b61 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Feb 2026 17:13:34 +0000
Subject: [PATCH 02/10] Add FileType enum and GetFileType method to
SafeFileHandle
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
---
.../Kernel32/Interop.GetNamedPipeInfo.cs | 10 +++
.../Win32/SafeHandles/SafeFileHandle.Unix.cs | 45 +++++++++++++
.../SafeHandles/SafeFileHandle.Windows.cs | 66 +++++++++++++++++++
.../Win32/SafeHandles/SafeFileHandle.cs | 8 +++
.../src/System/IO/FileType.cs | 52 +++++++++++++++
.../System.Runtime/ref/System.Runtime.cs | 13 ++++
6 files changed, 194 insertions(+)
create mode 100644 src/libraries/System.Private.CoreLib/src/System/IO/FileType.cs
diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetNamedPipeInfo.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetNamedPipeInfo.cs
index 5945fa95c28263..f883e4120f70a4 100644
--- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetNamedPipeInfo.cs
+++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetNamedPipeInfo.cs
@@ -17,5 +17,15 @@ internal static unsafe partial bool GetNamedPipeInfo(
uint* lpInBufferSize,
uint* lpMaxInstances
);
+
+ [LibraryImport(Libraries.Kernel32, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static unsafe partial bool GetNamedPipeInfo(
+ SafeFileHandle hNamedPipe,
+ uint* lpFlags,
+ uint* lpOutBufferSize,
+ uint* lpInBufferSize,
+ uint* lpMaxInstances
+ );
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
index 196ec738116e6a..7478e13b0ad986 100644
--- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
+++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
@@ -339,6 +339,9 @@ private bool Init(string path, FileMode mode, FileAccess access, FileShare share
Debug.Assert(status.Size == 0 || Interop.Sys.LSeek(this, 0, Interop.Sys.SeekWhence.SEEK_CUR) >= 0);
}
+ // Cache the file type from the status
+ _cachedFileType = (int)MapUnixFileTypeToFileType(status.Mode & Interop.Sys.FileTypes.S_IFMT);
+
fileLength = status.Size;
filePermissions = ((UnixFileMode)status.Mode) & PermissionMask;
}
@@ -494,6 +497,48 @@ private bool GetCanSeek()
return canSeek == NullableBool.True;
}
+ ///
+ /// Gets the type of the file that this handle represents.
+ ///
+ /// The type of the file.
+ /// The handle is closed.
+ public new System.IO.FileType GetFileType()
+ {
+ ObjectDisposedException.ThrowIf(IsClosed, this);
+
+ int cachedType = _cachedFileType;
+ if (cachedType != -1)
+ {
+ return (System.IO.FileType)cachedType;
+ }
+
+ // If we don't have a cached value, call FStat to get it
+ if (Interop.Sys.FStat(this, out Interop.Sys.FileStatus status) == 0)
+ {
+ System.IO.FileType fileType = MapUnixFileTypeToFileType(status.Mode & Interop.Sys.FileTypes.S_IFMT);
+ _cachedFileType = (int)fileType;
+ return fileType;
+ }
+
+ // If FStat fails, return Unknown
+ return System.IO.FileType.Unknown;
+ }
+
+ private static System.IO.FileType MapUnixFileTypeToFileType(int unixFileType)
+ {
+ return unixFileType switch
+ {
+ Interop.Sys.FileTypes.S_IFREG => System.IO.FileType.RegularFile,
+ Interop.Sys.FileTypes.S_IFDIR => System.IO.FileType.Directory,
+ Interop.Sys.FileTypes.S_IFLNK => System.IO.FileType.SymbolicLink,
+ Interop.Sys.FileTypes.S_IFIFO => System.IO.FileType.Pipe,
+ Interop.Sys.FileTypes.S_IFSOCK => System.IO.FileType.Socket,
+ Interop.Sys.FileTypes.S_IFCHR => System.IO.FileType.CharacterDevice,
+ Interop.Sys.FileTypes.S_IFBLK => System.IO.FileType.BlockDevice,
+ _ => System.IO.FileType.Unknown
+ };
+ }
+
internal long GetFileLength()
{
int result = Interop.Sys.FStat(this, out Interop.Sys.FileStatus status);
diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
index 8ee1660b705640..e704f0279c7a2c 100644
--- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
@@ -270,6 +270,72 @@ internal int GetFileType()
return fileType;
}
+ ///
+ /// Gets the type of the file that this handle represents.
+ ///
+ /// The type of the file.
+ /// The handle is closed.
+ public new unsafe System.IO.FileType GetFileType()
+ {
+ ObjectDisposedException.ThrowIf(IsClosed, this);
+
+ int cachedType = _cachedFileType;
+ if (cachedType != -1)
+ {
+ return (System.IO.FileType)cachedType;
+ }
+
+ int kernelFileType = GetFileType();
+
+ System.IO.FileType result = kernelFileType switch
+ {
+ Interop.Kernel32.FileTypes.FILE_TYPE_CHAR => System.IO.FileType.CharacterDevice,
+ Interop.Kernel32.FileTypes.FILE_TYPE_PIPE => GetPipeOrSocketType(),
+ Interop.Kernel32.FileTypes.FILE_TYPE_DISK => GetDiskBasedType(),
+ _ => System.IO.FileType.Unknown
+ };
+
+ _cachedFileType = (int)result;
+ return result;
+ }
+
+ private unsafe System.IO.FileType GetPipeOrSocketType()
+ {
+ // Try to call GetNamedPipeInfo to determine if it's a pipe or socket
+ uint flags;
+ if (Interop.Kernel32.GetNamedPipeInfo(this, &flags, null, null, null))
+ {
+ return System.IO.FileType.Pipe;
+ }
+
+ // If GetNamedPipeInfo fails, it's likely a socket
+ return System.IO.FileType.Socket;
+ }
+
+ private unsafe System.IO.FileType GetDiskBasedType()
+ {
+ // First check if it's a directory using GetFileInformationByHandle
+ if (Interop.Kernel32.GetFileInformationByHandle(this, out Interop.Kernel32.BY_HANDLE_FILE_INFORMATION fileInfo))
+ {
+ if ((fileInfo.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) != 0)
+ {
+ return System.IO.FileType.Directory;
+ }
+ }
+
+ // Check if it's a reparse point (symbolic link) using GetFileInformationByHandleEx
+ Interop.Kernel32.FILE_BASIC_INFO basicInfo;
+ if (Interop.Kernel32.GetFileInformationByHandleEx(this, Interop.Kernel32.FileBasicInfo, &basicInfo, (uint)sizeof(Interop.Kernel32.FILE_BASIC_INFO)))
+ {
+ if ((basicInfo.FileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_REPARSE_POINT) != 0)
+ {
+ return System.IO.FileType.SymbolicLink;
+ }
+ }
+
+ return System.IO.FileType.RegularFile;
+ }
+
internal long GetFileLength()
{
if (!_lengthCanBeCached)
diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs
index 76dc55b74dba71..c2446bcb8d1b79 100644
--- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs
+++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs
@@ -8,6 +8,7 @@ namespace Microsoft.Win32.SafeHandles
public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private string? _path;
+ private volatile int _cachedFileType = -1;
///
/// Creates a around a file handle.
@@ -20,5 +21,12 @@ public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHand
}
internal string? Path => _path;
+
+ ///
+ /// Gets the type of the file that this handle represents.
+ ///
+ /// The type of the file.
+ /// The handle is closed.
+ public System.IO.FileType GetFileType() => throw new PlatformNotSupportedException();
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileType.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileType.cs
new file mode 100644
index 00000000000000..d83bff7ab762df
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileType.cs
@@ -0,0 +1,52 @@
+// 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
+{
+ ///
+ /// Specifies the type of a file.
+ ///
+ public enum FileType
+ {
+ ///
+ /// The file type is unknown.
+ ///
+ Unknown,
+
+ ///
+ /// The file is a regular file.
+ ///
+ RegularFile,
+
+ ///
+ /// The file is a pipe (FIFO).
+ ///
+ Pipe,
+
+ ///
+ /// The file is a socket.
+ ///
+ Socket,
+
+ ///
+ /// The file is a character device.
+ ///
+ CharacterDevice,
+
+ ///
+ /// The file is a directory.
+ ///
+ Directory,
+
+ ///
+ /// The file is a symbolic link.
+ ///
+ SymbolicLink,
+
+ ///
+ /// The file is a block device.
+ ///
+ [UnsupportedOSPlatform("windows")]
+ BlockDevice
+ }
+}
diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs
index 65b98d13078b29..14d13061412189 100644
--- a/src/libraries/System.Runtime/ref/System.Runtime.cs
+++ b/src/libraries/System.Runtime/ref/System.Runtime.cs
@@ -22,6 +22,7 @@ public SafeFileHandle() : base (default(bool)) { }
public SafeFileHandle(System.IntPtr preexistingHandle, bool ownsHandle) : base (default(bool)) { }
public override bool IsInvalid { get { throw null; } }
public bool IsAsync { get { throw null; } }
+ public System.IO.FileType GetFileType() { throw null; }
protected override bool ReleaseHandle() { throw null; }
}
public abstract partial class SafeHandleMinusOneIsInvalid : System.Runtime.InteropServices.SafeHandle
@@ -10445,6 +10446,18 @@ public enum FileAttributes
IntegrityStream = 32768,
NoScrubData = 131072,
}
+ public enum FileType
+ {
+ Unknown = 0,
+ RegularFile = 1,
+ Pipe = 2,
+ Socket = 3,
+ CharacterDevice = 4,
+ Directory = 5,
+ SymbolicLink = 6,
+ [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")]
+ BlockDevice = 7,
+ }
public sealed partial class FileInfo : System.IO.FileSystemInfo
{
public FileInfo(string fileName) { }
From 71e5a815b8f5f3241661422dfae0cc134f547011 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Feb 2026 17:16:00 +0000
Subject: [PATCH 03/10] Add comprehensive tests for SafeFileHandle.GetFileType
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
---
.../SafeFileHandle/GetFileType.cs | 254 ++++++++++++++++++
1 file changed, 254 insertions(+)
create mode 100644 src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.cs
diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.cs
new file mode 100644
index 00000000000000..d3d4d7507372ce
--- /dev/null
+++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.cs
@@ -0,0 +1,254 @@
+// 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.IO.Pipes;
+using System.Net;
+using System.Net.Sockets;
+using Xunit;
+
+namespace System.IO.Tests
+{
+ public class SafeFileHandle_GetFileType : FileSystemTest
+ {
+ [Fact]
+ public void GetFileType_RegularFile()
+ {
+ string path = GetTestFilePath();
+ File.WriteAllText(path, "test");
+
+ using SafeFileHandle handle = File.OpenHandle(path, FileMode.Open, FileAccess.Read);
+ Assert.Equal(FileType.RegularFile, handle.GetFileType());
+ }
+
+ [Fact]
+ public void GetFileType_Directory()
+ {
+ string path = GetTestFilePath();
+ Directory.CreateDirectory(path);
+
+ if (OperatingSystem.IsWindows())
+ {
+ IntPtr hFile = Interop.Kernel32.CreateFile(
+ path,
+ Interop.Kernel32.GenericOperations.GENERIC_READ,
+ FileShare.ReadWrite,
+ null,
+ FileMode.Open,
+ Interop.Kernel32.FileOperations.FILE_FLAG_BACKUP_SEMANTICS,
+ IntPtr.Zero);
+
+ using SafeFileHandle handle = new SafeFileHandle(hFile, ownsHandle: true);
+ Assert.False(handle.IsInvalid);
+ Assert.Equal(FileType.Directory, handle.GetFileType());
+ }
+ else
+ {
+ IntPtr fd = Interop.Sys.Open(path, Interop.Sys.OpenFlags.O_RDONLY, 0);
+ using SafeFileHandle handle = new SafeFileHandle(fd, ownsHandle: true);
+ Assert.False(handle.IsInvalid);
+ Assert.Equal(FileType.Directory, handle.GetFileType());
+ }
+ }
+
+ [Fact]
+ public void GetFileType_NullDevice()
+ {
+ using SafeFileHandle handle = File.OpenHandle(
+ OperatingSystem.IsWindows() ? "NUL" : "/dev/null",
+ FileMode.Open,
+ FileAccess.Write);
+
+ Assert.Equal(FileType.CharacterDevice, handle.GetFileType());
+ }
+
+ [Fact]
+ public void GetFileType_AnonymousPipe()
+ {
+ using AnonymousPipeServerStream server = new AnonymousPipeServerStream(PipeDirection.Out);
+ using SafeFileHandle serverHandle = new SafeFileHandle(server.SafePipeHandle.DangerousGetHandle(), ownsHandle: false);
+
+ Assert.Equal(FileType.Pipe, serverHandle.GetFileType());
+
+ using SafeFileHandle clientHandle = new SafeFileHandle(server.ClientSafePipeHandle.DangerousGetHandle(), ownsHandle: false);
+ Assert.Equal(FileType.Pipe, clientHandle.GetFileType());
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux))]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void GetFileType_NamedPipe_Windows()
+ {
+ string pipeName = Path.GetRandomFileName();
+ using NamedPipeServerStream server = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
+
+ Task serverTask = Task.Run(async () => await server.WaitForConnectionAsync());
+
+ using NamedPipeClientStream client = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.None);
+ client.Connect();
+ serverTask.Wait();
+
+ using SafeFileHandle serverHandle = new SafeFileHandle(server.SafePipeHandle.DangerousGetHandle(), ownsHandle: false);
+ Assert.Equal(FileType.Pipe, serverHandle.GetFileType());
+
+ using SafeFileHandle clientHandle = new SafeFileHandle(client.SafePipeHandle.DangerousGetHandle(), ownsHandle: false);
+ Assert.Equal(FileType.Pipe, clientHandle.GetFileType());
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux))]
+ [PlatformSpecific(TestPlatforms.AnyUnix)]
+ public void GetFileType_NamedPipe_Unix()
+ {
+ string pipePath = GetTestFilePath();
+ Assert.Equal(0, Interop.Sys.MkFifo(pipePath, (int)UnixFileMode.UserRead | (int)UnixFileMode.UserWrite));
+
+ Task readerTask = Task.Run(() =>
+ {
+ using SafeFileHandle reader = File.OpenHandle(pipePath, FileMode.Open, FileAccess.Read);
+ Assert.Equal(FileType.Pipe, reader.GetFileType());
+ });
+
+ using SafeFileHandle writer = File.OpenHandle(pipePath, FileMode.Open, FileAccess.Write);
+ Assert.Equal(FileType.Pipe, writer.GetFileType());
+
+ readerTask.Wait();
+ }
+
+ [Fact]
+ public void GetFileType_Socket()
+ {
+ using Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+ listener.Listen(1);
+
+ using Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ client.Connect(listener.LocalEndPoint);
+
+ using Socket server = listener.Accept();
+
+ using SafeFileHandle serverHandle = new SafeFileHandle(server.Handle, ownsHandle: false);
+ using SafeFileHandle clientHandle = new SafeFileHandle(client.Handle, ownsHandle: false);
+
+ Assert.Equal(FileType.Socket, serverHandle.GetFileType());
+ Assert.Equal(FileType.Socket, clientHandle.GetFileType());
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
+ public void GetFileType_ConsoleInput()
+ {
+ if (!Console.IsInputRedirected)
+ {
+ using SafeFileHandle handle = new SafeFileHandle(Console.OpenStandardInput().SafeFileHandle.DangerousGetHandle(), ownsHandle: false);
+ FileType type = handle.GetFileType();
+
+ Assert.True(type == FileType.CharacterDevice || type == FileType.Pipe || type == FileType.RegularFile,
+ $"Expected CharacterDevice, Pipe, or RegularFile but got {type}");
+ }
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
+ [PlatformSpecific(TestPlatforms.AnyUnix)]
+ public void GetFileType_SymbolicLink_Unix()
+ {
+ string targetPath = GetTestFilePath();
+ string linkPath = GetTestFilePath();
+ File.WriteAllText(targetPath, "test");
+ File.CreateSymbolicLink(linkPath, targetPath);
+
+ IntPtr fd = Interop.Sys.Open(linkPath, Interop.Sys.OpenFlags.O_RDONLY | Interop.Sys.OpenFlags.O_NOFOLLOW, 0);
+ using SafeFileHandle handle = new SafeFileHandle(fd, ownsHandle: true);
+
+ if (!handle.IsInvalid)
+ {
+ Assert.Equal(FileType.SymbolicLink, handle.GetFileType());
+ }
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsPrivilegedProcess))]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void GetFileType_SymbolicLink_Windows()
+ {
+ string targetPath = GetTestFilePath();
+ string linkPath = GetTestFilePath();
+ File.WriteAllText(targetPath, "test");
+ File.CreateSymbolicLink(linkPath, targetPath);
+
+ IntPtr hFile = Interop.Kernel32.CreateFile(
+ linkPath,
+ Interop.Kernel32.GenericOperations.GENERIC_READ,
+ FileShare.ReadWrite,
+ null,
+ FileMode.Open,
+ Interop.Kernel32.FileOperations.FILE_FLAG_OPEN_REPARSE_POINT | Interop.Kernel32.FileOperations.FILE_FLAG_BACKUP_SEMANTICS,
+ IntPtr.Zero);
+
+ using SafeFileHandle handle = new SafeFileHandle(hFile, ownsHandle: true);
+ if (!handle.IsInvalid)
+ {
+ Assert.Equal(FileType.SymbolicLink, handle.GetFileType());
+ }
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsPrivilegedProcess))]
+ [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser & ~TestPlatforms.Wasi)]
+ public void GetFileType_BlockDevice_Unix()
+ {
+ string[] possibleBlockDevices = { "/dev/sda", "/dev/loop0", "/dev/vda", "/dev/nvme0n1" };
+
+ string blockDevice = null;
+ foreach (string device in possibleBlockDevices)
+ {
+ if (File.Exists(device))
+ {
+ blockDevice = device;
+ break;
+ }
+ }
+
+ if (blockDevice == null)
+ {
+ throw new SkipTestException("No accessible block device found for testing");
+ }
+
+ try
+ {
+ IntPtr fd = Interop.Sys.Open(blockDevice, Interop.Sys.OpenFlags.O_RDONLY, 0);
+ if (fd == (IntPtr)(-1))
+ {
+ throw new SkipTestException($"Could not open {blockDevice}");
+ }
+
+ using SafeFileHandle handle = new SafeFileHandle(fd, ownsHandle: true);
+ Assert.Equal(FileType.BlockDevice, handle.GetFileType());
+ }
+ catch (UnauthorizedAccessException)
+ {
+ throw new SkipTestException("Insufficient privileges to open block device");
+ }
+ }
+
+ [Fact]
+ public void GetFileType_ClosedHandle_ThrowsObjectDisposedException()
+ {
+ SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create);
+ handle.Dispose();
+
+ Assert.Throws(() => handle.GetFileType());
+ }
+
+ [Fact]
+ public void GetFileType_CachesResult()
+ {
+ string path = GetTestFilePath();
+ File.WriteAllText(path, "test");
+
+ using SafeFileHandle handle = File.OpenHandle(path, FileMode.Open, FileAccess.Read);
+
+ FileType firstCall = handle.GetFileType();
+ FileType secondCall = handle.GetFileType();
+
+ Assert.Equal(firstCall, secondCall);
+ Assert.Equal(FileType.RegularFile, firstCall);
+ }
+ }
+}
From 603de644380a622e1ae58ff61b9f6be667481752 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Feb 2026 17:20:58 +0000
Subject: [PATCH 04/10] Fix build errors for FileType enum and GetFileType
method
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
---
.../src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs | 4 +++-
.../Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs | 2 +-
.../src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs | 7 -------
.../src/System.Private.CoreLib.Shared.projitems | 1 +
.../System.Private.CoreLib/src/System/IO/FileType.cs | 2 +-
5 files changed, 6 insertions(+), 10 deletions(-)
diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
index 7478e13b0ad986..f4aa338e77f988 100644
--- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
+++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
@@ -502,7 +502,7 @@ private bool GetCanSeek()
///
/// The type of the file.
/// The handle is closed.
- public new System.IO.FileType GetFileType()
+ public System.IO.FileType GetFileType()
{
ObjectDisposedException.ThrowIf(IsClosed, this);
@@ -526,6 +526,7 @@ private bool GetCanSeek()
private static System.IO.FileType MapUnixFileTypeToFileType(int unixFileType)
{
+#pragma warning disable CA1416 // BlockDevice is only returned on Unix platforms
return unixFileType switch
{
Interop.Sys.FileTypes.S_IFREG => System.IO.FileType.RegularFile,
@@ -537,6 +538,7 @@ private static System.IO.FileType MapUnixFileTypeToFileType(int unixFileType)
Interop.Sys.FileTypes.S_IFBLK => System.IO.FileType.BlockDevice,
_ => System.IO.FileType.Unknown
};
+#pragma warning restore CA1416
}
internal long GetFileLength()
diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
index e704f0279c7a2c..074fa175196277 100644
--- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
@@ -275,7 +275,7 @@ internal int GetFileType()
///
/// The type of the file.
/// The handle is closed.
- public new unsafe System.IO.FileType GetFileType()
+ public unsafe System.IO.FileType GetFileType()
{
ObjectDisposedException.ThrowIf(IsClosed, this);
diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs
index c2446bcb8d1b79..45d05a97454e7f 100644
--- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs
+++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs
@@ -21,12 +21,5 @@ public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHand
}
internal string? Path => _path;
-
- ///
- /// Gets the type of the file that this handle represents.
- ///
- /// The type of the file.
- /// The handle is closed.
- public System.IO.FileType GetFileType() => throw new PlatformNotSupportedException();
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index c744f85d3e05ce..41890be47fba0c 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -514,6 +514,7 @@
+
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileType.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileType.cs
index d83bff7ab762df..3f4290c2286388 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/FileType.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileType.cs
@@ -46,7 +46,7 @@ public enum FileType
///
/// The file is a block device.
///
- [UnsupportedOSPlatform("windows")]
+ [System.Runtime.Versioning.UnsupportedOSPlatform("windows")]
BlockDevice
}
}
From 9edcc961c043c099e9e22c865ce4cc74003ce683 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Feb 2026 17:49:18 +0000
Subject: [PATCH 05/10] Address PR feedback: refactor GetFileType, split tests,
use SafeHandle
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
---
.../Kernel32/Interop.GetNamedPipeInfo.cs | 13 +-
.../Win32/SafeHandles/SafeFileHandle.Unix.cs | 21 +--
.../SafeHandles/SafeFileHandle.Windows.cs | 37 ++--
.../Win32/SafeHandles/SafeFileHandle.cs | 11 ++
.../SafeFileHandle/GetFileType.Unix.cs | 98 ++++++++++
.../SafeFileHandle/GetFileType.Windows.cs | 90 +++++++++
.../SafeFileHandle/GetFileType.cs | 172 +-----------------
.../System.IO.FileSystem.Tests.csproj | 3 +
8 files changed, 228 insertions(+), 217 deletions(-)
create mode 100644 src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Unix.cs
create mode 100644 src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Windows.cs
diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetNamedPipeInfo.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetNamedPipeInfo.cs
index f883e4120f70a4..686432415150a4 100644
--- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetNamedPipeInfo.cs
+++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetNamedPipeInfo.cs
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Runtime.InteropServices;
-using Microsoft.Win32.SafeHandles;
internal static partial class Interop
{
@@ -11,17 +10,7 @@ internal static partial class Kernel32
[LibraryImport(Libraries.Kernel32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static unsafe partial bool GetNamedPipeInfo(
- SafePipeHandle hNamedPipe,
- uint* lpFlags,
- uint* lpOutBufferSize,
- uint* lpInBufferSize,
- uint* lpMaxInstances
- );
-
- [LibraryImport(Libraries.Kernel32, SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- internal static unsafe partial bool GetNamedPipeInfo(
- SafeFileHandle hNamedPipe,
+ SafeHandle hNamedPipe,
uint* lpFlags,
uint* lpOutBufferSize,
uint* lpInBufferSize,
diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
index f4aa338e77f988..261307a4c57365 100644
--- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
+++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
@@ -497,15 +497,8 @@ private bool GetCanSeek()
return canSeek == NullableBool.True;
}
- ///
- /// Gets the type of the file that this handle represents.
- ///
- /// The type of the file.
- /// The handle is closed.
- public System.IO.FileType GetFileType()
+ internal System.IO.FileType GetFileTypeCore()
{
- ObjectDisposedException.ThrowIf(IsClosed, this);
-
int cachedType = _cachedFileType;
if (cachedType != -1)
{
@@ -513,15 +506,15 @@ public System.IO.FileType GetFileType()
}
// If we don't have a cached value, call FStat to get it
- if (Interop.Sys.FStat(this, out Interop.Sys.FileStatus status) == 0)
+ int result = Interop.Sys.FStat(this, out Interop.Sys.FileStatus status);
+ if (result != 0)
{
- System.IO.FileType fileType = MapUnixFileTypeToFileType(status.Mode & Interop.Sys.FileTypes.S_IFMT);
- _cachedFileType = (int)fileType;
- return fileType;
+ throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo());
}
- // If FStat fails, return Unknown
- return System.IO.FileType.Unknown;
+ System.IO.FileType fileType = MapUnixFileTypeToFileType(status.Mode & Interop.Sys.FileTypes.S_IFMT);
+ _cachedFileType = (int)fileType;
+ return fileType;
}
private static System.IO.FileType MapUnixFileTypeToFileType(int unixFileType)
diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
index 074fa175196277..694df37937fd8b 100644
--- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
@@ -16,7 +16,6 @@ public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
private long _length = -1; // negative means that hasn't been fetched.
private bool _lengthCanBeCached; // file has been opened for reading and not shared for writing.
private volatile FileOptions _fileOptions = (FileOptions)(-1);
- private volatile int _fileType = -1;
public SafeFileHandle() : base(true)
{
@@ -26,7 +25,7 @@ public SafeFileHandle() : base(true)
internal bool IsNoBuffering => (GetFileOptions() & NoBuffering) != 0;
- internal bool CanSeek => !IsClosed && GetFileType() == Interop.Kernel32.FileTypes.FILE_TYPE_DISK;
+ internal bool CanSeek => !IsClosed && GetKernelFileType() == Interop.Kernel32.FileTypes.FILE_TYPE_DISK;
internal ThreadPoolBoundHandle? ThreadPoolBinding { get; set; }
@@ -254,38 +253,34 @@ internal unsafe FileOptions GetFileOptions()
return _fileOptions = result;
}
- internal int GetFileType()
+ private int GetKernelFileType()
{
- int fileType = _fileType;
- if (fileType == -1)
+ int cachedType = _cachedFileType;
+ if (cachedType != -1)
{
- _fileType = fileType = Interop.Kernel32.GetFileType(this);
-
- Debug.Assert(fileType == Interop.Kernel32.FileTypes.FILE_TYPE_DISK
- || fileType == Interop.Kernel32.FileTypes.FILE_TYPE_PIPE
- || fileType == Interop.Kernel32.FileTypes.FILE_TYPE_CHAR,
- $"Unknown file type: {fileType}");
+ // Return the Kernel file type from cached FileType enum
+ System.IO.FileType fileType = (System.IO.FileType)cachedType;
+ return fileType switch
+ {
+ System.IO.FileType.CharacterDevice => Interop.Kernel32.FileTypes.FILE_TYPE_CHAR,
+ System.IO.FileType.Pipe or System.IO.FileType.Socket => Interop.Kernel32.FileTypes.FILE_TYPE_PIPE,
+ System.IO.FileType.RegularFile or System.IO.FileType.Directory or System.IO.FileType.SymbolicLink => Interop.Kernel32.FileTypes.FILE_TYPE_DISK,
+ _ => Interop.Kernel32.FileTypes.FILE_TYPE_UNKNOWN
+ };
}
- return fileType;
+ return Interop.Kernel32.GetFileType(this);
}
- ///
- /// Gets the type of the file that this handle represents.
- ///
- /// The type of the file.
- /// The handle is closed.
- public unsafe System.IO.FileType GetFileType()
+ internal unsafe System.IO.FileType GetFileTypeCore()
{
- ObjectDisposedException.ThrowIf(IsClosed, this);
-
int cachedType = _cachedFileType;
if (cachedType != -1)
{
return (System.IO.FileType)cachedType;
}
- int kernelFileType = GetFileType();
+ int kernelFileType = Interop.Kernel32.GetFileType(this);
System.IO.FileType result = kernelFileType switch
{
diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs
index 45d05a97454e7f..493720deb4b896 100644
--- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs
+++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs
@@ -21,5 +21,16 @@ public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHand
}
internal string? Path => _path;
+
+ ///
+ /// Gets the type of the file that this handle represents.
+ ///
+ /// The type of the file.
+ /// The handle is closed.
+ public System.IO.FileType GetFileType()
+ {
+ ObjectDisposedException.ThrowIf(IsClosed, this);
+ return GetFileTypeCore();
+ }
}
}
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
new file mode 100644
index 00000000000000..385df3f381000d
--- /dev/null
+++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Unix.cs
@@ -0,0 +1,98 @@
+// 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 Xunit;
+
+namespace System.IO.Tests
+{
+ [PlatformSpecific(TestPlatforms.AnyUnix)]
+ public class SafeFileHandle_GetFileType_Unix : FileSystemTest
+ {
+ [Fact]
+ public void GetFileType_Directory()
+ {
+ string path = GetTestFilePath();
+ Directory.CreateDirectory(path);
+
+ IntPtr fd = Interop.Sys.Open(path, Interop.Sys.OpenFlags.O_RDONLY, 0);
+ using SafeFileHandle handle = new SafeFileHandle(fd, ownsHandle: true);
+ Assert.False(handle.IsInvalid);
+ Assert.Equal(FileType.Directory, handle.GetFileType());
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux))]
+ public void GetFileType_NamedPipe()
+ {
+ string pipePath = GetTestFilePath();
+ Assert.Equal(0, Interop.Sys.MkFifo(pipePath, (int)UnixFileMode.UserRead | (int)UnixFileMode.UserWrite));
+
+ Task readerTask = Task.Run(() =>
+ {
+ using SafeFileHandle reader = File.OpenHandle(pipePath, FileMode.Open, FileAccess.Read);
+ Assert.Equal(FileType.Pipe, reader.GetFileType());
+ });
+
+ using SafeFileHandle writer = File.OpenHandle(pipePath, FileMode.Open, FileAccess.Write);
+ Assert.Equal(FileType.Pipe, writer.GetFileType());
+
+ readerTask.Wait();
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
+ public void GetFileType_SymbolicLink()
+ {
+ string targetPath = GetTestFilePath();
+ string linkPath = GetTestFilePath();
+ File.WriteAllText(targetPath, "test");
+ File.CreateSymbolicLink(linkPath, targetPath);
+
+ IntPtr fd = Interop.Sys.Open(linkPath, Interop.Sys.OpenFlags.O_RDONLY | Interop.Sys.OpenFlags.O_NOFOLLOW, 0);
+ using SafeFileHandle handle = new SafeFileHandle(fd, ownsHandle: true);
+
+ if (!handle.IsInvalid)
+ {
+ Assert.Equal(FileType.SymbolicLink, handle.GetFileType());
+ }
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsPrivilegedProcess))]
+ [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser & ~TestPlatforms.Wasi)]
+ public void GetFileType_BlockDevice()
+ {
+ string[] possibleBlockDevices = { "/dev/sda", "/dev/loop0", "/dev/vda", "/dev/nvme0n1" };
+
+ string blockDevice = null;
+ foreach (string device in possibleBlockDevices)
+ {
+ if (File.Exists(device))
+ {
+ blockDevice = device;
+ break;
+ }
+ }
+
+ if (blockDevice == null)
+ {
+ throw new SkipTestException("No accessible block device found for testing");
+ }
+
+ try
+ {
+ IntPtr fd = Interop.Sys.Open(blockDevice, Interop.Sys.OpenFlags.O_RDONLY, 0);
+ if (fd == (IntPtr)(-1))
+ {
+ throw new SkipTestException($"Could not open {blockDevice}");
+ }
+
+ using SafeFileHandle handle = new SafeFileHandle(fd, ownsHandle: true);
+ Assert.Equal(FileType.BlockDevice, handle.GetFileType());
+ }
+ catch (UnauthorizedAccessException)
+ {
+ throw new SkipTestException("Insufficient privileges to open block device");
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Windows.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Windows.cs
new file mode 100644
index 00000000000000..6a161a4fd69fbd
--- /dev/null
+++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Windows.cs
@@ -0,0 +1,90 @@
+// 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.IO.Pipes;
+using Xunit;
+
+namespace System.IO.Tests
+{
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public class SafeFileHandle_GetFileType_Windows : FileSystemTest
+ {
+ [Fact]
+ public void GetFileType_Directory()
+ {
+ string path = GetTestFilePath();
+ Directory.CreateDirectory(path);
+
+ IntPtr hFile = Interop.Kernel32.CreateFile(
+ path,
+ Interop.Kernel32.GenericOperations.GENERIC_READ,
+ FileShare.ReadWrite,
+ null,
+ FileMode.Open,
+ Interop.Kernel32.FileOperations.FILE_FLAG_BACKUP_SEMANTICS,
+ IntPtr.Zero);
+
+ using SafeFileHandle handle = new SafeFileHandle(hFile, ownsHandle: true);
+ Assert.False(handle.IsInvalid);
+ Assert.Equal(FileType.Directory, handle.GetFileType());
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux))]
+ public void GetFileType_NamedPipe()
+ {
+ string pipeName = Path.GetRandomFileName();
+ using NamedPipeServerStream server = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
+
+ Task serverTask = Task.Run(async () => await server.WaitForConnectionAsync());
+
+ using NamedPipeClientStream client = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.None);
+ client.Connect();
+ serverTask.Wait();
+
+ using SafeFileHandle serverHandle = new SafeFileHandle(server.SafePipeHandle.DangerousGetHandle(), ownsHandle: false);
+ Assert.Equal(FileType.Pipe, serverHandle.GetFileType());
+
+ using SafeFileHandle clientHandle = new SafeFileHandle(client.SafePipeHandle.DangerousGetHandle(), ownsHandle: false);
+ Assert.Equal(FileType.Pipe, clientHandle.GetFileType());
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
+ public void GetFileType_ConsoleInput()
+ {
+ if (!Console.IsInputRedirected)
+ {
+ using SafeFileHandle handle = new SafeFileHandle(Console.OpenStandardInput().SafeFileHandle.DangerousGetHandle(), ownsHandle: false);
+ FileType type = handle.GetFileType();
+
+ Assert.True(type == FileType.CharacterDevice || type == FileType.Pipe || type == FileType.RegularFile,
+ $"Expected CharacterDevice, Pipe, or RegularFile but got {type}");
+ }
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsPrivilegedProcess))]
+ public void GetFileType_SymbolicLink()
+ {
+ string targetPath = GetTestFilePath();
+ string linkPath = GetTestFilePath();
+ File.WriteAllText(targetPath, "test");
+ File.CreateSymbolicLink(linkPath, targetPath);
+
+ IntPtr hFile = Interop.Kernel32.CreateFile(
+ linkPath,
+ Interop.Kernel32.GenericOperations.GENERIC_READ,
+ FileShare.ReadWrite,
+ null,
+ FileMode.Open,
+ Interop.Kernel32.FileOperations.FILE_FLAG_OPEN_REPARSE_POINT | Interop.Kernel32.FileOperations.FILE_FLAG_BACKUP_SEMANTICS,
+ IntPtr.Zero);
+
+ using SafeFileHandle handle = new SafeFileHandle(hFile, ownsHandle: true);
+ if (!handle.IsInvalid)
+ {
+ Assert.Equal(FileType.SymbolicLink, handle.GetFileType());
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.cs
index d3d4d7507372ce..9cf46dfdeb9cc0 100644
--- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.cs
+++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.cs
@@ -1,7 +1,6 @@
// 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.IO.Pipes;
using System.Net;
@@ -22,51 +21,17 @@ public void GetFileType_RegularFile()
Assert.Equal(FileType.RegularFile, handle.GetFileType());
}
- [Fact]
- public void GetFileType_Directory()
- {
- string path = GetTestFilePath();
- Directory.CreateDirectory(path);
-
- if (OperatingSystem.IsWindows())
- {
- IntPtr hFile = Interop.Kernel32.CreateFile(
- path,
- Interop.Kernel32.GenericOperations.GENERIC_READ,
- FileShare.ReadWrite,
- null,
- FileMode.Open,
- Interop.Kernel32.FileOperations.FILE_FLAG_BACKUP_SEMANTICS,
- IntPtr.Zero);
-
- using SafeFileHandle handle = new SafeFileHandle(hFile, ownsHandle: true);
- Assert.False(handle.IsInvalid);
- Assert.Equal(FileType.Directory, handle.GetFileType());
- }
- else
- {
- IntPtr fd = Interop.Sys.Open(path, Interop.Sys.OpenFlags.O_RDONLY, 0);
- using SafeFileHandle handle = new SafeFileHandle(fd, ownsHandle: true);
- Assert.False(handle.IsInvalid);
- Assert.Equal(FileType.Directory, handle.GetFileType());
- }
- }
-
[Fact]
public void GetFileType_NullDevice()
{
- using SafeFileHandle handle = File.OpenHandle(
- OperatingSystem.IsWindows() ? "NUL" : "/dev/null",
- FileMode.Open,
- FileAccess.Write);
-
+ using SafeFileHandle handle = File.OpenNullHandle();
Assert.Equal(FileType.CharacterDevice, handle.GetFileType());
}
[Fact]
public void GetFileType_AnonymousPipe()
{
- using AnonymousPipeServerStream server = new AnonymousPipeServerStream(PipeDirection.Out);
+ using AnonymousPipeServerStream server = new(PipeDirection.Out);
using SafeFileHandle serverHandle = new SafeFileHandle(server.SafePipeHandle.DangerousGetHandle(), ownsHandle: false);
Assert.Equal(FileType.Pipe, serverHandle.GetFileType());
@@ -75,45 +40,6 @@ public void GetFileType_AnonymousPipe()
Assert.Equal(FileType.Pipe, clientHandle.GetFileType());
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux))]
- [PlatformSpecific(TestPlatforms.Windows)]
- public void GetFileType_NamedPipe_Windows()
- {
- string pipeName = Path.GetRandomFileName();
- using NamedPipeServerStream server = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
-
- Task serverTask = Task.Run(async () => await server.WaitForConnectionAsync());
-
- using NamedPipeClientStream client = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.None);
- client.Connect();
- serverTask.Wait();
-
- using SafeFileHandle serverHandle = new SafeFileHandle(server.SafePipeHandle.DangerousGetHandle(), ownsHandle: false);
- Assert.Equal(FileType.Pipe, serverHandle.GetFileType());
-
- using SafeFileHandle clientHandle = new SafeFileHandle(client.SafePipeHandle.DangerousGetHandle(), ownsHandle: false);
- Assert.Equal(FileType.Pipe, clientHandle.GetFileType());
- }
-
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux))]
- [PlatformSpecific(TestPlatforms.AnyUnix)]
- public void GetFileType_NamedPipe_Unix()
- {
- string pipePath = GetTestFilePath();
- Assert.Equal(0, Interop.Sys.MkFifo(pipePath, (int)UnixFileMode.UserRead | (int)UnixFileMode.UserWrite));
-
- Task readerTask = Task.Run(() =>
- {
- using SafeFileHandle reader = File.OpenHandle(pipePath, FileMode.Open, FileAccess.Read);
- Assert.Equal(FileType.Pipe, reader.GetFileType());
- });
-
- using SafeFileHandle writer = File.OpenHandle(pipePath, FileMode.Open, FileAccess.Write);
- Assert.Equal(FileType.Pipe, writer.GetFileType());
-
- readerTask.Wait();
- }
-
[Fact]
public void GetFileType_Socket()
{
@@ -133,100 +59,6 @@ public void GetFileType_Socket()
Assert.Equal(FileType.Socket, clientHandle.GetFileType());
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
- public void GetFileType_ConsoleInput()
- {
- if (!Console.IsInputRedirected)
- {
- using SafeFileHandle handle = new SafeFileHandle(Console.OpenStandardInput().SafeFileHandle.DangerousGetHandle(), ownsHandle: false);
- FileType type = handle.GetFileType();
-
- Assert.True(type == FileType.CharacterDevice || type == FileType.Pipe || type == FileType.RegularFile,
- $"Expected CharacterDevice, Pipe, or RegularFile but got {type}");
- }
- }
-
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
- [PlatformSpecific(TestPlatforms.AnyUnix)]
- public void GetFileType_SymbolicLink_Unix()
- {
- string targetPath = GetTestFilePath();
- string linkPath = GetTestFilePath();
- File.WriteAllText(targetPath, "test");
- File.CreateSymbolicLink(linkPath, targetPath);
-
- IntPtr fd = Interop.Sys.Open(linkPath, Interop.Sys.OpenFlags.O_RDONLY | Interop.Sys.OpenFlags.O_NOFOLLOW, 0);
- using SafeFileHandle handle = new SafeFileHandle(fd, ownsHandle: true);
-
- if (!handle.IsInvalid)
- {
- Assert.Equal(FileType.SymbolicLink, handle.GetFileType());
- }
- }
-
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsPrivilegedProcess))]
- [PlatformSpecific(TestPlatforms.Windows)]
- public void GetFileType_SymbolicLink_Windows()
- {
- string targetPath = GetTestFilePath();
- string linkPath = GetTestFilePath();
- File.WriteAllText(targetPath, "test");
- File.CreateSymbolicLink(linkPath, targetPath);
-
- IntPtr hFile = Interop.Kernel32.CreateFile(
- linkPath,
- Interop.Kernel32.GenericOperations.GENERIC_READ,
- FileShare.ReadWrite,
- null,
- FileMode.Open,
- Interop.Kernel32.FileOperations.FILE_FLAG_OPEN_REPARSE_POINT | Interop.Kernel32.FileOperations.FILE_FLAG_BACKUP_SEMANTICS,
- IntPtr.Zero);
-
- using SafeFileHandle handle = new SafeFileHandle(hFile, ownsHandle: true);
- if (!handle.IsInvalid)
- {
- Assert.Equal(FileType.SymbolicLink, handle.GetFileType());
- }
- }
-
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsPrivilegedProcess))]
- [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser & ~TestPlatforms.Wasi)]
- public void GetFileType_BlockDevice_Unix()
- {
- string[] possibleBlockDevices = { "/dev/sda", "/dev/loop0", "/dev/vda", "/dev/nvme0n1" };
-
- string blockDevice = null;
- foreach (string device in possibleBlockDevices)
- {
- if (File.Exists(device))
- {
- blockDevice = device;
- break;
- }
- }
-
- if (blockDevice == null)
- {
- throw new SkipTestException("No accessible block device found for testing");
- }
-
- try
- {
- IntPtr fd = Interop.Sys.Open(blockDevice, Interop.Sys.OpenFlags.O_RDONLY, 0);
- if (fd == (IntPtr)(-1))
- {
- throw new SkipTestException($"Could not open {blockDevice}");
- }
-
- using SafeFileHandle handle = new SafeFileHandle(fd, ownsHandle: true);
- Assert.Equal(FileType.BlockDevice, handle.GetFileType());
- }
- catch (UnauthorizedAccessException)
- {
- throw new SkipTestException("Insufficient privileges to open block device");
- }
- }
-
[Fact]
public void GetFileType_ClosedHandle_ThrowsObjectDisposedException()
{
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 d6ca52e5e34c12..bccdc010b62184 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
@@ -227,6 +227,9 @@
+
+
+
From 1f0655fe83d2848b3f44cb96a0570e83637b2259 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Feb 2026 18:07:06 +0000
Subject: [PATCH 06/10] Address feedback: move test files, delete
GetKernelFileType, use new API in CanSeek
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
---
.../SafeHandles/SafeFileHandle.Windows.cs | 21 +------------------
.../System.IO.FileSystem.Tests.csproj | 6 +++---
2 files changed, 4 insertions(+), 23 deletions(-)
diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
index 694df37937fd8b..9680ac6fe25d86 100644
--- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
@@ -25,7 +25,7 @@ public SafeFileHandle() : base(true)
internal bool IsNoBuffering => (GetFileOptions() & NoBuffering) != 0;
- internal bool CanSeek => !IsClosed && GetKernelFileType() == Interop.Kernel32.FileTypes.FILE_TYPE_DISK;
+ internal bool CanSeek => !IsClosed && GetFileType() == System.IO.FileType.RegularFile;
internal ThreadPoolBoundHandle? ThreadPoolBinding { get; set; }
@@ -253,25 +253,6 @@ internal unsafe FileOptions GetFileOptions()
return _fileOptions = result;
}
- private int GetKernelFileType()
- {
- int cachedType = _cachedFileType;
- if (cachedType != -1)
- {
- // Return the Kernel file type from cached FileType enum
- System.IO.FileType fileType = (System.IO.FileType)cachedType;
- return fileType switch
- {
- System.IO.FileType.CharacterDevice => Interop.Kernel32.FileTypes.FILE_TYPE_CHAR,
- System.IO.FileType.Pipe or System.IO.FileType.Socket => Interop.Kernel32.FileTypes.FILE_TYPE_PIPE,
- System.IO.FileType.RegularFile or System.IO.FileType.Directory or System.IO.FileType.SymbolicLink => Interop.Kernel32.FileTypes.FILE_TYPE_DISK,
- _ => Interop.Kernel32.FileTypes.FILE_TYPE_UNKNOWN
- };
- }
-
- return Interop.Kernel32.GetFileType(this);
- }
-
internal unsafe System.IO.FileType GetFileTypeCore()
{
int cachedType = _cachedFileType;
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 bccdc010b62184..ee394f212c3f9c 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
@@ -93,12 +93,14 @@
+
+
@@ -112,6 +114,7 @@
+
@@ -227,9 +230,6 @@
-
-
-
From 89944640f76cbcaa129133df97523f3a128fc6f4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Feb 2026 19:22:51 +0000
Subject: [PATCH 07/10] Add GetNamedPipeInfo reference and fix Unix test
attribute
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
---
.../src/System.Private.CoreLib.Shared.projitems | 3 +++
.../SafeFileHandle/GetFileType.Unix.cs | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index 41890be47fba0c..b4f043fd3e3063 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -1954,6 +1954,9 @@
Common\Interop\Windows\Kernel32\Interop.GetFileType_SafeHandle.cs
+
+ Common\Interop\Windows\Kernel32\Interop.GetNamedPipeInfo.cs
+
Common\Interop\Windows\Kernel32\Interop.GetFinalPathNameByHandle.cs
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 385df3f381000d..197e19c38acf5d 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
@@ -22,7 +22,7 @@ public void GetFileType_Directory()
Assert.Equal(FileType.Directory, handle.GetFileType());
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux))]
+ [Fact]
public void GetFileType_NamedPipe()
{
string pipePath = GetTestFilePath();
From 98347983793beb300a3141e01bea76f397d92153 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Feb 2026 10:00:43 +0000
Subject: [PATCH 08/10] Add Unix and Windows Interop references to fix test
build errors
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
---
.../System.IO.FileSystem.Tests.csproj | 5 +++++
1 file changed, 5 insertions(+)
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 ee394f212c3f9c..f9cbc5f387f21a 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
@@ -101,6 +101,10 @@
+
+
@@ -244,6 +248,7 @@
+
From ef2da05b9263d18f132722bab4092d66af161022 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Feb 2026 16:24:38 +0000
Subject: [PATCH 09/10] Fix Unix test build errors: use Open correctly, add
Task using, add MkFifo
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
---
.../SafeFileHandle/GetFileType.Unix.cs | 12 +++++-------
.../System.IO.FileSystem.Tests.csproj | 2 ++
2 files changed, 7 insertions(+), 7 deletions(-)
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 197e19c38acf5d..35069ebfaf28b8 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
@@ -3,6 +3,7 @@
using Microsoft.DotNet.XUnitExtensions;
using Microsoft.Win32.SafeHandles;
+using System.Threading.Tasks;
using Xunit;
namespace System.IO.Tests
@@ -16,8 +17,7 @@ public void GetFileType_Directory()
string path = GetTestFilePath();
Directory.CreateDirectory(path);
- IntPtr fd = Interop.Sys.Open(path, Interop.Sys.OpenFlags.O_RDONLY, 0);
- using SafeFileHandle handle = new SafeFileHandle(fd, ownsHandle: true);
+ using SafeFileHandle handle = Interop.Sys.Open(path, Interop.Sys.OpenFlags.O_RDONLY, 0);
Assert.False(handle.IsInvalid);
Assert.Equal(FileType.Directory, handle.GetFileType());
}
@@ -48,8 +48,7 @@ public void GetFileType_SymbolicLink()
File.WriteAllText(targetPath, "test");
File.CreateSymbolicLink(linkPath, targetPath);
- IntPtr fd = Interop.Sys.Open(linkPath, Interop.Sys.OpenFlags.O_RDONLY | Interop.Sys.OpenFlags.O_NOFOLLOW, 0);
- using SafeFileHandle handle = new SafeFileHandle(fd, ownsHandle: true);
+ using SafeFileHandle handle = Interop.Sys.Open(linkPath, Interop.Sys.OpenFlags.O_RDONLY | Interop.Sys.OpenFlags.O_NOFOLLOW, 0);
if (!handle.IsInvalid)
{
@@ -80,13 +79,12 @@ public void GetFileType_BlockDevice()
try
{
- IntPtr fd = Interop.Sys.Open(blockDevice, Interop.Sys.OpenFlags.O_RDONLY, 0);
- if (fd == (IntPtr)(-1))
+ using SafeFileHandle handle = Interop.Sys.Open(blockDevice, Interop.Sys.OpenFlags.O_RDONLY, 0);
+ if (handle.IsInvalid)
{
throw new SkipTestException($"Could not open {blockDevice}");
}
- using SafeFileHandle handle = new SafeFileHandle(fd, ownsHandle: true);
Assert.Equal(FileType.BlockDevice, handle.GetFileType());
}
catch (UnauthorizedAccessException)
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 f9cbc5f387f21a..19a3719f682d97 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
@@ -105,6 +105,8 @@
Link="Common\Interop\Unix\Interop.Open.cs" />
+
From 28ec926560ecf27da13d322d7b699ba7e58d61f5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Feb 2026 17:29:42 +0000
Subject: [PATCH 10/10] Remove invalid ConditionalFact from Windows NamedPipe
test
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
---
.../SafeFileHandle/GetFileType.Windows.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Windows.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Windows.cs
index 6a161a4fd69fbd..0b45b1d547dcc1 100644
--- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Windows.cs
+++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Windows.cs
@@ -31,7 +31,7 @@ public void GetFileType_Directory()
Assert.Equal(FileType.Directory, handle.GetFileType());
}
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux))]
+ [Fact]
public void GetFileType_NamedPipe()
{
string pipeName = Path.GetRandomFileName();