diff --git a/src/libstd/io/stdio.rs b/src/libstd/io/stdio.rs index c798ee0e2209a..be0b48ba10e02 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}; @@ -84,6 +84,11 @@ impl Read for StdinRaw { Initializer::nop() } } +impl StdoutRaw { + fn should_be_line_buffered(&self) -> bool { + self.0.should_be_line_buffered() + } +} impl Write for StdoutRaw { fn write(&mut self, buf: &[u8]) -> io::Result { self.0.write(buf) } @@ -103,48 +108,31 @@ impl Write for StderrRaw { fn flush(&mut self) -> io::Result<()> { self.0.flush() } } -enum Maybe { - Real(T), - Fake, -} +/// Maps `EBADF` errors for reading and writing to success. +struct HandleEbadf(T); -impl io::Write for Maybe { +impl Write for HandleEbadf { fn write(&mut self, buf: &[u8]) -> io::Result { - match *self { - Maybe::Real(ref mut w) => handle_ebadf(w.write(buf), buf.len()), - Maybe::Fake => Ok(buf.len()) - } + handle_ebadf(self.0.write(buf), buf.len()) } - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { let total = bufs.iter().map(|b| b.len()).sum(); - match self { - Maybe::Real(w) => handle_ebadf(w.write_vectored(bufs), total), - Maybe::Fake => Ok(total), - } + handle_ebadf(self.0.write_vectored(bufs), total) } - fn flush(&mut self) -> io::Result<()> { - match *self { - Maybe::Real(ref mut w) => handle_ebadf(w.flush(), ()), - Maybe::Fake => Ok(()) - } + handle_ebadf(self.0.flush(), ()) } } -impl io::Read for Maybe { +impl Read for HandleEbadf { fn read(&mut self, buf: &mut [u8]) -> io::Result { - match *self { - Maybe::Real(ref mut r) => handle_ebadf(r.read(buf), 0), - Maybe::Fake => Ok(0) - } + handle_ebadf(self.0.read(buf), 0) } - fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { - match self { - Maybe::Real(r) => handle_ebadf(r.read_vectored(bufs), 0), - Maybe::Fake => Ok(0) - } + handle_ebadf(self.0.read_vectored(bufs), 0) + } + unsafe fn initializer(&self) -> Initializer { + self.0.initializer() } } @@ -155,6 +143,121 @@ fn handle_ebadf(r: io::Result, default: T) -> io::Result { } } +enum StdioBufferKind { + LineBuffered, + Buffered, + Unbuffered, +} + +/// A reader that can either be buffered or fake. +/// +/// If it is fake, all reads succeed and indicate end-of-file. +enum StdioReader { + Buffered(BufReader>), + Fake, +} + +impl StdioReader { + fn new_buffered(reader: R) -> StdioReader { + let cap = stdio::STDIN_BUF_SIZE; + StdioReader::Buffered(BufReader::with_capacity(cap, HandleEbadf(reader))) + } + fn new_fake() -> StdioReader { + StdioReader::Fake + } +} + +impl Read for StdioReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self { + StdioReader::Buffered(i) => i.read(buf), + StdioReader::Fake => Ok(0), + } + } + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + match self { + StdioReader::Buffered(i) => i.read_vectored(bufs), + StdioReader::Fake => Ok(0), + } + } + #[inline] + unsafe fn initializer(&self) -> Initializer { + match self { + StdioReader::Buffered(i) => i.initializer(), + StdioReader::Fake => Initializer::nop(), + } + } +} + +impl BufRead for StdioReader { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + match self { + StdioReader::Buffered(i) => i.fill_buf(), + StdioReader::Fake => Ok(&[]), + } + } + fn consume(&mut self, amt: usize) { + match self { + StdioReader::Buffered(i) => i.consume(amt), + StdioReader::Fake => (), + } + } +} + +/// A writer that can either be line-buffered, buffered, unbuffered or fake. +/// +/// If it is fake, all outputs just succeed and are dropped silently. +enum StdioWriter { + LineBuffered(LineWriter>), + Buffered(BufWriter>), + Unbuffered(HandleEbadf), + Fake, +} + +impl StdioWriter { + fn new(writer: W, kind: StdioBufferKind) -> StdioWriter { + let writer = HandleEbadf(writer); + match kind { + StdioBufferKind::LineBuffered => + StdioWriter::LineBuffered(LineWriter::new(writer)), + StdioBufferKind::Buffered => + StdioWriter::Buffered(BufWriter::new(writer)), + StdioBufferKind::Unbuffered => + StdioWriter::Unbuffered(writer), + } + } + fn new_fake() -> StdioWriter { + StdioWriter::Fake + } +} + +impl Write for StdioWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self { + StdioWriter::LineBuffered(i) => i.write(buf), + StdioWriter::Buffered(i) => i.write(buf), + StdioWriter::Unbuffered(i) => i.write(buf), + StdioWriter::Fake => Ok(buf.len()), + } + } + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + match self { + StdioWriter::LineBuffered(i) => i.write_vectored(bufs), + StdioWriter::Buffered(i) => i.write_vectored(bufs), + StdioWriter::Unbuffered(i) => i.write_vectored(bufs), + StdioWriter::Fake => Ok(bufs.iter().map(|b| b.len()).sum()), + } + } + fn flush(&mut self) -> io::Result<()> { + match self { + StdioWriter::LineBuffered(i) => i.flush(), + StdioWriter::Buffered(i) => i.flush(), + StdioWriter::Unbuffered(i) => i.flush(), + StdioWriter::Fake => Ok(()), + } + } +} + /// A handle to the standard input stream of a process. /// /// Each handle is a shared reference to a global buffer of input data to this @@ -176,7 +279,7 @@ fn handle_ebadf(r: io::Result, default: T) -> io::Result { /// an error. #[stable(feature = "rust1", since = "1.0.0")] pub struct Stdin { - inner: Arc>>>, + inner: Arc>>, } /// A locked reference to the `Stdin` handle. @@ -194,7 +297,7 @@ pub struct Stdin { /// an error. #[stable(feature = "rust1", since = "1.0.0")] pub struct StdinLock<'a> { - inner: MutexGuard<'a, BufReader>>, + inner: MutexGuard<'a, StdioReader>, } /// Constructs a new handle to the standard input of the current process. @@ -240,21 +343,21 @@ pub struct StdinLock<'a> { /// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn stdin() -> Stdin { - static INSTANCE: Lazy>>> = Lazy::new(); + static INSTANCE: Lazy>> = Lazy::new(); return Stdin { inner: unsafe { INSTANCE.get(stdin_init).expect("cannot access stdin during shutdown") }, }; - fn stdin_init() -> Arc>>> { + fn stdin_init() -> Arc>> { // This must not reentrantly access `INSTANCE` let stdin = match stdin_raw() { - Ok(stdin) => Maybe::Real(stdin), - _ => Maybe::Fake + Ok(stdin) => StdioReader::new_buffered(stdin), + _ => StdioReader::new_fake(), }; - Arc::new(Mutex::new(BufReader::with_capacity(stdio::STDIN_BUF_SIZE, stdin))) + Arc::new(Mutex::new(stdin)) } } @@ -398,10 +501,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 +518,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 +564,27 @@ 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 buffering = if stdout.should_be_line_buffered() { + StdioBufferKind::LineBuffered + } else { + StdioBufferKind::Buffered + }; + StdioWriter::new(stdout, buffering) + }, + _ => StdioWriter::new_fake(), }; - Arc::new(ReentrantMutex::new(RefCell::new(LineWriter::new(stdout)))) + Arc::new(ReentrantMutex::new(RefCell::new(stdout))) } } @@ -565,7 +672,7 @@ impl fmt::Debug for StdoutLock<'_> { /// an error. #[stable(feature = "rust1", since = "1.0.0")] pub struct Stderr { - inner: Arc>>>, + inner: Arc>>>, } /// A locked reference to the `Stderr` handle. @@ -581,7 +688,7 @@ pub struct Stderr { /// an error. #[stable(feature = "rust1", since = "1.0.0")] pub struct StderrLock<'a> { - inner: ReentrantMutexGuard<'a, RefCell>>, + inner: ReentrantMutexGuard<'a, RefCell>>, } /// Constructs a new handle to the standard error of the current process. @@ -623,18 +730,18 @@ pub struct StderrLock<'a> { /// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn stderr() -> Stderr { - static INSTANCE: Lazy>>> = Lazy::new(); + static INSTANCE: Lazy>>> = Lazy::new(); return Stderr { inner: unsafe { INSTANCE.get(stderr_init).expect("cannot access stderr during shutdown") }, }; - fn stderr_init() -> Arc>>> { + fn stderr_init() -> Arc>>> { // This must not reentrantly access `INSTANCE` let stderr = match stderr_raw() { - Ok(stderr) => Maybe::Real(stderr), - _ => Maybe::Fake, + Ok(stderr) => StdioWriter::new(stderr, StdioBufferKind::Unbuffered), + _ => StdioWriter::new_fake(), }; Arc::new(ReentrantMutex::new(RefCell::new(stderr))) } diff --git a/src/libstd/sys/cloudabi/stdio.rs b/src/libstd/sys/cloudabi/stdio.rs index 601563c5b1fcb..7ce94820696ca 100644 --- a/src/libstd/sys/cloudabi/stdio.rs +++ b/src/libstd/sys/cloudabi/stdio.rs @@ -21,6 +21,9 @@ impl Stdout { pub fn new() -> io::Result { Ok(Stdout(())) } + pub fn should_be_line_buffered(&self) -> bool { + true + } } impl io::Write for Stdout { diff --git a/src/libstd/sys/sgx/stdio.rs b/src/libstd/sys/sgx/stdio.rs index a575401f5f60d..c423880bc3183 100644 --- a/src/libstd/sys/sgx/stdio.rs +++ b/src/libstd/sys/sgx/stdio.rs @@ -30,6 +30,10 @@ impl io::Read for Stdin { impl Stdout { pub fn new() -> io::Result { Ok(Stdout(())) } + pub fn should_be_line_buffered(&self) -> bool { + // FIXME: Implement me. + true + } } impl io::Write for Stdout { diff --git a/src/libstd/sys/unix/stdio.rs b/src/libstd/sys/unix/stdio.rs index f9b017df24088..2c3bad1711f6a 100644 --- a/src/libstd/sys/unix/stdio.rs +++ b/src/libstd/sys/unix/stdio.rs @@ -22,6 +22,11 @@ impl io::Read for Stdin { impl Stdout { pub fn new() -> io::Result { Ok(Stdout(())) } + pub fn should_be_line_buffered(&self) -> bool { + unsafe { + libc::isatty(libc::STDOUT_FILENO) != 0 + } + } } impl io::Write for Stdout { diff --git a/src/libstd/sys/vxworks/stdio.rs b/src/libstd/sys/vxworks/stdio.rs index 35f163bbdb10f..46f13b2b47e65 100644 --- a/src/libstd/sys/vxworks/stdio.rs +++ b/src/libstd/sys/vxworks/stdio.rs @@ -20,6 +20,10 @@ impl io::Read for Stdin { impl Stdout { pub fn new() -> io::Result { Ok(Stdout(())) } + pub fn should_be_line_buffered(&self) -> bool { + // FIXME: Implement me. + true + } } impl io::Write for Stdout { diff --git a/src/libstd/sys/wasi/stdio.rs b/src/libstd/sys/wasi/stdio.rs index 1d57b9922e599..3e9e3ebbcfa7a 100644 --- a/src/libstd/sys/wasi/stdio.rs +++ b/src/libstd/sys/wasi/stdio.rs @@ -40,6 +40,12 @@ impl Stdout { pub fn flush(&self) -> io::Result<()> { Ok(()) } + + pub fn should_be_line_buffered(&self) -> bool { + // FIXME: Currently there seems to be no way to query whether stdout is + // a tty, `isatty` is not exposed by WASI. + true + } } impl Stderr { diff --git a/src/libstd/sys/wasm/stdio.rs b/src/libstd/sys/wasm/stdio.rs index 5a4e4505e93bd..34b8d9caedcf6 100644 --- a/src/libstd/sys/wasm/stdio.rs +++ b/src/libstd/sys/wasm/stdio.rs @@ -30,6 +30,10 @@ impl io::Write for Stdout { fn flush(&mut self) -> io::Result<()> { Ok(()) } + + fn should_be_line_buffered(&self) -> bool { + true + } } impl Stderr { diff --git a/src/libstd/sys/windows/stdio.rs b/src/libstd/sys/windows/stdio.rs index b1e76b3b755da..32156d478b0e5 100644 --- a/src/libstd/sys/windows/stdio.rs +++ b/src/libstd/sys/windows/stdio.rs @@ -246,6 +246,11 @@ impl Stdout { pub fn new() -> io::Result { Ok(Stdout) } + pub fn should_be_line_buffered(&self) -> bool { + // FIXME: Fill in. I don't know how to check whether output is + // redirected on Windows. + true + } } impl io::Write for Stdout { diff --git a/src/libstd/sys/windows/stdio_uwp.rs b/src/libstd/sys/windows/stdio_uwp.rs index 489d3df28600b..9f5e27722b47d 100644 --- a/src/libstd/sys/windows/stdio_uwp.rs +++ b/src/libstd/sys/windows/stdio_uwp.rs @@ -48,6 +48,11 @@ impl Stdout { pub fn new() -> io::Result { Ok(Stdout) } + pub fn should_be_line_buffered(&self) -> bool { + // FIXME: Fill in. I don't know how to check whether output is + // redirected on Windows. + true + } } impl io::Write for Stdout { diff --git a/src/test/ui/command-pre-exec.rs b/src/test/ui/command-pre-exec.rs index c0fc554183a43..1a88ce1fdc97b 100644 --- a/src/test/ui/command-pre-exec.rs +++ b/src/test/ui/command-pre-exec.rs @@ -10,7 +10,7 @@ extern crate libc; use std::env; -use std::io::Error; +use std::io::{self, Error, Write}; use std::os::unix::process::CommandExt; use std::process::Command; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -35,6 +35,7 @@ fn main() { .arg("test1") .pre_exec(|| { println!("hello"); + io::stdout().flush().unwrap(); Ok(()) }) .output() diff --git a/src/test/ui/issues/issue-30490.rs b/src/test/ui/issues/issue-30490.rs index 76e72246887b6..59a1c5eb372a0 100644 --- a/src/test/ui/issues/issue-30490.rs +++ b/src/test/ui/issues/issue-30490.rs @@ -57,6 +57,7 @@ fn main() { stdout().write_all("parent stdout\n".as_bytes()).expect("failed to write to stdout"); stderr().write_all("parent stderr\n".as_bytes()).expect("failed to write to stderr"); + stdout().flush().expect("failed to flush stdout"); let child = { Command::new(name)