Skip to content
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

io/os: Implement IsTerminal trait on Stdio #91121

Closed
wants to merge 7 commits into from

Conversation

ghost
Copy link

@ghost ghost commented Nov 22, 2021

Supersedes #81807
Fixes #80937

I've reimplemented the changes off of the latest master as the rebase on the old PR was causing issues.

This PR adds a IsTerminal trait to Stdin, Stdout, and Stderr for the Unix, Windows, and Hermit platforms. This allows the stdlib's CLI tests to make use of this trait instead of a custom isatty implementation contained only within the testing crate.

I also incorporated @r00ster91's feedback from the original PR on using STD_INPUT_HANDLE and such directly instead of importing and renaming them.

@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @m-ou-se (or someone else) soon.

Please see the contribution instructions for more information.

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Nov 22, 2021
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

Copy link
Member

@Urgau Urgau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was passing by so I thought I would give my impression of your PR.

PS: I'm not a Rust reviewer or maintainer, just a random guy. 😄

@@ -1202,6 +1202,59 @@ where
}
}

/// Trait to determine if stdio stream (Stdin, Stdout, Stderr) is a terminal/tty.
#[unstable(feature = "is_terminal", issue = "80937")]
pub trait IsTerminal {
Copy link
Member

@Urgau Urgau Nov 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you using a trait ? Are you expecting that user code will want to implement that trait ? (if not it must be sealed with a private module).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a scenario in which user code would need to implement this trait, so it could just be converted to regular impls if that is easier. I originally created it as a trait after a recommendation from @bjorn3: #81807 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think this should start out as a sealed trait. I can imagine reasons why people might want to implement it in the future, but we should be able to make the decision about exposing the trait and its method independently of the decision about letting people implement it outside the standard library.

#[unstable(feature = "is_terminal", issue = "80937")]
pub trait IsTerminal {
/// returns true if stdio stream (Stdin, Stdout, Stderr) is a terminal/tty.
fn is_terminal() -> bool;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should maybe return an Option of bool where None will be returned when we are unable to determine if it's either a terminal or not.

Suggested change
fn is_terminal() -> bool;
fn is_terminal() -> Option<bool>;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a solution to the Windows hack problem. Instead of falling back to the msys_tty_on hack, we just return None? Although I'm not sure how useful this would be to the end user, as I'm assuming that any Nones would have to be treated the same as Some(false) in the calling code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be appropriate to return io::Result<bool> here. If we get the expected error that indicates "not a terminal" we should return false, but if we get an unexpected error we should return that error

#[unstable(feature = "is_terminal", issue = "80937")]
impl io::IsTerminal for sys::stdio::Stdin {
fn is_terminal() -> bool {
unsafe { libc::isatty(libc::STDIN_FILENO) != 0 }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this return true in case of an error?

Copy link
Author

@ghost ghost Nov 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The man page indicates that this function can only return 0 or 1: https://linux.die.net/man/3/isatty

I could make it libc::isatty(libc::STDIN_FILENO) == 1 to be more explicit?

@ChrisDenton
Copy link
Member

I'm not sure I understand the intent of the Windows code. It returns true if any handle is a console handle? That doesn't seem right? What if, say, Stdout has been redirected? Or is null?

I'm also somewhat concerned about having the msys hack in the standard library as it looks like it's purely based on the file name. Though I'm not sure how much of a problem this will be in practice.

@bjorn3
Copy link
Member

bjorn3 commented Nov 22, 2021

I'm not sure I understand the intent of the Windows code. It returns true if any handle is a console handle? That doesn't seem right? What if, say, Stdout has been redirected? Or is null?

I think the intent is that if any handle is a console handle, then we are running inside a non-msys terminal, so if say stdout is not a console handle, then we know for a fact that it is redirected. If all handles are not console handles, then we may be running inside an msys terminal, so we need to use the filename hack to check if this is the case. Stdin and stdout and stderr are rarely redirected at the same time, right? So the filename hack should rarely be used.

@ChrisDenton
Copy link
Member

Ah, thanks I get it now. I guess what I'm struggling with is how reliable these heuristics are and in what situations they're needed.

@ChrisDenton
Copy link
Member

ChrisDenton commented Nov 22, 2021

For prior art, git uses GetFileType instead to check if the handle is a pipe. Only then does it do a test for special names. Source.

@ghost
Copy link
Author

ghost commented Nov 22, 2021

Ah, thanks I get it now. I guess what I'm struggling with is how reliable these heuristics are and in what situations they're needed.

The code for this is basically lifted from the atty crate: https://github.com/softprops/atty/blob/master/src/lib.rs.

I'm not an expert on the ins and outs of Windows terminals unfortunately, so i'll have to defer to those who know better.

@BurntSushi was the original author of the Windows logic, and explains the reasoning here #81807 (comment). He also mentions how git does it.

I suppose the biggest issue is if we are OK with having a heuristic-y detection inside the standard library. But considering git does something very similar, there doesn't seem to be another option besides just not having this functionality at all, or at least not on Windows.

@BurntSushi
Copy link
Member

BurntSushi commented Nov 22, 2021

@BurntSushi was the original author of the Windows logic, and explains the reasoning here #81807 (comment). He also mentions how git does it.

Right. I got the logic straight from git.

Folks are right to be skeptical of it. It's quite precarious looking, and it certainly is not bullet proof. But, it works well enough in practice. I do think it's probably tied to whether we expose this sort of functionality at all in the first place. If we expose something that doesn't work in some particularly common cases on Windows, then I think that just wouldn't be good because probably folks wouldn't use it or have to invent their own work-arounds.

@ChrisDenton
Copy link
Member

He also mentions how git does it.

Huh, I guess the git code has changed slightly. Or I'm looking at something different.

Side note: I've been testing this a bit. It seems that the version of msys used by "Git Bash" on Windows uses winpty so that non-msys applications run as expected (or at least on my install of it does). This confused me at first because I couldn't initially reproduce the issue until I switched to a clean install of msys2.

@BurntSushi
Copy link
Member

BurntSushi commented Nov 22, 2021 via email

@ChrisDenton
Copy link
Member

No worries. The core part of testing the name is the same in any case. I'm trying to see if there is any precedent in other language standard libraries to see if they are of any help.

@apiraino apiraino added the T-libs Relevant to the library team, which will review and decide on the PR/issue. label Nov 22, 2021
@ghost
Copy link
Author

ghost commented Dec 2, 2021

@m-ou-se @bjorn3 etc: Did you have any comments on this PR? Not sure what the next steps are for this.

@ChrisDenton
Copy link
Member

If it helps, I don't think my reservations should be a blocker to adding this unstably. It could be mentioned as a concern in the tracking issue or the msys heuristics could be put in a separate PR and discussed independently. I'd personally be ok with either.

@ghost
Copy link
Author

ghost commented Dec 10, 2021

r? @Amanieu

@rust-highfive rust-highfive assigned Amanieu and unassigned m-ou-se Dec 10, 2021
@dtolnay dtolnay added T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. and removed T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Dec 10, 2021
#[unstable(feature = "is_terminal", issue = "80937")]
pub trait IsTerminal {
/// returns true if stdio stream (Stdin, Stdout, Stderr) is a terminal/tty.
fn is_terminal() -> bool;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn is_terminal() -> bool;
fn is_terminal() -> io::Result<bool>;

This would allow for implementations that might return errors when attempting to determine this.

@joshtriplett
Copy link
Member

I do think this trait needs to be sealed before we add it.

I also personally think it should return an io::Result<bool>, to allow for implementations where that determination could return an error. In particular, that could happen if called on a bad file descriptor.

In the future, I'd love to see implementations of this trait for BorrowedFd and OwnedFd, but that doesn't need to happen in the initial PR.


#[unstable(feature = "is_terminal", issue = "80937")]
impl io::IsTerminal for sys::stdio::Stdin {
fn is_terminal() -> bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of duplication here, this should be factored out into a method that takes fd and others as arguments.

@@ -118,3 +118,23 @@ pub fn is_ebadf(_err: &io::Error) -> bool {
pub fn panic_output() -> Option<impl io::Write> {
Some(Stderr::new())
}

#[unstable(feature = "is_terminal", issue = "80937")]
impl io::IsTerminal for sys::stdio::Stdin {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The types in sys are private, so there is no need to use a trait here. These can just be inherent methods on the private type.

@JohnCSimon JohnCSimon added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jul 24, 2022
joshtriplett added a commit to joshtriplett/rust that referenced this pull request Aug 25, 2022
…rminal

The UNIX and WASI implementations use `isatty`. The Windows
implementation uses the same logic the `atty` crate uses, including the
hack needed to detect msys terminals.

Implement this trait for `File` and for `Stdin`/`Stdout`/`Stderr` and
their locked counterparts on all platforms. On UNIX and WASI, implement
it for `BorrowedFd`/`OwnedFd`. On Windows, implement it for
`BorrowedHandle`/`OwnedHandle`.

Based on rust-lang#91121

Co-authored-by: Matt Wilkinson <[email protected]>
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this pull request Aug 26, 2022
… r=thomcc

Add `IsTerminal` trait to determine if a descriptor or handle is a terminal

The UNIX implementation uses `isatty`. The Windows implementation uses
the same logic the `atty` crate uses, including the hack needed to
detect msys terminals.

Implement this trait for `Stdin`/`Stdout`/`Stderr`/`File` on all
platforms. On Unix, implement it for `BorrowedFd`/`OwnedFd`. On Windows,
implement it for `BorrowedHandle`/`OwnedHandle`.

Based on rust-lang#91121

Co-authored-by: Matt Wilkinson <[email protected]>
joshtriplett added a commit to joshtriplett/rust that referenced this pull request Aug 26, 2022
…rminal

The UNIX and WASI implementations use `isatty`. The Windows
implementation uses the same logic the `atty` crate uses, including the
hack needed to detect msys terminals.

Implement this trait for `File` and for `Stdin`/`Stdout`/`Stderr` and
their locked counterparts on all platforms. On UNIX and WASI, implement
it for `BorrowedFd`/`OwnedFd`. On Windows, implement it for
`BorrowedHandle`/`OwnedHandle`.

Based on rust-lang#91121

Co-authored-by: Matt Wilkinson <[email protected]>
compiler-errors added a commit to compiler-errors/rust that referenced this pull request Aug 27, 2022
… r=thomcc

Add `IsTerminal` trait to determine if a descriptor or handle is a terminal

The UNIX implementation uses `isatty`. The Windows implementation uses
the same logic the `atty` crate uses, including the hack needed to
detect msys terminals.

Implement this trait for `Stdin`/`Stdout`/`Stderr`/`File` on all
platforms. On Unix, implement it for `BorrowedFd`/`OwnedFd`. On Windows,
implement it for `BorrowedHandle`/`OwnedHandle`.

Based on rust-lang#91121

Co-authored-by: Matt Wilkinson <[email protected]>
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this pull request Aug 28, 2022
… r=thomcc

Add `IsTerminal` trait to determine if a descriptor or handle is a terminal

The UNIX implementation uses `isatty`. The Windows implementation uses
the same logic the `atty` crate uses, including the hack needed to
detect msys terminals.

Implement this trait for `Stdin`/`Stdout`/`Stderr`/`File` on all
platforms. On Unix, implement it for `BorrowedFd`/`OwnedFd`. On Windows,
implement it for `BorrowedHandle`/`OwnedHandle`.

Based on rust-lang#91121

Co-authored-by: Matt Wilkinson <[email protected]>
joshtriplett added a commit to joshtriplett/rust that referenced this pull request Sep 9, 2022
…rminal

The UNIX and WASI implementations use `isatty`. The Windows
implementation uses the same logic the `atty` crate uses, including the
hack needed to detect msys terminals.

Implement this trait for `File` and for `Stdin`/`Stdout`/`Stderr` and
their locked counterparts on all platforms. On UNIX and WASI, implement
it for `BorrowedFd`/`OwnedFd`. On Windows, implement it for
`BorrowedHandle`/`OwnedHandle`.

Based on rust-lang#91121

Co-authored-by: Matt Wilkinson <[email protected]>
@Dylan-DPC
Copy link
Member

Closing this due to inactivity

@Dylan-DPC Dylan-DPC closed this Sep 10, 2022
@Dylan-DPC Dylan-DPC added S-inactive Status: Inactive and waiting on the author. This is often applied to closed PRs. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Sep 10, 2022
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this pull request Sep 20, 2022
… r=thomcc

Add `IsTerminal` trait to determine if a descriptor or handle is a terminal

The UNIX implementation uses `isatty`. The Windows implementation uses
the same logic the `atty` crate uses, including the hack needed to
detect msys terminals.

Implement this trait for `Stdin`/`Stdout`/`Stderr`/`File` on all
platforms. On Unix, implement it for `BorrowedFd`/`OwnedFd`. On Windows,
implement it for `BorrowedHandle`/`OwnedHandle`.

Based on rust-lang#91121

Co-authored-by: Matt Wilkinson <[email protected]>
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this pull request Sep 24, 2022
… r=thomcc

Add `IsTerminal` trait to determine if a descriptor or handle is a terminal

The UNIX implementation uses `isatty`. The Windows implementation uses
the same logic the `atty` crate uses, including the hack needed to
detect msys terminals.

Implement this trait for `Stdin`/`Stdout`/`Stderr`/`File` on all
platforms. On Unix, implement it for `BorrowedFd`/`OwnedFd`. On Windows,
implement it for `BorrowedHandle`/`OwnedHandle`.

Based on rust-lang#91121

Co-authored-by: Matt Wilkinson <[email protected]>
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this pull request Sep 24, 2022
… r=thomcc

Add `IsTerminal` trait to determine if a descriptor or handle is a terminal

The UNIX implementation uses `isatty`. The Windows implementation uses
the same logic the `atty` crate uses, including the hack needed to
detect msys terminals.

Implement this trait for `Stdin`/`Stdout`/`Stderr`/`File` on all
platforms. On Unix, implement it for `BorrowedFd`/`OwnedFd`. On Windows,
implement it for `BorrowedHandle`/`OwnedHandle`.

Based on rust-lang#91121

Co-authored-by: Matt Wilkinson <[email protected]>
joshtriplett added a commit to joshtriplett/rust that referenced this pull request Oct 7, 2022
…rminal

The UNIX and WASI implementations use `isatty`. The Windows
implementation uses the same logic the `atty` crate uses, including the
hack needed to detect msys terminals.

Implement this trait for `File` and for `Stdin`/`Stdout`/`Stderr` and
their locked counterparts on all platforms. On UNIX and WASI, implement
it for `BorrowedFd`/`OwnedFd`. On Windows, implement it for
`BorrowedHandle`/`OwnedHandle`.

Based on rust-lang#91121

Co-authored-by: Matt Wilkinson <[email protected]>
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this pull request Oct 8, 2022
… r=thomcc

Add `IsTerminal` trait to determine if a descriptor or handle is a terminal

The UNIX implementation uses `isatty`. The Windows implementation uses
the same logic the `atty` crate uses, including the hack needed to
detect msys terminals.

Implement this trait for `Stdin`/`Stdout`/`Stderr`/`File` on all
platforms. On Unix, implement it for `BorrowedFd`/`OwnedFd`. On Windows,
implement it for `BorrowedHandle`/`OwnedHandle`.

Based on rust-lang#91121

Co-authored-by: Matt Wilkinson <[email protected]>
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Oct 8, 2022
… r=thomcc

Add `IsTerminal` trait to determine if a descriptor or handle is a terminal

The UNIX implementation uses `isatty`. The Windows implementation uses
the same logic the `atty` crate uses, including the hack needed to
detect msys terminals.

Implement this trait for `Stdin`/`Stdout`/`Stderr`/`File` on all
platforms. On Unix, implement it for `BorrowedFd`/`OwnedFd`. On Windows,
implement it for `BorrowedHandle`/`OwnedHandle`.

Based on rust-lang#91121

Co-authored-by: Matt Wilkinson <[email protected]>
joshtriplett added a commit to joshtriplett/rust that referenced this pull request Oct 14, 2022
…rminal

The UNIX and WASI implementations use `isatty`. The Windows
implementation uses the same logic the `atty` crate uses, including the
hack needed to detect msys terminals.

Implement this trait for `File` and for `Stdin`/`Stdout`/`Stderr` and
their locked counterparts on all platforms. On UNIX and WASI, implement
it for `BorrowedFd`/`OwnedFd`. On Windows, implement it for
`BorrowedHandle`/`OwnedHandle`.

Based on rust-lang#91121

Co-authored-by: Matt Wilkinson <[email protected]>
bors added a commit to rust-lang-ci/rust that referenced this pull request Oct 15, 2022
…=thomcc

Add `IsTerminal` trait to determine if a descriptor or handle is a terminal

The UNIX implementation uses `isatty`. The Windows implementation uses
the same logic the `atty` crate uses, including the hack needed to
detect msys terminals.

Implement this trait for `Stdin`/`Stdout`/`Stderr`/`File` on all
platforms. On Unix, implement it for `BorrowedFd`/`OwnedFd`. On Windows,
implement it for `BorrowedHandle`/`OwnedHandle`.

Based on rust-lang#91121

Co-authored-by: Matt Wilkinson <[email protected]>
lyming2007 pushed a commit to lyming2007/rust that referenced this pull request Oct 21, 2022
…rminal

The UNIX and WASI implementations use `isatty`. The Windows
implementation uses the same logic the `atty` crate uses, including the
hack needed to detect msys terminals.

Implement this trait for `File` and for `Stdin`/`Stdout`/`Stderr` and
their locked counterparts on all platforms. On UNIX and WASI, implement
it for `BorrowedFd`/`OwnedFd`. On Windows, implement it for
`BorrowedHandle`/`OwnedHandle`.

Based on rust-lang#91121

Co-authored-by: Matt Wilkinson <[email protected]>
thomcc pushed a commit to tcdi/postgrestd that referenced this pull request Feb 10, 2023
…rminal

The UNIX and WASI implementations use `isatty`. The Windows
implementation uses the same logic the `atty` crate uses, including the
hack needed to detect msys terminals.

Implement this trait for `File` and for `Stdin`/`Stdout`/`Stderr` and
their locked counterparts on all platforms. On UNIX and WASI, implement
it for `BorrowedFd`/`OwnedFd`. On Windows, implement it for
`BorrowedHandle`/`OwnedHandle`.

Based on rust-lang/rust#91121

Co-authored-by: Matt Wilkinson <[email protected]>
thomcc pushed a commit to tcdi/postgrestd that referenced this pull request Feb 10, 2023
Add `IsTerminal` trait to determine if a descriptor or handle is a terminal

The UNIX implementation uses `isatty`. The Windows implementation uses
the same logic the `atty` crate uses, including the hack needed to
detect msys terminals.

Implement this trait for `Stdin`/`Stdout`/`Stderr`/`File` on all
platforms. On Unix, implement it for `BorrowedFd`/`OwnedFd`. On Windows,
implement it for `BorrowedHandle`/`OwnedHandle`.

Based on rust-lang/rust#91121

Co-authored-by: Matt Wilkinson <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-inactive Status: Inactive and waiting on the author. This is often applied to closed PRs. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use atty crate in libtest instead of rolling our own