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 #81807

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions library/std/src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
54 changes: 54 additions & 0 deletions library/std/src/io/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
}
1 change: 1 addition & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
23 changes: 23 additions & 0 deletions library/std/src/sys/hermit/ext/io.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}
1 change: 1 addition & 0 deletions library/std/src/sys/hermit/ext/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#![allow(missing_docs)]

pub mod ffi;
pub mod io;

/// A prelude for conveniently writing platform-specific code.
///
Expand Down
20 changes: 20 additions & 0 deletions library/std/src/sys/unix/ext/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
}
6 changes: 6 additions & 0 deletions library/std/src/sys/windows/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
138 changes: 138 additions & 0 deletions library/std/src/sys/windows/ext/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it a possibility to just remove these 3 uses and use them directly like c::STD_ERROR_HANDLE etc. like it's done in other parts of this file? See e.g.

unsafe { c::GetStdHandle(c::STD_INPUT_HANDLE) as RawHandle }


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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// we definitely have a tty on stdin.
// 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 {
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// we definitely have a tty on stdin.
// 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 {
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::<FILE_NAME_INFO>();
let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::<WCHAR>()];
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
}
4 changes: 2 additions & 2 deletions library/test/src/cli.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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,
}
Expand Down
32 changes: 0 additions & 32 deletions library/test/src/helpers/isatty.rs

This file was deleted.

1 change: 0 additions & 1 deletion library/test/src/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@

pub mod concurrency;
pub mod exit_code;
pub mod isatty;
pub mod metrics;
1 change: 1 addition & 0 deletions library/test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down