Skip to content

Commit f9f2c8f

Browse files
author
Matt Wilkinson
committed
reimplement IsAtty in os module and wrap with io module
1 parent 9b90f9e commit f9f2c8f

File tree

12 files changed

+248
-35
lines changed

12 files changed

+248
-35
lines changed

library/std/src/io/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ pub use self::error::{Error, ErrorKind, Result};
273273
#[unstable(feature = "internal_output_capture", issue = "none")]
274274
#[doc(no_inline, hidden)]
275275
pub use self::stdio::set_output_capture;
276+
#[unstable(feature = "is_atty", issue = "80937")]
277+
pub use self::stdio::IsAtty;
276278
#[stable(feature = "rust1", since = "1.0.0")]
277279
pub use self::stdio::{stderr, stdin, stdout, Stderr, Stdin, Stdout};
278280
#[stable(feature = "rust1", since = "1.0.0")]

library/std/src/io/stdio.rs

+54
Original file line numberDiff line numberDiff line change
@@ -965,3 +965,57 @@ pub fn _eprint(args: fmt::Arguments<'_>) {
965965

966966
#[cfg(test)]
967967
pub use realstd::io::{_eprint, _print};
968+
969+
/// Trait to determine if stdio stream (Stdin, Stdout, Stderr) is a tty.
970+
#[unstable(feature = "is_atty", issue = "80937")]
971+
pub trait IsAtty {
972+
/// returns true if implemented stream is a tty.
973+
fn is_atty() -> bool;
974+
}
975+
976+
#[unstable(feature = "is_atty", issue = "80937")]
977+
cfg_if::cfg_if! {
978+
if #[cfg(any(unix, windows, hermit))] {
979+
#[unstable(feature = "is_atty", issue = "80937")]
980+
impl IsAtty for Stdin {
981+
fn is_atty() -> bool {
982+
stdio::Stdin::is_atty()
983+
}
984+
}
985+
986+
#[unstable(feature = "is_atty", issue = "80937")]
987+
impl IsAtty for Stdout {
988+
fn is_atty() -> bool {
989+
stdio::Stdout::is_atty()
990+
}
991+
}
992+
993+
#[unstable(feature = "is_atty", issue = "80937")]
994+
impl IsAtty for Stderr {
995+
fn is_atty() -> bool {
996+
stdio::Stderr::is_atty()
997+
}
998+
}
999+
} else {
1000+
#[unstable(feature = "is_atty", issue = "80937")]
1001+
impl IsAtty for Stdin {
1002+
fn is_atty() -> bool {
1003+
false
1004+
}
1005+
}
1006+
1007+
#[unstable(feature = "is_atty", issue = "80937")]
1008+
impl IsAtty for Stdout {
1009+
fn is_atty() -> bool {
1010+
false
1011+
}
1012+
}
1013+
1014+
#[unstable(feature = "is_atty", issue = "80937")]
1015+
impl IsAtty for Stderr {
1016+
fn is_atty() -> bool {
1017+
false
1018+
}
1019+
}
1020+
}
1021+
}

library/std/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@
335335
#![feature(unwind_attributes)]
336336
#![feature(vec_into_raw_parts)]
337337
#![feature(vec_spare_capacity)]
338+
#![feature(is_atty)]
338339
// NB: the above list is sorted to minimize merge conflicts.
339340
#![default_lib_allocator]
340341

library/std/src/sys/hermit/ext/io.rs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use crate::io;
2+
use crate::sys;
3+
use crate::sys::hermit::abi;
4+
5+
#[unstable(feature = "is_atty", issue = "80937")]
6+
impl io::IsAtty for sys::stdio::Stdin {
7+
fn is_atty() -> bool {
8+
abi::isatty(abi::STDIN_FILENO)
9+
}
10+
}
11+
12+
#[unstable(feature = "is_atty", issue = "80937")]
13+
impl io::IsAtty for sys::stdio::Stdout {
14+
fn is_atty() -> bool {
15+
abi::isatty(abi::STDOUT_FILENO)
16+
}
17+
}
18+
#[unstable(feature = "is_atty", issue = "80937")]
19+
impl io::IsAtty for sys::stdio::Stderr {
20+
fn is_atty() -> bool {
21+
abi::isatty(abi::STDERR_FILENO)
22+
}
23+
}

library/std/src/sys/hermit/ext/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![allow(missing_docs)]
33

44
pub mod ffi;
5+
pub mod io;
56

67
/// A prelude for conveniently writing platform-specific code.
78
///

library/std/src/sys/unix/ext/io.rs

+20
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,23 @@ impl<'a> AsRawFd for io::StderrLock<'a> {
181181
libc::STDERR_FILENO
182182
}
183183
}
184+
185+
#[unstable(feature = "is_atty", issue = "80937")]
186+
impl io::IsAtty for sys::stdio::Stdin {
187+
fn is_atty() -> bool {
188+
unsafe { libc::isatty(libc::STDIN_FILENO) != 0 }
189+
}
190+
}
191+
192+
#[unstable(feature = "is_atty", issue = "80937")]
193+
impl io::IsAtty for sys::stdio::Stdout {
194+
fn is_atty() -> bool {
195+
unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 }
196+
}
197+
}
198+
#[unstable(feature = "is_atty", issue = "80937")]
199+
impl io::IsAtty for sys::stdio::Stderr {
200+
fn is_atty() -> bool {
201+
unsafe { libc::isatty(libc::STDERR_FILENO) != 0 }
202+
}
203+
}

library/std/src/sys/windows/c.rs

+6
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,12 @@ pub struct FILE_BASIC_INFO {
388388
pub FileAttributes: DWORD,
389389
}
390390

391+
#[repr(C)]
392+
pub struct FILE_NAME_INFO {
393+
FileNameLength: DWORD,
394+
FileName: [WCHAR; 1],
395+
}
396+
391397
#[repr(C)]
392398
pub struct FILE_END_OF_FILE_INFO {
393399
pub EndOfFile: LARGE_INTEGER,

library/std/src/sys/windows/ext/io.rs

+138
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,141 @@ impl IntoRawSocket for net::UdpSocket {
220220
self.into_inner().into_socket().into_inner()
221221
}
222222
}
223+
224+
#[unstable(feature = "is_atty", issue = "80937")]
225+
impl io::IsAtty for sys::stdio::Stdin {
226+
fn is_atty() -> bool {
227+
use c::{
228+
STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT,
229+
STD_OUTPUT_HANDLE as STD_OUTPUT,
230+
};
231+
232+
let fd = STD_INPUT;
233+
let others = [STD_ERROR, STD_OUTPUT];
234+
235+
if unsafe { console_on_any(&[fd]) } {
236+
// False positives aren't possible. If we got a console then
237+
// we definitely have a tty on stdin.
238+
return true;
239+
}
240+
241+
// At this point, we *could* have a false negative. We can determine that
242+
// this is true negative if we can detect the presence of a console on
243+
// any of the other streams. If another stream has a console, then we know
244+
// we're in a Windows console and can therefore trust the negative.
245+
if unsafe { console_on_any(&others) } {
246+
return false;
247+
}
248+
249+
// Otherwise, we fall back to a very strange msys hack to see if we can
250+
// sneakily detect the presence of a tty.
251+
unsafe { msys_tty_on(fd) }
252+
}
253+
}
254+
255+
#[unstable(feature = "is_atty", issue = "80937")]
256+
impl io::IsAtty for sys::stdio::Stdout {
257+
fn is_atty() -> bool {
258+
use c::{
259+
STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT,
260+
STD_OUTPUT_HANDLE as STD_OUTPUT,
261+
};
262+
263+
let fd = STD_OUTPUT;
264+
let others = [STD_INPUT, STD_ERROR];
265+
266+
if unsafe { console_on_any(&[fd]) } {
267+
// False positives aren't possible. If we got a console then
268+
// we definitely have a tty on stdin.
269+
return true;
270+
}
271+
272+
// At this point, we *could* have a false negative. We can determine that
273+
// this is true negative if we can detect the presence of a console on
274+
// any of the other streams. If another stream has a console, then we know
275+
// we're in a Windows console and can therefore trust the negative.
276+
if unsafe { console_on_any(&others) } {
277+
return false;
278+
}
279+
280+
// Otherwise, we fall back to a very strange msys hack to see if we can
281+
// sneakily detect the presence of a tty.
282+
unsafe { msys_tty_on(fd) }
283+
}
284+
}
285+
286+
#[unstable(feature = "is_atty", issue = "80937")]
287+
impl io::IsAtty for sys::stdio::Stderr {
288+
fn is_atty() -> bool {
289+
use c::{
290+
STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT,
291+
STD_OUTPUT_HANDLE as STD_OUTPUT,
292+
};
293+
294+
let fd = STD_ERROR;
295+
let others = [STD_INPUT, STD_OUTPUT];
296+
297+
if unsafe { console_on_any(&[fd]) } {
298+
// False positives aren't possible. If we got a console then
299+
// we definitely have a tty on stdin.
300+
return true;
301+
}
302+
303+
// At this point, we *could* have a false negative. We can determine that
304+
// this is true negative if we can detect the presence of a console on
305+
// any of the other streams. If another stream has a console, then we know
306+
// we're in a Windows console and can therefore trust the negative.
307+
if unsafe { console_on_any(&others) } {
308+
return false;
309+
}
310+
311+
// Otherwise, we fall back to a very strange msys hack to see if we can
312+
// sneakily detect the presence of a tty.
313+
unsafe { msys_tty_on(fd) }
314+
}
315+
}
316+
317+
#[unstable(feature = "is_atty", issue = "80937")]
318+
unsafe fn console_on_any(fds: &[c::DWORD]) -> bool {
319+
use c::{GetConsoleMode, GetStdHandle};
320+
321+
for &fd in fds {
322+
let mut out = 0;
323+
let handle = GetStdHandle(fd);
324+
if GetConsoleMode(handle, &mut out) != 0 {
325+
return true;
326+
}
327+
}
328+
false
329+
}
330+
#[unstable(feature = "is_atty", issue = "80937")]
331+
unsafe fn msys_tty_on(fd: c::DWORD) -> bool {
332+
use std::{mem, slice};
333+
334+
use c::{
335+
c_void, FileNameInfo, GetFileInformationByHandleEx, GetStdHandle, FILE_NAME_INFO, MAX_PATH,
336+
};
337+
338+
let size = mem::size_of::<FILE_NAME_INFO>();
339+
let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::<WCHAR>()];
340+
let res = GetFileInformationByHandleEx(
341+
GetStdHandle(fd),
342+
FileNameInfo,
343+
&mut *name_info_bytes as *mut _ as *mut c_void,
344+
name_info_bytes.len() as u32,
345+
);
346+
if res == 0 {
347+
return false;
348+
}
349+
let name_info: &FILE_NAME_INFO = &*(name_info_bytes.as_ptr() as *const FILE_NAME_INFO);
350+
let s =
351+
slice::from_raw_parts(name_info.FileName.as_ptr(), name_info.FileNameLength as usize / 2);
352+
let name = String::from_utf16_lossy(s);
353+
// This checks whether 'pty' exists in the file name, which indicates that
354+
// a pseudo-terminal is attached. To mitigate against false positives
355+
// (e.g., an actual file name that contains 'pty'), we also require that
356+
// either the strings 'msys-' or 'cygwin-' are in the file name as well.)
357+
let is_msys = name.contains("msys-") || name.contains("cygwin-");
358+
let is_pty = name.contains("-pty");
359+
is_msys && is_pty
360+
}

library/test/src/cli.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
//! Module converting command-line arguments into test configuration.
22
33
use std::env;
4+
use std::io::{IsAtty, Stdout};
45
use std::path::PathBuf;
56

6-
use super::helpers::isatty;
77
use super::options::{ColorConfig, Options, OutputFormat, RunIgnored};
88
use super::time::TestTimeOptions;
99

@@ -30,7 +30,7 @@ pub struct TestOpts {
3030
impl TestOpts {
3131
pub fn use_color(&self) -> bool {
3232
match self.color {
33-
ColorConfig::AutoColor => !self.nocapture && isatty::stdout_isatty(),
33+
ColorConfig::AutoColor => !self.nocapture && Stdout::is_atty(),
3434
ColorConfig::AlwaysColor => true,
3535
ColorConfig::NeverColor => false,
3636
}

library/test/src/helpers/isatty.rs

-32
This file was deleted.

library/test/src/helpers/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,4 @@
33
44
pub mod concurrency;
55
pub mod exit_code;
6-
pub mod isatty;
76
pub mod metrics;

library/test/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#![feature(termination_trait_lib)]
3131
#![feature(test)]
3232
#![feature(total_cmp)]
33+
#![feature(is_atty)]
3334

3435
// Public reexports
3536
pub use self::bench::{black_box, Bencher};

0 commit comments

Comments
 (0)