Skip to content

Commit 2fac8ba

Browse files
Copilotadamsitnikjkotas
authored
Add OpenStandardInputHandle, OpenStandardOutputHandle, and OpenStandardErrorHandle APIs (#123478)
Fixes #122802 --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: Adam Sitnik <[email protected]> Co-authored-by: jkotas <[email protected]>
1 parent 756a984 commit 2fac8ba

File tree

12 files changed

+241
-54
lines changed

12 files changed

+241
-54
lines changed

src/libraries/Common/src/Interop/Unix/System.Native/Interop.FileDescriptors.cs

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/libraries/Common/src/Interop/Unix/System.Native/Interop.IsATty.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33

44
using System;
55
using System.Runtime.InteropServices;
6-
using Microsoft.Win32.SafeHandles;
76

87
internal static partial class Interop
98
{
109
internal static partial class Sys
1110
{
12-
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsATty", SetLastError = true)]
11+
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsATty")]
1312
[return: MarshalAs(UnmanagedType.Bool)]
14-
internal static partial bool IsATty(SafeFileHandle fd);
13+
internal static partial bool IsATty(IntPtr fd);
1514
}
1615
}

src/libraries/System.Console/ref/System.Console.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,29 @@ public static void MoveBufferArea(int sourceLeft, int sourceTop, int sourceWidth
101101
public static System.IO.Stream OpenStandardError() { throw null; }
102102
public static System.IO.Stream OpenStandardError(int bufferSize) { throw null; }
103103
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
104+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
105+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
106+
public static Microsoft.Win32.SafeHandles.SafeFileHandle OpenStandardErrorHandle() { throw null; }
107+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
104108
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
105109
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
106110
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
107111
public static System.IO.Stream OpenStandardInput() { throw null; }
108112
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
109113
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
110114
public static System.IO.Stream OpenStandardInput(int bufferSize) { throw null; }
115+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
116+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
117+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
118+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
119+
public static Microsoft.Win32.SafeHandles.SafeFileHandle OpenStandardInputHandle() { throw null; }
111120
public static System.IO.Stream OpenStandardOutput() { throw null; }
112121
public static System.IO.Stream OpenStandardOutput(int bufferSize) { throw null; }
113122
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
123+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
124+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
125+
public static Microsoft.Win32.SafeHandles.SafeFileHandle OpenStandardOutputHandle() { throw null; }
126+
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
114127
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
115128
public static int Read() { throw null; }
116129
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]

src/libraries/System.Console/src/System.Console.csproj

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,13 @@
5858

5959
<!-- Browser -->
6060
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'browser'">
61-
<Compile Include="System\ConsolePal.WebAssembly.cs" />
62-
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Dup.cs"
63-
Link="Common\Interop\Unix\Interop.Dup.cs" />
61+
<Compile Include="System\ConsolePal.Browser.cs" />
6462
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Write.cs"
6563
Link="Common\Interop\Unix\Interop.Write.cs" />
6664
<Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs"
6765
Link="Common\Interop\Unix\Interop.Libraries.cs" />
6866
<Compile Include="$(CommonPath)Interop\Unix\Interop.Errors.cs"
6967
Link="Common\Interop\Unix\Interop.Errors.cs" />
70-
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.FileDescriptors.cs"
71-
Link="Common\Interop\Unix\Interop.FileDescriptors.cs" />
7268
<Compile Include="$(CommonPath)Interop\Unix\Interop.IOErrors.cs"
7369
Link="Common\Interop\Unix\Interop.IOErrors.cs" />
7470
</ItemGroup>
@@ -99,8 +95,6 @@
9995
Link="Common\Interop\Unix\Interop.Libraries.cs" />
10096
<Compile Include="$(CommonPath)Interop\Unix\Interop.Errors.cs"
10197
Link="Common\Interop\Unix\Interop.Errors.cs" />
102-
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.FileDescriptors.cs"
103-
Link="Common\Interop\Unix\Interop.FileDescriptors.cs" />
10498
<Compile Include="$(CommonPath)Interop\Unix\Interop.IOErrors.cs"
10599
Link="Common\Interop\Unix\Interop.IOErrors.cs" />
106100
</ItemGroup>
@@ -227,10 +221,6 @@
227221
Link="Common\Interop\Unix\Interop.IOErrors.cs" />
228222
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Close.cs"
229223
Link="Common\Interop\Unix\Interop.Close.cs" />
230-
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Dup.cs"
231-
Link="Common\Interop\Unix\Interop.Dup.cs" />
232-
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.FileDescriptors.cs"
233-
Link="Common\Interop\Unix\Interop.FileDescriptors.cs" />
234224
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.FLock.cs"
235225
Link="Common\Interop\Unix\Interop.FLock.cs" />
236226
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetControlCharacters.cs"

src/libraries/System.Console/src/System/Console.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Runtime.Versioning;
1010
using System.Text;
1111
using System.Threading;
12+
using Microsoft.Win32.SafeHandles;
1213

1314
namespace System
1415
{
@@ -652,6 +653,52 @@ public static Stream OpenStandardError(int bufferSize)
652653
return ConsolePal.OpenStandardError();
653654
}
654655

656+
/// <summary>
657+
/// Gets the standard input handle.
658+
/// </summary>
659+
/// <returns>A <see cref="SafeFileHandle"/> representing the standard input handle.</returns>
660+
/// <remarks>
661+
/// The returned handle does not own the underlying resource, so disposing it will not close the standard input handle.
662+
/// </remarks>
663+
[UnsupportedOSPlatform("android")]
664+
[UnsupportedOSPlatform("browser")]
665+
[UnsupportedOSPlatform("ios")]
666+
[UnsupportedOSPlatform("tvos")]
667+
public static SafeFileHandle OpenStandardInputHandle()
668+
{
669+
return ConsolePal.OpenStandardInputHandle();
670+
}
671+
672+
/// <summary>
673+
/// Gets the standard output handle.
674+
/// </summary>
675+
/// <returns>A <see cref="SafeFileHandle"/> representing the standard output handle.</returns>
676+
/// <remarks>
677+
/// The returned handle does not own the underlying resource, so disposing it will not close the standard output handle.
678+
/// </remarks>
679+
[UnsupportedOSPlatform("android")]
680+
[UnsupportedOSPlatform("ios")]
681+
[UnsupportedOSPlatform("tvos")]
682+
public static SafeFileHandle OpenStandardOutputHandle()
683+
{
684+
return ConsolePal.OpenStandardOutputHandle();
685+
}
686+
687+
/// <summary>
688+
/// Gets the standard error handle.
689+
/// </summary>
690+
/// <returns>A <see cref="SafeFileHandle"/> representing the standard error handle.</returns>
691+
/// <remarks>
692+
/// The returned handle does not own the underlying resource, so disposing it will not close the standard error handle.
693+
/// </remarks>
694+
[UnsupportedOSPlatform("android")]
695+
[UnsupportedOSPlatform("ios")]
696+
[UnsupportedOSPlatform("tvos")]
697+
public static SafeFileHandle OpenStandardErrorHandle()
698+
{
699+
return ConsolePal.OpenStandardErrorHandle();
700+
}
701+
655702
[UnsupportedOSPlatform("android")]
656703
[UnsupportedOSPlatform("browser")]
657704
[UnsupportedOSPlatform("ios")]

src/libraries/System.Console/src/System/ConsolePal.Android.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.IO;
55
using System.Runtime.InteropServices;
66
using System.Text;
7+
using Microsoft.Win32.SafeHandles;
78

89
#pragma warning disable IDE0060
910

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

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

34+
public static SafeFileHandle OpenStandardInputHandle() => throw new PlatformNotSupportedException();
35+
36+
public static SafeFileHandle OpenStandardOutputHandle() => throw new PlatformNotSupportedException();
37+
38+
public static SafeFileHandle OpenStandardErrorHandle() => throw new PlatformNotSupportedException();
39+
3340
public static Encoding InputEncoding => throw new PlatformNotSupportedException();
3441

3542
public static void SetConsoleInputEncoding(Encoding enc) => throw new PlatformNotSupportedException();

src/libraries/System.Console/src/System/ConsolePal.WebAssembly.cs renamed to src/libraries/System.Console/src/System/ConsolePal.Browser.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88

99
namespace System
1010
{
11-
internal sealed class WasmConsoleStream : ConsoleStream
11+
internal sealed class BrowserConsoleStream : ConsoleStream
1212
{
1313
private readonly SafeFileHandle _handle;
1414

15-
internal WasmConsoleStream(SafeFileHandle handle, FileAccess access)
15+
internal BrowserConsoleStream(SafeFileHandle handle, FileAccess access)
1616
: base(access)
1717
{
1818
_handle = handle;
@@ -83,14 +83,22 @@ internal static void EnsureConsoleInitialized() { }
8383

8484
public static Stream OpenStandardOutput()
8585
{
86-
return new WasmConsoleStream(Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDOUT_FILENO), FileAccess.Write);
86+
return new BrowserConsoleStream(OpenStandardOutputHandle(), FileAccess.Write);
8787
}
8888

8989
public static Stream OpenStandardError()
9090
{
91-
return new WasmConsoleStream(Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDERR_FILENO), FileAccess.Write);
91+
return new BrowserConsoleStream(OpenStandardErrorHandle(), FileAccess.Write);
9292
}
9393

94+
public static SafeFileHandle OpenStandardInputHandle() => throw new PlatformNotSupportedException();
95+
96+
public static SafeFileHandle OpenStandardOutputHandle() => OpenStandardHandle(1);
97+
98+
public static SafeFileHandle OpenStandardErrorHandle() => OpenStandardHandle(2);
99+
100+
private static SafeFileHandle OpenStandardHandle(IntPtr fd) => new SafeFileHandle(fd, ownsHandle: false);
101+
94102
public static Encoding InputEncoding => throw new PlatformNotSupportedException();
95103

96104
public static void SetConsoleInputEncoding(Encoding enc) => throw new PlatformNotSupportedException();

src/libraries/System.Console/src/System/ConsolePal.Unix.cs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,28 @@ internal static partial class ConsolePal
4545

4646
public static Stream OpenStandardInput()
4747
{
48-
return new UnixConsoleStream(Interop.CheckIo(Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDIN_FILENO)), FileAccess.Read,
48+
return new UnixConsoleStream(OpenStandardInputHandle(), FileAccess.Read,
4949
useReadLine: !Console.IsInputRedirected);
5050
}
5151

5252
public static Stream OpenStandardOutput()
5353
{
54-
return new UnixConsoleStream(Interop.CheckIo(Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDOUT_FILENO)), FileAccess.Write);
54+
return new UnixConsoleStream(OpenStandardOutputHandle(), FileAccess.Write);
5555
}
5656

5757
public static Stream OpenStandardError()
5858
{
59-
return new UnixConsoleStream(Interop.CheckIo(Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDERR_FILENO)), FileAccess.Write);
59+
return new UnixConsoleStream(OpenStandardErrorHandle(), FileAccess.Write);
6060
}
6161

62+
public static SafeFileHandle OpenStandardInputHandle() => OpenStandardHandle(0);
63+
64+
public static SafeFileHandle OpenStandardOutputHandle() => OpenStandardHandle(1);
65+
66+
public static SafeFileHandle OpenStandardErrorHandle() => OpenStandardHandle(2);
67+
68+
private static SafeFileHandle OpenStandardHandle(IntPtr fd) => new SafeFileHandle(fd, ownsHandle: false);
69+
6270
public static Encoding InputEncoding
6371
{
6472
get { return GetConsoleEncoding(); }
@@ -660,7 +668,7 @@ static void TransferBytes(ReadOnlySpan<byte> src, StdInReader dst)
660668
/// Gets whether the specified file descriptor was redirected.
661669
/// It's considered redirected if it doesn't refer to a terminal.
662670
/// </summary>
663-
private static bool IsHandleRedirected(SafeFileHandle fd)
671+
private static bool IsHandleRedirected(IntPtr fd)
664672
{
665673
return !Interop.Sys.IsATty(fd);
666674
}
@@ -671,23 +679,23 @@ private static bool IsHandleRedirected(SafeFileHandle fd)
671679
/// </summary>
672680
public static bool IsInputRedirectedCore()
673681
{
674-
return IsHandleRedirected(Interop.Sys.FileDescriptors.STDIN_FILENO);
682+
return IsHandleRedirected(0);
675683
}
676684

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

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

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

892-
s_terminalHandle = !Console.IsOutputRedirected ? Interop.Sys.FileDescriptors.STDOUT_FILENO :
893-
!Console.IsInputRedirected ? Interop.Sys.FileDescriptors.STDIN_FILENO :
900+
s_terminalHandle = !Console.IsOutputRedirected ? OpenStandardOutputHandle() :
901+
!Console.IsInputRedirected ? OpenStandardInputHandle() :
894902
null;
895903

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

11131121
/// <summary>Writes a terminfo-based ANSI escape string to stdout.</summary>
11141122
/// <param name="value">The string to write.</param>

src/libraries/System.Console/src/System/ConsolePal.Wasi.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,28 @@ internal static partial class ConsolePal
2020
{
2121
public static Stream OpenStandardInput()
2222
{
23-
return new UnixConsoleStream(Interop.Sys.FileDescriptors.STDIN_FILENO, FileAccess.Read,
23+
return new UnixConsoleStream(OpenStandardInputHandle(), FileAccess.Read,
2424
useReadLine: !Console.IsInputRedirected);
2525
}
2626

2727
public static Stream OpenStandardOutput()
2828
{
29-
return new UnixConsoleStream(Interop.Sys.FileDescriptors.STDOUT_FILENO, FileAccess.Write);
29+
return new UnixConsoleStream(OpenStandardOutputHandle(), FileAccess.Write);
3030
}
3131

3232
public static Stream OpenStandardError()
3333
{
34-
return new UnixConsoleStream(Interop.Sys.FileDescriptors.STDERR_FILENO, FileAccess.Write);
34+
return new UnixConsoleStream(OpenStandardErrorHandle(), FileAccess.Write);
3535
}
3636

37+
public static SafeFileHandle OpenStandardInputHandle() => OpenStandardHandle(0);
38+
39+
public static SafeFileHandle OpenStandardOutputHandle() => OpenStandardHandle(1);
40+
41+
public static SafeFileHandle OpenStandardErrorHandle() => OpenStandardHandle(2);
42+
43+
private static SafeFileHandle OpenStandardHandle(IntPtr fd) => new SafeFileHandle(fd, ownsHandle: false);
44+
3745
public static Encoding InputEncoding
3846
{
3947
get { return GetConsoleEncoding(); }

src/libraries/System.Console/src/System/ConsolePal.Windows.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.IO;
66
using System.Runtime.InteropServices;
77
using System.Text;
8+
using Microsoft.Win32.SafeHandles;
89

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

94+
public static SafeFileHandle OpenStandardInputHandle() => OpenStandardHandle(Interop.Kernel32.HandleTypes.STD_INPUT_HANDLE);
95+
96+
public static SafeFileHandle OpenStandardOutputHandle() => OpenStandardHandle(Interop.Kernel32.HandleTypes.STD_OUTPUT_HANDLE);
97+
98+
public static SafeFileHandle OpenStandardErrorHandle() => OpenStandardHandle(Interop.Kernel32.HandleTypes.STD_ERROR_HANDLE);
99+
100+
private static SafeFileHandle OpenStandardHandle(int handleType)
101+
{
102+
IntPtr handle = Interop.Kernel32.GetStdHandle(handleType);
103+
return new SafeFileHandle(handle, ownsHandle: false);
104+
}
105+
93106
public static Encoding InputEncoding
94107
{
95108
get { return EncodingHelper.GetSupportedConsoleEncoding((int)Interop.Kernel32.GetConsoleCP()); }

0 commit comments

Comments
 (0)