Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
7 changes: 7 additions & 0 deletions src/libraries/System.Console/ref/System.Console.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public static void MoveBufferArea(int sourceLeft, int sourceTop, int sourceWidth
public static void MoveBufferArea(int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight, int targetLeft, int targetTop, char sourceChar, System.ConsoleColor sourceForeColor, System.ConsoleColor sourceBackColor) { }
public static System.IO.Stream OpenStandardError() { throw null; }
public static System.IO.Stream OpenStandardError(int bufferSize) { throw null; }
public static Microsoft.Win32.SafeHandles.SafeFileHandle OpenStandardErrorHandle() { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
Expand All @@ -108,8 +109,14 @@ public static void MoveBufferArea(int sourceLeft, int sourceTop, int sourceWidth
[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; }
public static Microsoft.Win32.SafeHandles.SafeFileHandle OpenStandardOutputHandle() { throw null; }
Comment thread
adamsitnik marked this conversation as resolved.
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public static int Read() { throw null; }
Expand Down
6 changes: 0 additions & 6 deletions src/libraries/System.Console/src/System.Console.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,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"
Comment thread
adamsitnik marked this conversation as resolved.
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 +97,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 @@ -229,8 +225,6 @@
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
41 changes: 41 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,46 @@ 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>
Comment thread
adamsitnik marked this conversation as resolved.
[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>
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>
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
24 changes: 15 additions & 9 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,26 @@ 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,
Comment thread
adamsitnik marked this conversation as resolved.
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() => new SafeFileHandle((IntPtr)0, ownsHandle: false);

public static SafeFileHandle OpenStandardOutputHandle() => new SafeFileHandle((IntPtr)1, ownsHandle: false);

public static SafeFileHandle OpenStandardErrorHandle() => new SafeFileHandle((IntPtr)2, ownsHandle: false);

public static Encoding InputEncoding
{
get { return GetConsoleEncoding(); }
Expand Down Expand Up @@ -671,23 +677,23 @@ private static bool IsHandleRedirected(SafeFileHandle fd)
/// </summary>
public static bool IsInputRedirectedCore()
{
return IsHandleRedirected(Interop.Sys.FileDescriptors.STDIN_FILENO);
return IsHandleRedirected(OpenStandardInputHandle());
}

/// <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(OpenStandardOutputHandle());
Comment thread
jkotas marked this conversation as resolved.
Outdated
}

/// <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(OpenStandardErrorHandle());
}

/// <summary>Creates an encoding from the current environment.</summary>
Expand Down Expand Up @@ -889,8 +895,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 +1114,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
12 changes: 9 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,26 @@ 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() => new SafeFileHandle((IntPtr)0, ownsHandle: false);

public static SafeFileHandle OpenStandardOutputHandle() => new SafeFileHandle((IntPtr)1, ownsHandle: false);

public static SafeFileHandle OpenStandardErrorHandle() => new SafeFileHandle((IntPtr)2, ownsHandle: false);

public static Encoding InputEncoding
{
get { return GetConsoleEncoding(); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,20 @@ internal static void EnsureConsoleInitialized() { }

public static Stream OpenStandardOutput()
{
return new WasmConsoleStream(Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDOUT_FILENO), FileAccess.Write);
return new WasmConsoleStream(OpenStandardOutputHandle(), FileAccess.Write);
Comment thread
jkotas marked this conversation as resolved.
Outdated
}

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

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

public static SafeFileHandle OpenStandardOutputHandle() => new SafeFileHandle((IntPtr)1, ownsHandle: false);

public static SafeFileHandle OpenStandardErrorHandle() => new SafeFileHandle((IntPtr)2, ownsHandle: false);

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

public static void SetConsoleInputEncoding(Encoding enc) => throw new PlatformNotSupportedException();
Expand Down
36 changes: 36 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,41 @@ private static unsafe bool ConsoleHandleIsWritable(IntPtr outErrHandle)
return r != 0; // In Win32 apps w/ no console, bResult should be 0 for failure.
}

// Checks whether stdin is readable. Do NOT pass
// stdout or stderr here!
private static unsafe bool ConsoleHandleIsReadable(IntPtr inHandle)
{
// Windows apps may have non-null valid looking handle values for
// stdin, stdout and stderr, but they may not be readable or
// writable. Verify this by calling ReadFile in the
// appropriate modes. This must handle console-less Windows apps.
int r = Interop.Kernel32.ReadFile(inHandle, null, 0, out _, IntPtr.Zero);
return r != 0; // In Win32 apps w/ no console, bResult should be 0 for failure.
}

Comment thread
adamsitnik marked this conversation as resolved.
Outdated
public static SafeFileHandle OpenStandardInputHandle() => OpenStandardHandle(Interop.Kernel32.HandleTypes.STD_INPUT_HANDLE);
Comment thread
adamsitnik marked this conversation as resolved.
Outdated

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);
bool isReadable = handleType == Interop.Kernel32.HandleTypes.STD_INPUT_HANDLE;

// If someone launches a managed process via CreateProcess, stdin/stdout/stderr
// could be set to INVALID_HANDLE_VALUE or they might use 0 as an invalid handle.
// We also need to ensure that the handle is readable (for stdin) or writable (for stdout/stderr).
if (handle == IntPtr.Zero || handle == InvalidHandleValue
|| (isReadable ? !ConsoleHandleIsReadable(handle) : !ConsoleHandleIsWritable(handle)))
{
throw new IOException(SR.IO_NoConsole);
Comment thread
adamsitnik marked this conversation as resolved.
Outdated
}

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