about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--library/std/src/io/mod.rs2
-rw-r--r--library/std/src/io/stdio.rs29
-rw-r--r--library/std/src/lib.rs1
-rw-r--r--library/std/src/os/fd/owned.rs17
-rw-r--r--library/std/src/os/windows/io/handle.rs17
-rw-r--r--library/std/src/sys/unix/io.rs6
-rw-r--r--library/std/src/sys/unsupported/io.rs4
-rw-r--r--library/std/src/sys/wasi/io.rs6
-rw-r--r--library/std/src/sys/windows/c.rs8
-rw-r--r--library/std/src/sys/windows/io.rs59
-rw-r--r--library/test/src/cli.rs4
-rw-r--r--library/test/src/helpers/isatty.rs32
-rw-r--r--library/test/src/helpers/mod.rs1
-rw-r--r--library/test/src/lib.rs1
14 files changed, 152 insertions, 35 deletions
diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs
index cceef539b90..0c29b001f01 100644
--- a/library/std/src/io/mod.rs
+++ b/library/std/src/io/mod.rs
@@ -266,6 +266,8 @@ pub(crate) use self::stdio::attempt_print_to_stderr;
 #[unstable(feature = "internal_output_capture", issue = "none")]
 #[doc(no_inline, hidden)]
 pub use self::stdio::set_output_capture;
+#[unstable(feature = "is_terminal", issue = "98070")]
+pub use self::stdio::IsTerminal;
 #[unstable(feature = "print_internals", issue = "none")]
 pub use self::stdio::{_eprint, _print};
 #[stable(feature = "rust1", since = "1.0.0")]
diff --git a/library/std/src/io/stdio.rs b/library/std/src/io/stdio.rs
index 4ccb2bf3231..1141a957d87 100644
--- a/library/std/src/io/stdio.rs
+++ b/library/std/src/io/stdio.rs
@@ -7,6 +7,7 @@ use crate::io::prelude::*;
 
 use crate::cell::{Cell, RefCell};
 use crate::fmt;
+use crate::fs::File;
 use crate::io::{self, BufReader, IoSlice, IoSliceMut, LineWriter, Lines};
 use crate::sync::atomic::{AtomicBool, Ordering};
 use crate::sync::{Arc, Mutex, MutexGuard, OnceLock};
@@ -1035,6 +1036,34 @@ pub(crate) fn attempt_print_to_stderr(args: fmt::Arguments<'_>) {
     let _ = stderr().write_fmt(args);
 }
 
+/// Trait to determine if a descriptor/handle refers to a terminal/tty.
+#[unstable(feature = "is_terminal", issue = "98070")]
+pub trait IsTerminal: crate::sealed::Sealed {
+    /// Returns `true` if the descriptor/handle refers to a terminal/tty.
+    ///
+    /// On platforms where Rust does not know how to detect a terminal yet, this will return
+    /// `false`. This will also return `false` if an unexpected error occurred, such as from
+    /// passing an invalid file descriptor.
+    fn is_terminal(&self) -> bool;
+}
+
+macro_rules! impl_is_terminal {
+    ($($t:ty),*$(,)?) => {$(
+        #[unstable(feature = "sealed", issue = "none")]
+        impl crate::sealed::Sealed for $t {}
+
+        #[unstable(feature = "is_terminal", issue = "98070")]
+        impl IsTerminal for $t {
+            #[inline]
+            fn is_terminal(&self) -> bool {
+                crate::sys::io::is_terminal(self)
+            }
+        }
+    )*}
+}
+
+impl_is_terminal!(File, Stdin, StdinLock<'_>, Stdout, StdoutLock<'_>, Stderr, StderrLock<'_>);
+
 #[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 5484d9c332a..78838adb8dd 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -253,6 +253,7 @@
 #![feature(exhaustive_patterns)]
 #![feature(if_let_guard)]
 #![feature(intra_doc_pointers)]
+#![feature(is_terminal)]
 #![feature(lang_items)]
 #![feature(let_chains)]
 #![feature(linkage)]
diff --git a/library/std/src/os/fd/owned.rs b/library/std/src/os/fd/owned.rs
index 9d758320cfc..c16518577f7 100644
--- a/library/std/src/os/fd/owned.rs
+++ b/library/std/src/os/fd/owned.rs
@@ -193,6 +193,23 @@ impl fmt::Debug for OwnedFd {
     }
 }
 
+macro_rules! impl_is_terminal {
+    ($($t:ty),*$(,)?) => {$(
+        #[unstable(feature = "sealed", issue = "none")]
+        impl crate::sealed::Sealed for $t {}
+
+        #[unstable(feature = "is_terminal", issue = "98070")]
+        impl crate::io::IsTerminal for $t {
+            #[inline]
+            fn is_terminal(&self) -> bool {
+                crate::sys::io::is_terminal(self)
+            }
+        }
+    )*}
+}
+
+impl_is_terminal!(BorrowedFd<'_>, OwnedFd);
+
 /// A trait to borrow the file descriptor from an underlying object.
 ///
 /// This is only available on unix platforms and must be imported in order to
diff --git a/library/std/src/os/windows/io/handle.rs b/library/std/src/os/windows/io/handle.rs
index 16cc8fa2783..1dfecc57338 100644
--- a/library/std/src/os/windows/io/handle.rs
+++ b/library/std/src/os/windows/io/handle.rs
@@ -384,6 +384,23 @@ impl fmt::Debug for OwnedHandle {
     }
 }
 
+macro_rules! impl_is_terminal {
+    ($($t:ty),*$(,)?) => {$(
+        #[unstable(feature = "sealed", issue = "none")]
+        impl crate::sealed::Sealed for $t {}
+
+        #[unstable(feature = "is_terminal", issue = "98070")]
+        impl crate::io::IsTerminal for $t {
+            #[inline]
+            fn is_terminal(&self) -> bool {
+                crate::sys::io::is_terminal(self)
+            }
+        }
+    )*}
+}
+
+impl_is_terminal!(BorrowedHandle<'_>, OwnedHandle);
+
 /// A trait to borrow the handle from an underlying object.
 #[stable(feature = "io_safety", since = "1.63.0")]
 pub trait AsHandle {
diff --git a/library/std/src/sys/unix/io.rs b/library/std/src/sys/unix/io.rs
index deb5ee76bd0..29c340dd349 100644
--- a/library/std/src/sys/unix/io.rs
+++ b/library/std/src/sys/unix/io.rs
@@ -1,4 +1,5 @@
 use crate::marker::PhantomData;
+use crate::os::fd::{AsFd, AsRawFd};
 use crate::slice;
 
 use libc::{c_void, iovec};
@@ -74,3 +75,8 @@ impl<'a> IoSliceMut<'a> {
         unsafe { slice::from_raw_parts_mut(self.vec.iov_base as *mut u8, self.vec.iov_len) }
     }
 }
+
+pub fn is_terminal(fd: &impl AsFd) -> bool {
+    let fd = fd.as_fd();
+    unsafe { libc::isatty(fd.as_raw_fd()) != 0 }
+}
diff --git a/library/std/src/sys/unsupported/io.rs b/library/std/src/sys/unsupported/io.rs
index d5f475b4310..82610ffab7e 100644
--- a/library/std/src/sys/unsupported/io.rs
+++ b/library/std/src/sys/unsupported/io.rs
@@ -45,3 +45,7 @@ impl<'a> IoSliceMut<'a> {
         self.0
     }
 }
+
+pub fn is_terminal<T>(_: &T) -> bool {
+    false
+}
diff --git a/library/std/src/sys/wasi/io.rs b/library/std/src/sys/wasi/io.rs
index ee017d13a4c..2cd45df88fa 100644
--- a/library/std/src/sys/wasi/io.rs
+++ b/library/std/src/sys/wasi/io.rs
@@ -1,6 +1,7 @@
 #![deny(unsafe_op_in_unsafe_fn)]
 
 use crate::marker::PhantomData;
+use crate::os::fd::{AsFd, AsRawFd};
 use crate::slice;
 
 #[derive(Copy, Clone)]
@@ -71,3 +72,8 @@ impl<'a> IoSliceMut<'a> {
         unsafe { slice::from_raw_parts_mut(self.vec.buf as *mut u8, self.vec.buf_len) }
     }
 }
+
+pub fn is_terminal(fd: &impl AsFd) -> bool {
+    let fd = fd.as_fd();
+    unsafe { libc::isatty(fd.as_raw_fd()) != 0 }
+}
diff --git a/library/std/src/sys/windows/c.rs b/library/std/src/sys/windows/c.rs
index 732e227d7e2..917fc8e4995 100644
--- a/library/std/src/sys/windows/c.rs
+++ b/library/std/src/sys/windows/c.rs
@@ -127,6 +127,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 {
@@ -539,6 +541,12 @@ pub struct SYMBOLIC_LINK_REPARSE_BUFFER {
 /// NB: Use carefully! In general using this as a reference is likely to get the
 /// provenance wrong for the `PathBuffer` field!
 #[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,
     pub SubstituteNameLength: c_ushort,
diff --git a/library/std/src/sys/windows/io.rs b/library/std/src/sys/windows/io.rs
index fb06df1f80c..489d66b0671 100644
--- a/library/std/src/sys/windows/io.rs
+++ b/library/std/src/sys/windows/io.rs
@@ -1,6 +1,10 @@
 use crate::marker::PhantomData;
+use crate::mem::size_of;
+use crate::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle};
 use crate::slice;
 use crate::sys::c;
+use core;
+use libc;
 
 #[derive(Copy, Clone)]
 #[repr(transparent)]
@@ -78,3 +82,58 @@ impl<'a> IoSliceMut<'a> {
         unsafe { slice::from_raw_parts_mut(self.vec.buf as *mut u8, self.vec.len as usize) }
     }
 }
+
+pub fn is_terminal(h: &impl AsHandle) -> bool {
+    unsafe { handle_is_console(h.as_handle()) }
+}
+
+unsafe fn handle_is_console(handle: BorrowedHandle<'_>) -> bool {
+    let handle = handle.as_raw_handle();
+
+    let mut out = 0;
+    if c::GetConsoleMode(handle, &mut out) != 0 {
+        // False positives aren't possible. If we got a console then we definitely have a console.
+        return true;
+    }
+
+    // At this point, we *could* have a false negative. We can determine that this is a true
+    // negative if we can detect the presence of a console on any of the standard I/O streams. If
+    // another stream has a console, then we know we're in a Windows console and can therefore
+    // trust the negative.
+    for std_handle in [c::STD_INPUT_HANDLE, c::STD_OUTPUT_HANDLE, c::STD_ERROR_HANDLE] {
+        let std_handle = c::GetStdHandle(std_handle);
+        if std_handle != handle && c::GetConsoleMode(std_handle, &mut out) != 0 {
+            return false;
+        }
+    }
+
+    // Otherwise, we fall back to an msys hack to see if we can detect the presence of a pty.
+    msys_tty_on(handle)
+}
+
+unsafe fn msys_tty_on(handle: c::HANDLE) -> bool {
+    let size = size_of::<c::FILE_NAME_INFO>() + c::MAX_PATH * size_of::<c::WCHAR>();
+    let mut name_info_bytes = vec![0u8; size];
+    let res = c::GetFileInformationByHandleEx(
+        handle,
+        c::FileNameInfo,
+        name_info_bytes.as_mut_ptr() as *mut libc::c_void,
+        size 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 f981b9c4954..8be32183fe7 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::{self, IsTerminal};
 
 #[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 && io::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 874ecc37645..00000000000
--- 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 049cadf86a6..6f366a911e8 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 c30257fc792..b1e0bbfc591 100644
--- a/library/test/src/lib.rs
+++ b/library/test/src/lib.rs
@@ -17,6 +17,7 @@
 #![unstable(feature = "test", issue = "50297")]
 #![doc(test(attr(deny(warnings))))]
 #![feature(internal_output_capture)]
+#![feature(is_terminal)]
 #![feature(staged_api)]
 #![feature(process_exitcode_internals)]
 #![feature(test)]