-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Console.Unix: for echoing, write back to the terminal instead of writing to Console.Out. #94414
Conversation
…ing to Console.Out. On Unix .NET implements its own echo to make Console behave similar to Windows. The echo implementation was writing to standard output, which has the unintended effect of writing the echo characters into a redirected standard output. This changes to write the echo to the stdin terminal where they were read from.
Tagging subscribers to this area: @dotnet/area-system-console Issue DetailsOn Unix .NET implements its own echo to make Console behave similar to Windows. The echo implementation was writing to standard output, which has the unintended effect of writing the echo characters into a redirected standard output. This changes to write the echo to the stdin terminal where they were read from. Fixes #22314. @adamsitnik @stephentoub ptal.
|
I need to make some additional changes so the characters that are written back for echo use the proper encoding. |
This is done. The PR is up for review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix is great! It's relatively simple for the bug that has been hunting us for years! 👍
I added some comments for improving perf. It would be great to address them before merging.
Thank you @tmds !
SetCursorPosition(Interop.Sys.FileDescriptors.STDOUT_FILENO, left, top); | ||
} | ||
|
||
internal static void SetCursorPosition(SafeFileHandle terminalHandle, int left, int top) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you tested all scenarios that include calling SetCursorPosition
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran the manual tests and I saw no regression.
I will try some additional scenarios this week.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the PR so it prefers using stdout as a terminal handle to remain closer to the current behavior.
I also made the cursor/window size getting to work against the stdin handle.
…or stdin terminal handle.
{ | ||
return; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adamsitnik does this look good to you? I wrote it this way to have the bytes
Span
is in the same scope as the WriteToTerminal
call.
I want to highlight a specific special case which will now throw. When standard output is redirected, and standard input is a read-only terminal handle,
This seems appropriate as we don't have a way to echo the characters to. |
public static Stream OpenStandardInput() | ||
{ | ||
return new UnixConsoleStream(Interop.CheckIo(Interop.Sys.FileDescriptors.STDIN_FILENO), FileAccess.Read, | ||
return new UnixConsoleStream(Interop.Sys.FileDescriptors.STDIN_FILENO, FileAccess.Read, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what the Interop.CheckIo
is about, so I removed it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not familiar with this particular overload, let me share it here think loud about that:
runtime/src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs
Lines 86 to 105 in 487d7f0
/// <summary> | |
/// Validates the result of system call that returns greater than or equal to 0 on success | |
/// and less than 0 on failure, with errno set to the error code. | |
/// If the system call failed for any reason, an exception is thrown. Otherwise, the system call succeeded. | |
/// </summary> | |
/// <returns> | |
/// On success, returns the valid SafeFileHandle that was validated. | |
/// </returns> | |
internal static TSafeHandle CheckIo<TSafeHandle>(TSafeHandle handle, string? path = null, bool isDirError = false) | |
where TSafeHandle : SafeHandle | |
{ | |
if (handle.IsInvalid) | |
{ | |
Exception e = Interop.GetExceptionForIoErrno(Sys.GetLastErrorInfo(), path, isDirError); | |
handle.Dispose(); | |
throw e; | |
} | |
return handle; | |
} |
- The xml comment does not describe what it does.
- If provided handle is invalid, then it gets last error for the current thread and throws an exception.
I don't think that we need such check here, as we have not performed any IO yet (the handle is created by providing a constant number: 0, 1 or 2, it's not a result of a sys-call), so there is no last error that makes sense. It can become invalid once we try to use it in a sys-call, but other layers are ready for that.
So I am fine with removing these checks (and the method itself if it's not used elsewhere) 👍
@@ -254,17 +250,21 @@ private static unsafe int Read(SafeFileHandle fd, Span<byte> buffer) | |||
} | |||
} | |||
|
|||
internal static unsafe void WriteFromConsoleStream(SafeFileHandle fd, ReadOnlySpan<byte> buffer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These other changes sync with the .Unix
file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thank you very much @tmds!
public static Stream OpenStandardInput() | ||
{ | ||
return new UnixConsoleStream(Interop.CheckIo(Interop.Sys.FileDescriptors.STDIN_FILENO), FileAccess.Read, | ||
return new UnixConsoleStream(Interop.Sys.FileDescriptors.STDIN_FILENO, FileAccess.Read, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not familiar with this particular overload, let me share it here think loud about that:
runtime/src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs
Lines 86 to 105 in 487d7f0
/// <summary> | |
/// Validates the result of system call that returns greater than or equal to 0 on success | |
/// and less than 0 on failure, with errno set to the error code. | |
/// If the system call failed for any reason, an exception is thrown. Otherwise, the system call succeeded. | |
/// </summary> | |
/// <returns> | |
/// On success, returns the valid SafeFileHandle that was validated. | |
/// </returns> | |
internal static TSafeHandle CheckIo<TSafeHandle>(TSafeHandle handle, string? path = null, bool isDirError = false) | |
where TSafeHandle : SafeHandle | |
{ | |
if (handle.IsInvalid) | |
{ | |
Exception e = Interop.GetExceptionForIoErrno(Sys.GetLastErrorInfo(), path, isDirError); | |
handle.Dispose(); | |
throw e; | |
} | |
return handle; | |
} |
- The xml comment does not describe what it does.
- If provided handle is invalid, then it gets last error for the current thread and throws an exception.
I don't think that we need such check here, as we have not performed any IO yet (the handle is created by providing a constant number: 0, 1 or 2, it's not a result of a sys-call), so there is no last error that makes sense. It can become invalid once we try to use it in a sys-call, but other layers are ready for that.
So I am fine with removing these checks (and the method itself if it's not used elsewhere) 👍
I am OK with that. I've applied |
@adamsitnik did you create a breaking change doc for this? |
On Unix .NET implements its own echo to make Console behave similar to Windows.
The echo implementation was writing to standard output, which has the unintended effect of writing the echo characters into a redirected standard output.
This changes to write the echo to the stdin terminal where they were read from.
Fixes #22314.
@adamsitnik @stephentoub ptal.