Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
477d864
Initial plan
Copilot Jan 22, 2026
334fb05
Add OpenStandardInputHandle, OpenStandardOutputHandle, and OpenStanda…
Copilot Jan 22, 2026
9e85d35
Add tests for new OpenStandardHandle APIs
Copilot Jan 22, 2026
1e306a0
Add missing using statements for SafeFileHandle in ConsolePal files
Copilot Jan 22, 2026
28358d4
Address PR feedback: remove FileDescriptors, add helper method, simpl…
Copilot Jan 22, 2026
e0b2048
Add fcntl validation for Unix file descriptors and capture test output
Copilot Jan 22, 2026
f356b35
Rename FcntlCanGetSetAccess to FcntlCheckAccess and improve naming
Copilot Jan 22, 2026
d75eb11
Update CheckAccess to return error codes and add verifyFd parameter
Copilot Jan 22, 2026
99540fd
Change exception to InvalidOperationException and remove verifyFd: fa…
Copilot Jan 22, 2026
17b3b9b
Add SystemNative_FcntlCheckAccess to entrypoints.c
Copilot Jan 22, 2026
3140874
Remove handle validation logic from all platforms
Copilot Feb 3, 2026
3060fe4
Apply suggestions from code review
adamsitnik Feb 3, 2026
fc6da20
Add UnsupportedOSPlatform attributes and remove FileDescriptors depen…
Copilot Feb 3, 2026
8fc95ba
Remove Interop.FileDescriptors.cs as it's no longer used
Copilot Feb 3, 2026
d977064
Make IsRedirectedCore methods allocation-free by passing file descrip…
Copilot Feb 3, 2026
afa0616
Remove unused SafeFileHandle overload of IsATty
Copilot Feb 3, 2026
dc25075
Remove SetLastError from IsATty as errors are not checked
Copilot Feb 3, 2026
95e05e3
Enable OpenStandardInputHandle test on Browser platform
Copilot Feb 3, 2026
aa8cb64
Rename ConsolePal.WebAssembly.cs to ConsolePal.Browser.cs and WasmCon…
Copilot Feb 3, 2026
dae8613
Fix test platform conditions to exclude unsupported mobile Unix platf…
Copilot Feb 3, 2026
80f4cd6
Remove unused Interop.Dup.cs references from System.Console.csproj
Copilot Feb 3, 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

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@

using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

internal static partial class Interop
{
internal static partial class Sys
{
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsATty", SetLastError = true)]
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsATty")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool IsATty(SafeFileHandle fd);
internal static partial bool IsATty(IntPtr fd);
}
}
13 changes: 13 additions & 0 deletions src/libraries/System.Console/ref/System.Console.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,29 @@ public static void MoveBufferArea(int sourceLeft, int sourceTop, int sourceWidth
public static System.IO.Stream OpenStandardError() { throw null; }
public static System.IO.Stream OpenStandardError(int bufferSize) { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
public static Microsoft.Win32.SafeHandles.SafeFileHandle OpenStandardErrorHandle() { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
public static System.IO.Stream OpenStandardInput() { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public static System.IO.Stream OpenStandardInput(int bufferSize) { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
public static Microsoft.Win32.SafeHandles.SafeFileHandle OpenStandardInputHandle() { throw null; }
public static System.IO.Stream OpenStandardOutput() { throw null; }
public static System.IO.Stream OpenStandardOutput(int bufferSize) { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
public static Microsoft.Win32.SafeHandles.SafeFileHandle OpenStandardOutputHandle() { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public static int Read() { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
Expand Down
12 changes: 1 addition & 11 deletions src/libraries/System.Console/src/System.Console.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,13 @@

<!-- Browser -->
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'browser'">
<Compile Include="System\ConsolePal.WebAssembly.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Dup.cs"
Link="Common\Interop\Unix\Interop.Dup.cs" />
<Compile Include="System\ConsolePal.Browser.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Write.cs"
Link="Common\Interop\Unix\Interop.Write.cs" />
<Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs"
Link="Common\Interop\Unix\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\Unix\Interop.Errors.cs"
Link="Common\Interop\Unix\Interop.Errors.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.FileDescriptors.cs"
Link="Common\Interop\Unix\Interop.FileDescriptors.cs" />
<Compile Include="$(CommonPath)Interop\Unix\Interop.IOErrors.cs"
Link="Common\Interop\Unix\Interop.IOErrors.cs" />
</ItemGroup>
Expand Down Expand Up @@ -99,8 +95,6 @@
Link="Common\Interop\Unix\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\Unix\Interop.Errors.cs"
Link="Common\Interop\Unix\Interop.Errors.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.FileDescriptors.cs"
Link="Common\Interop\Unix\Interop.FileDescriptors.cs" />
<Compile Include="$(CommonPath)Interop\Unix\Interop.IOErrors.cs"
Link="Common\Interop\Unix\Interop.IOErrors.cs" />
</ItemGroup>
Expand Down Expand Up @@ -227,10 +221,6 @@
Link="Common\Interop\Unix\Interop.IOErrors.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Close.cs"
Link="Common\Interop\Unix\Interop.Close.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Dup.cs"
Link="Common\Interop\Unix\Interop.Dup.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.FileDescriptors.cs"
Link="Common\Interop\Unix\Interop.FileDescriptors.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.FLock.cs"
Link="Common\Interop\Unix\Interop.FLock.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetControlCharacters.cs"
Expand Down
47 changes: 47 additions & 0 deletions src/libraries/System.Console/src/System/Console.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using Microsoft.Win32.SafeHandles;

namespace System
{
Expand Down Expand Up @@ -652,6 +653,52 @@ public static Stream OpenStandardError(int bufferSize)
return ConsolePal.OpenStandardError();
}

/// <summary>
/// Gets the standard input handle.
/// </summary>
/// <returns>A <see cref="SafeFileHandle"/> representing the standard input handle.</returns>
/// <remarks>
/// The returned handle does not own the underlying resource, so disposing it will not close the standard input handle.
/// </remarks>
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
public static SafeFileHandle OpenStandardInputHandle()
{
return ConsolePal.OpenStandardInputHandle();
}

/// <summary>
/// Gets the standard output handle.
/// </summary>
/// <returns>A <see cref="SafeFileHandle"/> representing the standard output handle.</returns>
/// <remarks>
/// The returned handle does not own the underlying resource, so disposing it will not close the standard output handle.
/// </remarks>
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
public static SafeFileHandle OpenStandardOutputHandle()
{
return ConsolePal.OpenStandardOutputHandle();
}

/// <summary>
/// Gets the standard error handle.
/// </summary>
/// <returns>A <see cref="SafeFileHandle"/> representing the standard error handle.</returns>
/// <remarks>
/// The returned handle does not own the underlying resource, so disposing it will not close the standard error handle.
/// </remarks>
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
public static SafeFileHandle OpenStandardErrorHandle()
{
return ConsolePal.OpenStandardErrorHandle();
}

[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
Expand Down
7 changes: 7 additions & 0 deletions src/libraries/System.Console/src/System/ConsolePal.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;

#pragma warning disable IDE0060

Expand All @@ -30,6 +31,12 @@ internal static void EnsureConsoleInitialized() { }

public static Stream OpenStandardError() => new LogcatStream(OutputEncoding);

public static SafeFileHandle OpenStandardInputHandle() => throw new PlatformNotSupportedException();

public static SafeFileHandle OpenStandardOutputHandle() => throw new PlatformNotSupportedException();

public static SafeFileHandle OpenStandardErrorHandle() => throw new PlatformNotSupportedException();

public static Encoding InputEncoding => throw new PlatformNotSupportedException();

public static void SetConsoleInputEncoding(Encoding enc) => throw new PlatformNotSupportedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@

namespace System
{
internal sealed class WasmConsoleStream : ConsoleStream
internal sealed class BrowserConsoleStream : ConsoleStream
{
private readonly SafeFileHandle _handle;

internal WasmConsoleStream(SafeFileHandle handle, FileAccess access)
internal BrowserConsoleStream(SafeFileHandle handle, FileAccess access)
: base(access)
{
_handle = handle;
Expand Down Expand Up @@ -83,14 +83,22 @@ internal static void EnsureConsoleInitialized() { }

public static Stream OpenStandardOutput()
{
return new WasmConsoleStream(Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDOUT_FILENO), FileAccess.Write);
return new BrowserConsoleStream(OpenStandardOutputHandle(), FileAccess.Write);
}

public static Stream OpenStandardError()
{
return new WasmConsoleStream(Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDERR_FILENO), FileAccess.Write);
return new BrowserConsoleStream(OpenStandardErrorHandle(), FileAccess.Write);
}

public static SafeFileHandle OpenStandardInputHandle() => throw new PlatformNotSupportedException();

public static SafeFileHandle OpenStandardOutputHandle() => OpenStandardHandle(1);

public static SafeFileHandle OpenStandardErrorHandle() => OpenStandardHandle(2);

private static SafeFileHandle OpenStandardHandle(IntPtr fd) => new SafeFileHandle(fd, ownsHandle: false);

public static Encoding InputEncoding => throw new PlatformNotSupportedException();

public static void SetConsoleInputEncoding(Encoding enc) => throw new PlatformNotSupportedException();
Expand Down
28 changes: 18 additions & 10 deletions src/libraries/System.Console/src/System/ConsolePal.Unix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,28 @@ internal static partial class ConsolePal

public static Stream OpenStandardInput()
{
return new UnixConsoleStream(Interop.CheckIo(Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDIN_FILENO)), FileAccess.Read,
return new UnixConsoleStream(OpenStandardInputHandle(), FileAccess.Read,
useReadLine: !Console.IsInputRedirected);
}

public static Stream OpenStandardOutput()
{
return new UnixConsoleStream(Interop.CheckIo(Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDOUT_FILENO)), FileAccess.Write);
return new UnixConsoleStream(OpenStandardOutputHandle(), FileAccess.Write);
}

public static Stream OpenStandardError()
{
return new UnixConsoleStream(Interop.CheckIo(Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDERR_FILENO)), FileAccess.Write);
return new UnixConsoleStream(OpenStandardErrorHandle(), FileAccess.Write);
}

public static SafeFileHandle OpenStandardInputHandle() => OpenStandardHandle(0);

public static SafeFileHandle OpenStandardOutputHandle() => OpenStandardHandle(1);

public static SafeFileHandle OpenStandardErrorHandle() => OpenStandardHandle(2);

private static SafeFileHandle OpenStandardHandle(IntPtr fd) => new SafeFileHandle(fd, ownsHandle: false);

public static Encoding InputEncoding
{
get { return GetConsoleEncoding(); }
Expand Down Expand Up @@ -660,7 +668,7 @@ static void TransferBytes(ReadOnlySpan<byte> src, StdInReader dst)
/// Gets whether the specified file descriptor was redirected.
/// It's considered redirected if it doesn't refer to a terminal.
/// </summary>
private static bool IsHandleRedirected(SafeFileHandle fd)
private static bool IsHandleRedirected(IntPtr fd)
{
return !Interop.Sys.IsATty(fd);
}
Expand All @@ -671,23 +679,23 @@ private static bool IsHandleRedirected(SafeFileHandle fd)
/// </summary>
public static bool IsInputRedirectedCore()
{
return IsHandleRedirected(Interop.Sys.FileDescriptors.STDIN_FILENO);
return IsHandleRedirected(0);
}

/// <summary>Gets whether Console.Out is redirected.
/// We approximate the behavior by checking whether the underlying stream is our UnixConsoleStream and it's wrapping a character device.
/// </summary>
public static bool IsOutputRedirectedCore()
{
return IsHandleRedirected(Interop.Sys.FileDescriptors.STDOUT_FILENO);
return IsHandleRedirected(1);
}

/// <summary>Gets whether Console.Error is redirected.
/// We approximate the behavior by checking whether the underlying stream is our UnixConsoleStream and it's wrapping a character device.
/// </summary>
public static bool IsErrorRedirectedCore()
{
return IsHandleRedirected(Interop.Sys.FileDescriptors.STDERR_FILENO);
return IsHandleRedirected(2);
}

/// <summary>Creates an encoding from the current environment.</summary>
Expand Down Expand Up @@ -889,8 +897,8 @@ private static unsafe void EnsureInitializedCore()
// This also resets it for termination due to an unhandled exception.
AppDomain.CurrentDomain.UnhandledException += (_, _) => { Interop.Sys.UninitializeTerminal(); };

s_terminalHandle = !Console.IsOutputRedirected ? Interop.Sys.FileDescriptors.STDOUT_FILENO :
!Console.IsInputRedirected ? Interop.Sys.FileDescriptors.STDIN_FILENO :
s_terminalHandle = !Console.IsOutputRedirected ? OpenStandardOutputHandle() :
!Console.IsInputRedirected ? OpenStandardInputHandle() :
null;

// Provide the native lib with the correct code from the terminfo to transition us into
Expand Down Expand Up @@ -1108,7 +1116,7 @@ private static void InvalidateTerminalSettings()
// DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION is set.
// In both cases, they are written to stdout.
internal static void WriteTerminalAnsiColorString(string? value)
=> WriteTerminalAnsiString(value, Interop.Sys.FileDescriptors.STDOUT_FILENO, mayChangeCursorPosition: false);
=> WriteTerminalAnsiString(value, OpenStandardOutputHandle(), mayChangeCursorPosition: false);

/// <summary>Writes a terminfo-based ANSI escape string to stdout.</summary>
/// <param name="value">The string to write.</param>
Expand Down
14 changes: 11 additions & 3 deletions src/libraries/System.Console/src/System/ConsolePal.Wasi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,28 @@ internal static partial class ConsolePal
{
public static Stream OpenStandardInput()
{
return new UnixConsoleStream(Interop.Sys.FileDescriptors.STDIN_FILENO, FileAccess.Read,
return new UnixConsoleStream(OpenStandardInputHandle(), FileAccess.Read,
useReadLine: !Console.IsInputRedirected);
}

public static Stream OpenStandardOutput()
{
return new UnixConsoleStream(Interop.Sys.FileDescriptors.STDOUT_FILENO, FileAccess.Write);
return new UnixConsoleStream(OpenStandardOutputHandle(), FileAccess.Write);
}

public static Stream OpenStandardError()
{
return new UnixConsoleStream(Interop.Sys.FileDescriptors.STDERR_FILENO, FileAccess.Write);
return new UnixConsoleStream(OpenStandardErrorHandle(), FileAccess.Write);
}

public static SafeFileHandle OpenStandardInputHandle() => OpenStandardHandle(0);

public static SafeFileHandle OpenStandardOutputHandle() => OpenStandardHandle(1);

public static SafeFileHandle OpenStandardErrorHandle() => OpenStandardHandle(2);

private static SafeFileHandle OpenStandardHandle(IntPtr fd) => new SafeFileHandle(fd, ownsHandle: false);

public static Encoding InputEncoding
{
get { return GetConsoleEncoding(); }
Expand Down
13 changes: 13 additions & 0 deletions src/libraries/System.Console/src/System/ConsolePal.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;

namespace System
{
Expand Down Expand Up @@ -90,6 +91,18 @@ private static unsafe bool ConsoleHandleIsWritable(IntPtr outErrHandle)
return r != 0; // In Win32 apps w/ no console, bResult should be 0 for failure.
}

public static SafeFileHandle OpenStandardInputHandle() => OpenStandardHandle(Interop.Kernel32.HandleTypes.STD_INPUT_HANDLE);

public static SafeFileHandle OpenStandardOutputHandle() => OpenStandardHandle(Interop.Kernel32.HandleTypes.STD_OUTPUT_HANDLE);

public static SafeFileHandle OpenStandardErrorHandle() => OpenStandardHandle(Interop.Kernel32.HandleTypes.STD_ERROR_HANDLE);

private static SafeFileHandle OpenStandardHandle(int handleType)
{
IntPtr handle = Interop.Kernel32.GetStdHandle(handleType);
return new SafeFileHandle(handle, ownsHandle: false);
}

public static Encoding InputEncoding
{
get { return EncodingHelper.GetSupportedConsoleEncoding((int)Interop.Kernel32.GetConsoleCP()); }
Expand Down
7 changes: 7 additions & 0 deletions src/libraries/System.Console/src/System/ConsolePal.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using System.IO;
using System.Text;
using Microsoft.Win32.SafeHandles;

#pragma warning disable IDE0060

Expand Down Expand Up @@ -34,6 +35,12 @@ internal static void EnsureConsoleInitialized()

public static Stream OpenStandardError() => new NSLogStream(OutputEncoding);

public static SafeFileHandle OpenStandardInputHandle() => throw new PlatformNotSupportedException();

public static SafeFileHandle OpenStandardOutputHandle() => throw new PlatformNotSupportedException();

public static SafeFileHandle OpenStandardErrorHandle() => throw new PlatformNotSupportedException();

public static Encoding InputEncoding => throw new PlatformNotSupportedException();

public static void SetConsoleInputEncoding(Encoding enc) => throw new PlatformNotSupportedException();
Expand Down
Loading
Loading