diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 9625984195bd2..e97123574da42 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -273,6 +273,8 @@ pub use self::error::{Error, ErrorKind, Result}; #[unstable(feature = "internal_output_capture", issue = "none")] #[doc(no_inline, hidden)] pub use self::stdio::set_output_capture; +#[unstable(feature = "is_terminal", issue = "80937")] +pub use self::stdio::IsTerminal; #[stable(feature = "rust1", since = "1.0.0")] pub use self::stdio::{stderr, stdin, stdout, Stderr, Stdin, Stdout}; #[stable(feature = "rust1", since = "1.0.0")] diff --git a/library/std/src/io/stdio.rs b/library/std/src/io/stdio.rs index c2e0b24ba8327..eda7aacf42bd8 100644 --- a/library/std/src/io/stdio.rs +++ b/library/std/src/io/stdio.rs @@ -965,3 +965,57 @@ pub fn _eprint(args: fmt::Arguments<'_>) { #[cfg(test)] pub use realstd::io::{_eprint, _print}; + +/// Trait to determine if stdio stream (Stdin, Stdout, Stderr) is a terminal/tty. +#[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; +} + +#[unstable(feature = "is_terminal", issue = "80937")] +cfg_if::cfg_if! { + if #[cfg(any(unix, windows, hermit))] { + #[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 + } + } + } +} diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 089d43483fcb3..d6e7572dbfd7b 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -335,6 +335,7 @@ #![feature(unwind_attributes)] #![feature(vec_into_raw_parts)] #![feature(vec_spare_capacity)] +#![feature(is_terminal)] // NB: the above list is sorted to minimize merge conflicts. #![default_lib_allocator] diff --git a/library/std/src/sys/hermit/ext/io.rs b/library/std/src/sys/hermit/ext/io.rs new file mode 100644 index 0000000000000..c57d6d8a28dbd --- /dev/null +++ b/library/std/src/sys/hermit/ext/io.rs @@ -0,0 +1,23 @@ +use crate::io; +use crate::sys; +use crate::sys::hermit::abi; + +#[unstable(feature = "is_terminal", issue = "80937")] +impl io::IsTerminal for sys::stdio::Stdin { + 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) + } +} diff --git a/library/std/src/sys/hermit/ext/mod.rs b/library/std/src/sys/hermit/ext/mod.rs index ea87d0ad2c94d..1f425cf114fa2 100644 --- a/library/std/src/sys/hermit/ext/mod.rs +++ b/library/std/src/sys/hermit/ext/mod.rs @@ -2,6 +2,7 @@ #![allow(missing_docs)] pub mod ffi; +pub mod io; /// A prelude for conveniently writing platform-specific code. /// diff --git a/library/std/src/sys/unix/ext/io.rs b/library/std/src/sys/unix/ext/io.rs index ef3c689bd3921..c85361aef13f2 100644 --- a/library/std/src/sys/unix/ext/io.rs +++ b/library/std/src/sys/unix/ext/io.rs @@ -181,3 +181,23 @@ impl<'a> AsRawFd for io::StderrLock<'a> { libc::STDERR_FILENO } } + +#[unstable(feature = "is_terminal", issue = "80937")] +impl io::IsTerminal for sys::stdio::Stdin { + fn is_terminal() -> bool { + unsafe { libc::isatty(libc::STDIN_FILENO) != 0 } + } +} + +#[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 } + } +} diff --git a/library/std/src/sys/windows/c.rs b/library/std/src/sys/windows/c.rs index 3e4176ef7f8fe..bd09aebe432f9 100644 --- a/library/std/src/sys/windows/c.rs +++ b/library/std/src/sys/windows/c.rs @@ -388,6 +388,12 @@ pub struct FILE_BASIC_INFO { pub FileAttributes: DWORD, } +#[repr(C)] +pub struct FILE_NAME_INFO { + FileNameLength: DWORD, + FileName: [WCHAR; 1], +} + #[repr(C)] pub struct FILE_END_OF_FILE_INFO { pub EndOfFile: LARGE_INTEGER, diff --git a/library/std/src/sys/windows/ext/io.rs b/library/std/src/sys/windows/ext/io.rs index e75f9a4bfd5e3..cf64a64251895 100644 --- a/library/std/src/sys/windows/ext/io.rs +++ b/library/std/src/sys/windows/ext/io.rs @@ -220,3 +220,141 @@ impl IntoRawSocket for net::UdpSocket { self.into_inner().into_socket().into_inner() } } + +#[unstable(feature = "is_terminal", issue = "80937")] +impl io::IsTerminal for sys::stdio::Stdin { + fn is_terminal() -> bool { + use c::{ + STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT, + STD_OUTPUT_HANDLE as STD_OUTPUT, + }; + + let fd = STD_INPUT; + let others = [STD_ERROR, STD_OUTPUT]; + + 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 { + use c::{ + STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT, + STD_OUTPUT_HANDLE as STD_OUTPUT, + }; + + let fd = STD_OUTPUT; + let others = [STD_INPUT, STD_ERROR]; + + 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::Stderr { + fn is_terminal() -> bool { + use c::{ + STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT, + STD_OUTPUT_HANDLE as STD_OUTPUT, + }; + + let fd = STD_ERROR; + let others = [STD_INPUT, STD_OUTPUT]; + + 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")] +unsafe fn console_on_any(fds: &[c::DWORD]) -> bool { + use c::{GetConsoleMode, GetStdHandle}; + + for &fd in fds { + let mut out = 0; + let handle = GetStdHandle(fd); + if GetConsoleMode(handle, &mut out) != 0 { + return true; + } + } + false +} +#[unstable(feature = "is_terminal", issue = "80937")] +unsafe fn msys_tty_on(fd: c::DWORD) -> bool { + use std::{mem, slice}; + + use c::{ + c_void, FileNameInfo, GetFileInformationByHandleEx, GetStdHandle, FILE_NAME_INFO, MAX_PATH, + }; + + let size = mem::size_of::(); + let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::()]; + let res = GetFileInformationByHandleEx( + GetStdHandle(fd), + FileNameInfo, + &mut *name_info_bytes as *mut _ as *mut c_void, + name_info_bytes.len() as u32, + ); + if res == 0 { + return false; + } + let name_info: &FILE_NAME_INFO = &*(name_info_bytes.as_ptr() as *const FILE_NAME_INFO); + let s = + 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 +} diff --git a/library/test/src/cli.rs b/library/test/src/cli.rs index b7791b1b24d45..c678183a92ee0 100644 --- a/library/test/src/cli.rs +++ b/library/test/src/cli.rs @@ -1,9 +1,9 @@ //! Module converting command-line arguments into test configuration. use std::env; +use std::io::{IsTerminal, Stdout}; use std::path::PathBuf; -use super::helpers::isatty; use super::options::{ColorConfig, Options, OutputFormat, RunIgnored}; use super::time::TestTimeOptions; @@ -30,7 +30,7 @@ pub struct TestOpts { impl TestOpts { pub fn use_color(&self) -> bool { match self.color { - ColorConfig::AutoColor => !self.nocapture && isatty::stdout_isatty(), + ColorConfig::AutoColor => !self.nocapture && Stdout::is_terminal(), ColorConfig::AlwaysColor => true, ColorConfig::NeverColor => false, } diff --git a/library/test/src/helpers/isatty.rs b/library/test/src/helpers/isatty.rs deleted file mode 100644 index 874ecc3764572..0000000000000 --- a/library/test/src/helpers/isatty.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Helper module which provides a function to test -//! if stdout is a tty. - -cfg_if::cfg_if! { - if #[cfg(unix)] { - pub fn stdout_isatty() -> bool { - unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 } - } - } else if #[cfg(windows)] { - pub fn stdout_isatty() -> bool { - type DWORD = u32; - type BOOL = i32; - type HANDLE = *mut u8; - type LPDWORD = *mut u32; - const STD_OUTPUT_HANDLE: DWORD = -11i32 as DWORD; - extern "system" { - fn GetStdHandle(which: DWORD) -> HANDLE; - fn GetConsoleMode(hConsoleHandle: HANDLE, lpMode: LPDWORD) -> BOOL; - } - unsafe { - let handle = GetStdHandle(STD_OUTPUT_HANDLE); - let mut out = 0; - GetConsoleMode(handle, &mut out) != 0 - } - } - } else { - // FIXME: Implement isatty on SGX - pub fn stdout_isatty() -> bool { - false - } - } -} diff --git a/library/test/src/helpers/mod.rs b/library/test/src/helpers/mod.rs index b7f00c4c86cdf..00723eb835f7c 100644 --- a/library/test/src/helpers/mod.rs +++ b/library/test/src/helpers/mod.rs @@ -3,5 +3,4 @@ pub mod concurrency; pub mod exit_code; -pub mod isatty; pub mod metrics; diff --git a/library/test/src/lib.rs b/library/test/src/lib.rs index 2e0864f303cc9..e838eb40a859e 100644 --- a/library/test/src/lib.rs +++ b/library/test/src/lib.rs @@ -30,6 +30,7 @@ #![feature(termination_trait_lib)] #![feature(test)] #![feature(total_cmp)] +#![feature(is_terminal)] // Public reexports pub use self::bench::{black_box, Bencher};