diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index cd2197fca350e..4fd618ec61a8a 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -266,6 +266,8 @@ pub use self::buffered::WriterPanicked; #[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; #[unstable(feature = "print_internals", issue = "none")] pub use self::stdio::{_eprint, _print}; #[stable(feature = "rust1", since = "1.0.0")] @@ -2379,7 +2381,11 @@ impl BufRead for Chain { } fn consume(&mut self, amt: usize) { - if !self.done_first { self.first.consume(amt) } else { self.second.consume(amt) } + if !self.done_first { + self.first.consume(amt) + } else { + self.second.consume(amt) + } } } diff --git a/library/std/src/io/stdio.rs b/library/std/src/io/stdio.rs index ac6d41e13b009..be102bcc8c50e 100644 --- a/library/std/src/io/stdio.rs +++ b/library/std/src/io/stdio.rs @@ -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 { + /// returns true if stdio stream (Stdin, Stdout, Stderr) is a terminal/tty. + fn is_terminal() -> bool; +} + +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", diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index a464f2d4c7431..9b096b65f6b51 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -279,6 +279,7 @@ #![feature(hashmap_internals)] #![feature(int_error_internals)] #![feature(intra_doc_pointers)] +#![feature(is_terminal)] #![feature(lang_items)] #![feature(linkage)] #![feature(log_syntax)] diff --git a/library/std/src/sys/hermit/stdio.rs b/library/std/src/sys/hermit/stdio.rs index 514de1df6f9c3..9c0098d96ec4c 100644 --- a/library/std/src/sys/hermit/stdio.rs +++ b/library/std/src/sys/hermit/stdio.rs @@ -118,3 +118,23 @@ pub fn is_ebadf(_err: &io::Error) -> bool { pub fn panic_output() -> Option { Some(Stderr::new()) } + +#[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/unix/io.rs b/library/std/src/sys/unix/io.rs index deb5ee76bd035..fa276a623b230 100644 --- a/library/std/src/sys/unix/io.rs +++ b/library/std/src/sys/unix/io.rs @@ -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 } + } +} + +#[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 9b61b2476d5bb..ccd68d073e5d7 100644 --- a/library/std/src/sys/windows/c.rs +++ b/library/std/src/sys/windows/c.rs @@ -124,6 +124,8 @@ pub const SECURITY_SQOS_PRESENT: DWORD = 0x00100000; pub const FIONBIO: c_ulong = 0x8004667e; +pub const MAX_PATH: usize = 260; + #[repr(C)] #[derive(Copy)] pub struct WIN32_FIND_DATAW { @@ -484,6 +486,12 @@ pub struct SYMBOLIC_LINK_REPARSE_BUFFER { pub PathBuffer: WCHAR, } +#[repr(C)] +pub struct FILE_NAME_INFO { + pub FileNameLength: DWORD, + pub FileName: [WCHAR; 1], +} + #[repr(C)] pub struct MOUNT_POINT_REPARSE_BUFFER { pub SubstituteNameOffset: c_ushort, diff --git a/library/std/src/sys/windows/io.rs b/library/std/src/sys/windows/io.rs index fb06df1f80cda..dd6861ff12de0 100644 --- a/library/std/src/sys/windows/io.rs +++ b/library/std/src/sys/windows/io.rs @@ -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 { + 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::(); + let mut name_info_bytes = vec![0u8; size + c::MAX_PATH * core::mem::size_of::()]; + 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 +} diff --git a/library/test/src/cli.rs b/library/test/src/cli.rs index 000f5fa3f5860..c0bdb8b8c9ad1 100644 --- a/library/test/src/cli.rs +++ b/library/test/src/cli.rs @@ -3,9 +3,9 @@ use std::env; use std::path::PathBuf; -use super::helpers::isatty; use super::options::{ColorConfig, Options, OutputFormat, RunIgnored}; use super::time::TestTimeOptions; +use std::io::{IsTerminal, Stdout}; #[derive(Debug)] pub struct TestOpts { @@ -32,7 +32,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 049cadf86a6d0..6f366a911e8cd 100644 --- a/library/test/src/helpers/mod.rs +++ b/library/test/src/helpers/mod.rs @@ -3,6 +3,5 @@ pub mod concurrency; pub mod exit_code; -pub mod isatty; pub mod metrics; pub mod shuffle; diff --git a/library/test/src/lib.rs b/library/test/src/lib.rs index 088e3a23ea4d9..d8aeb7318089f 100644 --- a/library/test/src/lib.rs +++ b/library/test/src/lib.rs @@ -18,6 +18,8 @@ #![feature(nll)] #![feature(bench_black_box)] #![feature(internal_output_capture)] +#![feature(is_terminal)] +#![feature(panic_unwind)] #![feature(staged_api)] #![feature(termination_trait_lib)] #![feature(process_exitcode_placeholder)] @@ -292,7 +294,11 @@ where fn calc_timeout(timeout_queue: &VecDeque) -> Option { timeout_queue.front().map(|&TimeoutEntry { timeout: next_timeout, .. }| { let now = Instant::now(); - if next_timeout >= now { next_timeout - now } else { Duration::new(0, 0) } + if next_timeout >= now { + next_timeout - now + } else { + Duration::new(0, 0) + } }) }