From 0a5cc0e54c0c1bc43e165448c4ffc5462984f8f7 Mon Sep 17 00:00:00 2001 From: Alex Lyon Date: Thu, 16 May 2019 22:57:32 -0700 Subject: [PATCH 1/3] libstd: use block buffering when stdout is not a TTY --- src/libstd/io/stdio.rs | 79 +++++++++++++++++++++++++++----- src/libstd/sys/cloudabi/stdio.rs | 5 ++ src/libstd/sys/redox/stdio.rs | 10 ++++ src/libstd/sys/sgx/stdio.rs | 6 +++ src/libstd/sys/unix/stdio.rs | 5 ++ src/libstd/sys/wasi/stdio.rs | 6 +++ src/libstd/sys/wasm/stdio.rs | 5 ++ src/libstd/sys/windows/stdio.rs | 24 ++++++++++ 8 files changed, 129 insertions(+), 11 deletions(-) diff --git a/src/libstd/io/stdio.rs b/src/libstd/io/stdio.rs index 990c0eb8955e4..f6dd6f87c1335 100644 --- a/src/libstd/io/stdio.rs +++ b/src/libstd/io/stdio.rs @@ -5,7 +5,7 @@ use crate::io::prelude::*; use crate::cell::RefCell; use crate::fmt; use crate::io::lazy::Lazy; -use crate::io::{self, Initializer, BufReader, LineWriter, IoSlice, IoSliceMut}; +use crate::io::{self, Initializer, BufReader, BufWriter, LineWriter, IoSlice, IoSliceMut}; use crate::sync::{Arc, Mutex, MutexGuard}; use crate::sys::stdio; use crate::sys_common::remutex::{ReentrantMutex, ReentrantMutexGuard}; @@ -398,10 +398,7 @@ impl fmt::Debug for StdinLock<'_> { /// [`io::stdout`]: fn.stdout.html #[stable(feature = "rust1", since = "1.0.0")] pub struct Stdout { - // FIXME: this should be LineWriter or BufWriter depending on the state of - // stdout (tty or not). Note that if this is not line buffered it - // should also flush-on-panic or some form of flush-on-abort. - inner: Arc>>>>, + inner: Arc>>, } /// A locked reference to the `Stdout` handle. @@ -418,7 +415,7 @@ pub struct Stdout { /// [`Stdout::lock`]: struct.Stdout.html#method.lock #[stable(feature = "rust1", since = "1.0.0")] pub struct StdoutLock<'a> { - inner: ReentrantMutexGuard<'a, RefCell>>>, + inner: ReentrantMutexGuard<'a, RefCell>, } /// Constructs a new handle to the standard output of the current process. @@ -464,20 +461,80 @@ pub struct StdoutLock<'a> { /// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn stdout() -> Stdout { - static INSTANCE: Lazy>>>> = Lazy::new(); + static INSTANCE: Lazy>> = Lazy::new(); return Stdout { inner: unsafe { INSTANCE.get(stdout_init).expect("cannot access stdout during shutdown") }, }; - fn stdout_init() -> Arc>>>> { + fn stdout_init() -> Arc>> { // This must not reentrantly access `INSTANCE` let stdout = match stdout_raw() { - Ok(stdout) => Maybe::Real(stdout), - _ => Maybe::Fake, + Ok(stdout) => { + let tty = stdout.0.is_tty(); + let stdout = Maybe::Real(stdout); + if tty { + let inner = LineWriter::new(stdout); + StdoutStream::LineBuffered(inner) + } else { + let inner = BufWriter::with_capacity(stdio::STDOUT_BUF_SIZE, stdout); + StdoutStream::BlockBuffered(inner) + } + } + _ => StdoutStream::LineBuffered(LineWriter::new(Maybe::Fake)), }; - Arc::new(ReentrantMutex::new(RefCell::new(LineWriter::new(stdout)))) + Arc::new(ReentrantMutex::new(RefCell::new(stdout))) + } +} + +/// Container to allow switching between block buffering and line buffering for the process's +/// standard output. +enum StdoutStream { + BlockBuffered(BufWriter>), + LineBuffered(LineWriter>), +} + +impl Write for StdoutStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + use self::StdoutStream::*; + + match self { + BlockBuffered(w) => w.write(buf), + LineBuffered(w) => w.write(buf), + } + } + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + use self::StdoutStream::*; + + match self { + BlockBuffered(w) => w.write_vectored(bufs), + LineBuffered(w) => w.write_vectored(bufs), + } + } + fn flush(&mut self) -> io::Result<()> { + use self::StdoutStream::*; + + match self { + BlockBuffered(w) => w.flush(), + LineBuffered(w) => w.flush(), + } + } + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + use self::StdoutStream::*; + + match self { + BlockBuffered(w) => w.write_all(buf), + LineBuffered(w) => w.write_all(buf) + } + } + fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> { + use self::StdoutStream::*; + + match self { + BlockBuffered(w) => w.write_fmt(args), + LineBuffered(w) => w.write_fmt(args), + } } } diff --git a/src/libstd/sys/cloudabi/stdio.rs b/src/libstd/sys/cloudabi/stdio.rs index 601563c5b1fcb..c4d5a9e5c91c8 100644 --- a/src/libstd/sys/cloudabi/stdio.rs +++ b/src/libstd/sys/cloudabi/stdio.rs @@ -21,6 +21,10 @@ impl Stdout { pub fn new() -> io::Result { Ok(Stdout(())) } + + pub fn is_tty(&self) -> bool { + false + } } impl io::Write for Stdout { @@ -60,6 +64,7 @@ pub fn is_ebadf(err: &io::Error) -> bool { } pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE; +pub const STDOUT_BUF_SIZE: usize = 0; pub fn panic_output() -> Option { Stderr::new().ok() diff --git a/src/libstd/sys/redox/stdio.rs b/src/libstd/sys/redox/stdio.rs index 33f5bdbb5d358..533eea259ac8d 100644 --- a/src/libstd/sys/redox/stdio.rs +++ b/src/libstd/sys/redox/stdio.rs @@ -21,6 +21,15 @@ impl io::Read for Stdin { impl Stdout { pub fn new() -> io::Result { Ok(Stdout(())) } + + pub fn is_tty(&self) -> bool { + if let Ok(fd) = syscall::dup(1, b"termios") { + let _ = syscall::close(fd); + true + } else { + false + } + } } impl io::Write for Stdout { @@ -58,6 +67,7 @@ pub fn is_ebadf(err: &io::Error) -> bool { } pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE; +pub const STDOUT_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE; pub fn panic_output() -> Option { Stderr::new().ok() diff --git a/src/libstd/sys/sgx/stdio.rs b/src/libstd/sys/sgx/stdio.rs index a575401f5f60d..98fc89acfd315 100644 --- a/src/libstd/sys/sgx/stdio.rs +++ b/src/libstd/sys/sgx/stdio.rs @@ -30,6 +30,11 @@ impl io::Read for Stdin { impl Stdout { pub fn new() -> io::Result { Ok(Stdout(())) } + + // FIXME: implement + pub fn is_tty(&self) -> bool { + false + } } impl io::Write for Stdout { @@ -57,6 +62,7 @@ impl io::Write for Stderr { } pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE; +pub const STDOUT_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE; pub fn is_ebadf(err: &io::Error) -> bool { // FIXME: Rust normally maps Unix EBADF to `Other` diff --git a/src/libstd/sys/unix/stdio.rs b/src/libstd/sys/unix/stdio.rs index f9b017df24088..9825cc544554b 100644 --- a/src/libstd/sys/unix/stdio.rs +++ b/src/libstd/sys/unix/stdio.rs @@ -22,6 +22,10 @@ impl io::Read for Stdin { impl Stdout { pub fn new() -> io::Result { Ok(Stdout(())) } + + pub fn is_tty(&self) -> bool { + unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 } + } } impl io::Write for Stdout { @@ -61,6 +65,7 @@ pub fn is_ebadf(err: &io::Error) -> bool { } pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE; +pub const STDOUT_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE; pub fn panic_output() -> Option { Stderr::new().ok() diff --git a/src/libstd/sys/wasi/stdio.rs b/src/libstd/sys/wasi/stdio.rs index 2bf8d803c01bb..a4d4e50e5cd84 100644 --- a/src/libstd/sys/wasi/stdio.rs +++ b/src/libstd/sys/wasi/stdio.rs @@ -27,6 +27,11 @@ impl Stdout { Ok(Stdout) } + // FIXME: implement? + pub fn is_tty(&self) -> bool { + false + } + pub fn write(&self, data: &[u8]) -> io::Result { self.write_vectored(&[IoSlice::new(data)]) } @@ -71,6 +76,7 @@ impl io::Write for Stderr { } pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE; +pub const STDOUT_BUF_SIZE: usize = 0; pub fn is_ebadf(err: &io::Error) -> bool { err.raw_os_error() == Some(libc::__WASI_EBADF as i32) diff --git a/src/libstd/sys/wasm/stdio.rs b/src/libstd/sys/wasm/stdio.rs index b8899a9c84746..83be3c49d7a26 100644 --- a/src/libstd/sys/wasm/stdio.rs +++ b/src/libstd/sys/wasm/stdio.rs @@ -21,6 +21,10 @@ impl Stdout { pub fn new() -> io::Result { Ok(Stdout) } + + pub fn is_tty(&self) -> bool { + false + } } impl io::Write for Stdout { @@ -52,6 +56,7 @@ impl io::Write for Stderr { } pub const STDIN_BUF_SIZE: usize = 0; +pub const STDOUT_BUF_SIZE: usize = 0; pub fn is_ebadf(_err: &io::Error) -> bool { true diff --git a/src/libstd/sys/windows/stdio.rs b/src/libstd/sys/windows/stdio.rs index b1e76b3b755da..09bd35207aba6 100644 --- a/src/libstd/sys/windows/stdio.rs +++ b/src/libstd/sys/windows/stdio.rs @@ -246,6 +246,30 @@ impl Stdout { pub fn new() -> io::Result { Ok(Stdout) } + + pub fn is_tty(&self) -> bool { + // Because these functions do not detect if the program is being run under a Cygwin/MSYS2 + // TTY, we just try our best to see if we are being piped on Windows Console. If we can't + // tell, we fallback to assuming we are on a TTY + if let Ok(handle) = get_handle(c::STD_OUTPUT_HANDLE) { + if is_console(handle) { + true + } else { + // We need to check if at least one of the other stdio handles is connected to the + // console + let stdin = get_handle(c::STD_INPUT_HANDLE) + .map(is_console) + .unwrap_or(false); + let stderr = get_handle(c::STD_ERROR_HANDLE) + .map(is_console) + .unwrap_or(false); + + !(stdin || stderr) + } + } else { + false + } + } } impl io::Write for Stdout { From 4cd7c6255fa116ec3a270a8da084b25f3c0354e0 Mon Sep 17 00:00:00 2001 From: Alex Lyon Date: Fri, 17 May 2019 11:15:47 -0700 Subject: [PATCH 2/3] libstd: default to line buffering rather than block buffering for stdout --- src/libstd/sys/cloudabi/stdio.rs | 2 +- src/libstd/sys/sgx/stdio.rs | 2 +- src/libstd/sys/wasi/stdio.rs | 2 +- src/libstd/sys/wasm/stdio.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libstd/sys/cloudabi/stdio.rs b/src/libstd/sys/cloudabi/stdio.rs index c4d5a9e5c91c8..f24676998a01e 100644 --- a/src/libstd/sys/cloudabi/stdio.rs +++ b/src/libstd/sys/cloudabi/stdio.rs @@ -23,7 +23,7 @@ impl Stdout { } pub fn is_tty(&self) -> bool { - false + true } } diff --git a/src/libstd/sys/sgx/stdio.rs b/src/libstd/sys/sgx/stdio.rs index 98fc89acfd315..90cd879615f5c 100644 --- a/src/libstd/sys/sgx/stdio.rs +++ b/src/libstd/sys/sgx/stdio.rs @@ -33,7 +33,7 @@ impl Stdout { // FIXME: implement pub fn is_tty(&self) -> bool { - false + true } } diff --git a/src/libstd/sys/wasi/stdio.rs b/src/libstd/sys/wasi/stdio.rs index a4d4e50e5cd84..58836955fc9d8 100644 --- a/src/libstd/sys/wasi/stdio.rs +++ b/src/libstd/sys/wasi/stdio.rs @@ -29,7 +29,7 @@ impl Stdout { // FIXME: implement? pub fn is_tty(&self) -> bool { - false + true } pub fn write(&self, data: &[u8]) -> io::Result { diff --git a/src/libstd/sys/wasm/stdio.rs b/src/libstd/sys/wasm/stdio.rs index 83be3c49d7a26..59e7d7d72dc92 100644 --- a/src/libstd/sys/wasm/stdio.rs +++ b/src/libstd/sys/wasm/stdio.rs @@ -23,7 +23,7 @@ impl Stdout { } pub fn is_tty(&self) -> bool { - false + true } } From 5f4cfb9ef02f22dd327acff9d689262196514448 Mon Sep 17 00:00:00 2001 From: Alex Lyon Date: Fri, 17 May 2019 17:49:32 -0700 Subject: [PATCH 3/3] libstd: use the TTY detection code from atty on Windows --- src/libstd/sys/windows/c.rs | 15 ++++- src/libstd/sys/windows/stdio.rs | 99 ++++++++++++++++++++++++++------- 2 files changed, 92 insertions(+), 22 deletions(-) diff --git a/src/libstd/sys/windows/c.rs b/src/libstd/sys/windows/c.rs index 518eccf754cff..374272894ca16 100644 --- a/src/libstd/sys/windows/c.rs +++ b/src/libstd/sys/windows/c.rs @@ -124,7 +124,7 @@ pub struct WIN32_FIND_DATAW { pub nFileSizeLow: DWORD, pub dwReserved0: DWORD, pub dwReserved1: DWORD, - pub cFileName: [wchar_t; 260], // #define MAX_PATH 260 + pub cFileName: [wchar_t; MAX_PATH], pub cAlternateFileName: [wchar_t; 14], } impl Clone for WIN32_FIND_DATAW { @@ -168,6 +168,8 @@ pub const STD_INPUT_HANDLE: DWORD = -10i32 as DWORD; pub const STD_OUTPUT_HANDLE: DWORD = -11i32 as DWORD; pub const STD_ERROR_HANDLE: DWORD = -12i32 as DWORD; +pub const MAX_PATH: usize = 260; + pub const HANDLE_FLAG_INHERIT: DWORD = 0x00000001; pub const PROGRESS_CONTINUE: DWORD = 0; @@ -424,6 +426,12 @@ pub struct FILE_END_OF_FILE_INFO { pub EndOfFile: LARGE_INTEGER, } +#[repr(C)] +pub struct FILE_NAME_INFO { + pub FileNameLength: DWORD, + pub FileName: [WCHAR; 1], +} + #[repr(C)] pub struct REPARSE_DATA_BUFFER { pub ReparseTag: c_uint, @@ -1048,6 +1056,11 @@ extern "system" { pub fn GetFileInformationByHandle(hFile: HANDLE, lpFileInformation: LPBY_HANDLE_FILE_INFORMATION) -> BOOL; + pub fn GetFileInformationByHandleEx(hFile: HANDLE, + FileInformationClass: FILE_INFO_BY_HANDLE_CLASS, + lpFileInformation: LPVOID, + dwBufferSize: DWORD) + -> BOOL; pub fn SetLastError(dwErrCode: DWORD); pub fn GetCommandLineW() -> *mut LPCWSTR; diff --git a/src/libstd/sys/windows/stdio.rs b/src/libstd/sys/windows/stdio.rs index 09bd35207aba6..d4fcdf21a1f7e 100644 --- a/src/libstd/sys/windows/stdio.rs +++ b/src/libstd/sys/windows/stdio.rs @@ -248,27 +248,7 @@ impl Stdout { } pub fn is_tty(&self) -> bool { - // Because these functions do not detect if the program is being run under a Cygwin/MSYS2 - // TTY, we just try our best to see if we are being piped on Windows Console. If we can't - // tell, we fallback to assuming we are on a TTY - if let Ok(handle) = get_handle(c::STD_OUTPUT_HANDLE) { - if is_console(handle) { - true - } else { - // We need to check if at least one of the other stdio handles is connected to the - // console - let stdin = get_handle(c::STD_INPUT_HANDLE) - .map(is_console) - .unwrap_or(false); - let stderr = get_handle(c::STD_ERROR_HANDLE) - .map(is_console) - .unwrap_or(false); - - !(stdin || stderr) - } - } else { - false - } + is_tty(Stream::Stdout) } } @@ -305,3 +285,80 @@ pub fn is_ebadf(err: &io::Error) -> bool { pub fn panic_output() -> Option { Stderr::new().ok() } + +// NOTE: the following has been taken from the atty crate and modified to work with libstd + +enum Stream { + Stdout, + Stderr, + Stdin, +} + +fn is_tty(stream: Stream) -> bool { + let (fd, others) = match stream { + Stream::Stdin => (c::STD_INPUT_HANDLE, [c::STD_ERROR_HANDLE, c::STD_OUTPUT_HANDLE]), + Stream::Stderr => (c::STD_ERROR_HANDLE, [c::STD_INPUT_HANDLE, c::STD_OUTPUT_HANDLE]), + Stream::Stdout => (c::STD_OUTPUT_HANDLE, [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 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) } +} + +/// Returns true if any of the given fds are on a console. +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 +} + +/// Returns true if there is an MSYS tty on the given handle. +unsafe fn msys_tty_on(fd: c::DWORD) -> bool { + use std::mem; + use std::slice; + use libc::c_void; + + let size = mem::size_of::(); + let mut name_info_bytes = vec![0u8; size + c::MAX_PATH * mem::size_of::()]; + let res = c::GetFileInformationByHandleEx( + c::GetStdHandle(fd), + c::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: &c::FILE_NAME_INFO = &*(name_info_bytes.as_ptr() as *const c::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 +}