Repository: dotnet/runtime
File: src/libraries/System.Console/src/System/ConsolePal.Unix.cs (C#)

Code before:
        // with an interactive terminal.  In that case, performance isn't critical
        // and we can use a smaller buffer to minimize working set.
        private const int InteractiveBufferSize = 255;

        // For performance we cache Cursor{Left,Top} and Window{Width,Height}.
        // These values must be read/written under lock (Console.Out).
        // We also need to invalidate these values when certain signals occur.
        // We don't want to take the lock in the signal handling thread for this.
        // Instead, we set a flag. Before reading a cached value, a call to CheckTerminalSettingsInvalidated
        // will invalidate the cached values if a signal has occurred.
        private static int s_cursorVersion; // Gets incremented each time the cursor position changed.
                                            // Used to synchronize between lock (Console.Out) blocks.
        private static int s_cursorLeft;    // Cached CursorLeft, -1 when invalid.
        private static int s_cursorTop;     // Cached CursorTop, invalid when s_cursorLeft == -1.
        private static int s_windowWidth;   // Cached WindowWidth, -1 when invalid.
        private static int s_windowHeight;  // Cached WindowHeight, invalid when s_windowWidth == -1.
        private static int s_invalidateCachedSettings = 1; // Tracks whether we should invalidate the cached settings.
        private static SafeFileHandle? s_terminalHandle; // Tracks the handle used for writing to the terminal.

        /// <summary>Gets the lazily-initialized terminal information for the terminal.</summary>
        public static TerminalFormatStrings TerminalFormatStringsInstance { get { return s_terminalFormatStringsInstance.Value; } }
        private static readonly Lazy<TerminalFormatStrings> s_terminalFormatStringsInstance = new(() => new TerminalFormatStrings(TermInfo.DatabaseFactory.ReadActiveDatabase()));

        public static Stream OpenStandardInput()
        {
            return new UnixConsoleStream(OpenStandardInputHandle(), FileAccess.Read,
                                         useReadLine: !Console.IsInputRedirected);
        }

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

        public static Stream OpenStandardError()
        {
            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(); }
        }

        public static Encoding OutputEncoding

Review comment (suggestion):
@copilot Update: after thinking for a while I believe the `dup` was called to check whether `0` is valid file descriptor opened for reading and `1` and `2` are valid and opened for writing (on every OS it's possible to start process with invalid standard handles).

We need to keep that behavior. Please use `fcntl` to ensure these descriptors are valid.

My current best idea for that is to extend [Interop.Fcntl.cs](https://github.com/dotnet/runtime/blob/e991b26a8a104dd405cf05358af56e6b31367951/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Fcntl.cs)

with a new method:

```csharp
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_FcntIsValid", SetLastError = true)]
internal static partial bool IsValid(IntPtr fd, FileAccess fileAccess);
```

The native layer should try to open the fd with `fcntl`:

```c
int flags = fcntl(fd, F_GETFL);
if (flags == -1) {
    // invalid FD
}
```

In case the fd is valid, it should map the managed enum using constant values to the right C consts and check if the file was opened for reading/writing.





 

Code after:
        // with an interactive terminal.  In that case, performance isn't critical
        // and we can use a smaller buffer to minimize working set.
        private const int InteractiveBufferSize = 255;

        // For performance we cache Cursor{Left,Top} and Window{Width,Height}.
        // These values must be read/written under lock (Console.Out).
        // We also need to invalidate these values when certain signals occur.
        // We don't want to take the lock in the signal handling thread for this.
        // Instead, we set a flag. Before reading a cached value, a call to CheckTerminalSettingsInvalidated
        // will invalidate the cached values if a signal has occurred.
        private static int s_cursorVersion; // Gets incremented each time the cursor position changed.
                                            // Used to synchronize between lock (Console.Out) blocks.
        private static int s_cursorLeft;    // Cached CursorLeft, -1 when invalid.
        private static int s_cursorTop;     // Cached CursorTop, invalid when s_cursorLeft == -1.
        private static int s_windowWidth;   // Cached WindowWidth, -1 when invalid.
        private static int s_windowHeight;  // Cached WindowHeight, invalid when s_windowWidth == -1.
        private static int s_invalidateCachedSettings = 1; // Tracks whether we should invalidate the cached settings.
        private static SafeFileHandle? s_terminalHandle; // Tracks the handle used for writing to the terminal.

        /// <summary>Gets the lazily-initialized terminal information for the terminal.</summary>
        public static TerminalFormatStrings TerminalFormatStringsInstance { get { return s_terminalFormatStringsInstance.Value; } }
        private static readonly Lazy<TerminalFormatStrings> s_terminalFormatStringsInstance = new(() => new TerminalFormatStrings(TermInfo.DatabaseFactory.ReadActiveDatabase()));

        public static Stream OpenStandardInput()
        {
            return new UnixConsoleStream(OpenStandardInputHandle(), FileAccess.Read,
                                         useReadLine: !Console.IsInputRedirected);
        }

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

        public static Stream OpenStandardError()
        {
            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(); }
        }