-
Notifications
You must be signed in to change notification settings - Fork 13.2k
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
Changes from all commits
e884ec7
645eadd
e7f6e76
16194b5
37ae51e
03f076f
a82bae5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -1018,6 +1018,59 @@ where | |||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
/// Trait to determine if stdio stream (Stdin, Stdout, Stderr) is a terminal/tty. | ||||||||||
#[unstable(feature = "is_terminal", issue = "80937")] | ||||||||||
pub trait IsTerminal { | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
/// returns true if stdio stream (Stdin, Stdout, Stderr) is a terminal/tty. | ||||||||||
fn is_terminal() -> bool; | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should maybe return an
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it'd be appropriate to return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This would allow for implementations that might return errors when attempting to determine this. |
||||||||||
} | ||||||||||
|
||||||||||
cfg_if::cfg_if! { | ||||||||||
if #[cfg(any(unix, windows))] { | ||||||||||
#[unstable(feature = "is_terminal", issue = "80937")] | ||||||||||
impl IsTerminal for Stdin { | ||||||||||
fn is_terminal() -> bool { | ||||||||||
stdio::Stdin::is_terminal() | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
#[unstable(feature = "is_terminal", issue = "80937")] | ||||||||||
impl IsTerminal for Stdout { | ||||||||||
fn is_terminal() -> bool { | ||||||||||
stdio::Stdout::is_terminal() | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
#[unstable(feature = "is_terminal", issue = "80937")] | ||||||||||
impl IsTerminal for Stderr { | ||||||||||
fn is_terminal() -> bool { | ||||||||||
stdio::Stderr::is_terminal() | ||||||||||
} | ||||||||||
} | ||||||||||
} else { | ||||||||||
#[unstable(feature = "is_terminal", issue = "80937")] | ||||||||||
impl IsTerminal for Stdin { | ||||||||||
fn is_terminal() -> bool { | ||||||||||
false | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
#[unstable(feature = "is_terminal", issue = "80937")] | ||||||||||
impl IsTerminal for Stdout { | ||||||||||
fn is_terminal() -> bool { | ||||||||||
false | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
#[unstable(feature = "is_terminal", issue = "80937")] | ||||||||||
impl IsTerminal for Stderr { | ||||||||||
fn is_terminal() -> bool { | ||||||||||
false | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
#[unstable( | ||||||||||
feature = "print_internals", | ||||||||||
reason = "implementation detail which may disappear or be replaced at any time", | ||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The types in |
||
fn is_terminal() -> bool { | ||
abi::isatty(abi::STDIN_FILENO) | ||
} | ||
} | ||
|
||
#[unstable(feature = "is_terminal", issue = "80937")] | ||
impl io::IsTerminal for sys::stdio::Stdout { | ||
fn is_terminal() -> bool { | ||
abi::isatty(abi::STDOUT_FILENO) | ||
} | ||
} | ||
#[unstable(feature = "is_terminal", issue = "80937")] | ||
impl io::IsTerminal for sys::stdio::Stderr { | ||
fn is_terminal() -> bool { | ||
abi::isatty(abi::STDERR_FILENO) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
use crate::io; | ||
use crate::marker::PhantomData; | ||
use crate::slice; | ||
|
||
use crate::sys; | ||
use libc::{c_void, iovec}; | ||
|
||
#[derive(Copy, Clone)] | ||
|
@@ -74,3 +75,23 @@ impl<'a> IoSliceMut<'a> { | |
unsafe { slice::from_raw_parts_mut(self.vec.iov_base as *mut u8, self.vec.iov_len) } | ||
} | ||
} | ||
|
||
#[unstable(feature = "is_terminal", issue = "80937")] | ||
impl io::IsTerminal for sys::stdio::Stdin { | ||
fn is_terminal() -> bool { | ||
unsafe { libc::isatty(libc::STDIN_FILENO) != 0 } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't this return true in case of an error? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
} | ||
} | ||
|
||
#[unstable(feature = "is_terminal", issue = "80937")] | ||
impl io::IsTerminal for sys::stdio::Stdout { | ||
fn is_terminal() -> bool { | ||
unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 } | ||
} | ||
} | ||
#[unstable(feature = "is_terminal", issue = "80937")] | ||
impl io::IsTerminal for sys::stdio::Stderr { | ||
fn is_terminal() -> bool { | ||
unsafe { libc::isatty(libc::STDERR_FILENO) != 0 } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,10 @@ | ||
use crate::io; | ||
use crate::marker::PhantomData; | ||
use crate::slice; | ||
use crate::sys; | ||
use crate::sys::c; | ||
use core; | ||
use libc; | ||
|
||
#[derive(Copy, Clone)] | ||
#[repr(transparent)] | ||
|
@@ -78,3 +82,120 @@ impl<'a> IoSliceMut<'a> { | |
unsafe { slice::from_raw_parts_mut(self.vec.buf as *mut u8, self.vec.len as usize) } | ||
} | ||
} | ||
|
||
#[unstable(feature = "is_terminal", issue = "80937")] | ||
impl io::IsTerminal for sys::stdio::Stdin { | ||
fn is_terminal() -> bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
let fd = c::STD_INPUT_HANDLE; | ||
let others = [c::STD_ERROR_HANDLE, c::STD_OUTPUT_HANDLE]; | ||
|
||
if unsafe { console_on_any(&[fd]) } { | ||
// False positives aren't possible. If we got a console then | ||
// we definitely have a tty on stdin. | ||
return true; | ||
} | ||
|
||
// At this point, we *could* have a false negative. We can determine that | ||
// this is true negative if we can detect the presence of a console on | ||
// any of the other streams. If another stream has a console, then we know | ||
// we're in a Windows console and can therefore trust the negative. | ||
if unsafe { console_on_any(&others) } { | ||
return false; | ||
} | ||
|
||
// Otherwise, we fall back to a very strange msys hack to see if we can | ||
// sneakily detect the presence of a tty. | ||
unsafe { msys_tty_on(fd) } | ||
} | ||
} | ||
|
||
#[unstable(feature = "is_terminal", issue = "80937")] | ||
impl io::IsTerminal for sys::stdio::Stdout { | ||
fn is_terminal() -> bool { | ||
let fd = c::STD_OUTPUT_HANDLE; | ||
let others = [c::STD_INPUT_HANDLE, c::STD_ERROR_HANDLE]; | ||
|
||
if unsafe { console_on_any(&[fd]) } { | ||
// False positives aren't possible. If we got a console then | ||
// we definitely have a tty on stdout. | ||
return true; | ||
} | ||
|
||
// At this point, we *could* have a false negative. We can determine that | ||
// this is true negative if we can detect the presence of a console on | ||
// any of the other streams. If another stream has a console, then we know | ||
// we're in a Windows console and can therefore trust the negative. | ||
if unsafe { console_on_any(&others) } { | ||
return false; | ||
} | ||
|
||
// Otherwise, we fall back to a very strange msys hack to see if we can | ||
// sneakily detect the presence of a tty. | ||
unsafe { msys_tty_on(fd) } | ||
} | ||
} | ||
|
||
#[unstable(feature = "is_terminal", issue = "80937")] | ||
impl io::IsTerminal for sys::stdio::Stderr { | ||
fn is_terminal() -> bool { | ||
let fd = c::STD_ERROR_HANDLE; | ||
let others = [c::STD_INPUT_HANDLE, c::STD_OUTPUT_HANDLE]; | ||
|
||
if unsafe { console_on_any(&[fd]) } { | ||
// False positives aren't possible. If we got a console then | ||
// we definitely have a tty on stderr. | ||
return true; | ||
} | ||
|
||
// At this point, we *could* have a false negative. We can determine that | ||
// this is true negative if we can detect the presence of a console on | ||
// any of the other streams. If another stream has a console, then we know | ||
// we're in a Windows console and can therefore trust the negative. | ||
if unsafe { console_on_any(&others) } { | ||
return false; | ||
} | ||
|
||
// Otherwise, we fall back to a very strange msys hack to see if we can | ||
// sneakily detect the presence of a tty. | ||
unsafe { msys_tty_on(fd) } | ||
} | ||
} | ||
|
||
#[unstable(feature = "is_terminal", issue = "80937")] | ||
unsafe fn console_on_any(fds: &[c::DWORD]) -> bool { | ||
for &fd in fds { | ||
let mut out = 0; | ||
let handle = c::GetStdHandle(fd); | ||
if c::GetConsoleMode(handle, &mut out) != 0 { | ||
return true; | ||
} | ||
} | ||
false | ||
} | ||
#[unstable(feature = "is_terminal", issue = "80937")] | ||
unsafe fn msys_tty_on(fd: c::DWORD) -> bool { | ||
let size = core::mem::size_of::<c::FILE_NAME_INFO>(); | ||
let mut name_info_bytes = vec![0u8; size + c::MAX_PATH * core::mem::size_of::<c::WCHAR>()]; | ||
let res = c::GetFileInformationByHandleEx( | ||
c::GetStdHandle(fd), | ||
c::FileNameInfo, | ||
&mut *name_info_bytes as *mut _ as *mut libc::c_void, | ||
name_info_bytes.len() as u32, | ||
); | ||
if res == 0 { | ||
return false; | ||
} | ||
let name_info: &c::FILE_NAME_INFO = &*(name_info_bytes.as_ptr() as *const c::FILE_NAME_INFO); | ||
let s = core::slice::from_raw_parts( | ||
name_info.FileName.as_ptr(), | ||
name_info.FileNameLength as usize / 2, | ||
); | ||
let name = String::from_utf16_lossy(s); | ||
// This checks whether 'pty' exists in the file name, which indicates that | ||
// a pseudo-terminal is attached. To mitigate against false positives | ||
// (e.g., an actual file name that contains 'pty'), we also require that | ||
// either the strings 'msys-' or 'cygwin-' are in the file name as well.) | ||
let is_msys = name.contains("msys-") || name.contains("cygwin-"); | ||
let is_pty = name.contains("-pty"); | ||
is_msys && is_pty | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,5 @@ | |
|
||
pub mod concurrency; | ||
pub mod exit_code; | ||
pub mod isatty; | ||
pub mod metrics; | ||
pub mod shuffle; |
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.
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).
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 don't see a scenario in which user code would need to implement this trait, so it could just be converted to regular
impl
s if that is easier. I originally created it as a trait after a recommendation from @bjorn3: #81807 (comment)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 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.