Skip to content

Commit baf0f1a

Browse files
committed
Work with stdbuf environment variables
Check `_STDBUF_O` and `_STDBUF_E` environment variables for setting up stdout and stderr. This makes Rust programs compatible with coreutils's (and `uutils`'s) `stdbuf` program. It incidentally also solves uutils/coreutils#7967. This lays ground work for solving #60673 because it lets the user of a Rust program decide the default buffering, offering the opportunity to change the current default, letting stdout buffer blockwise if it's not connected to a terminal, because the user can now override the behavior using [`stdbuf`](https://linux.die.net/man/1/stdbuf) if it's needed.
1 parent a909ae6 commit baf0f1a

File tree

2 files changed

+79
-137
lines changed

2 files changed

+79
-137
lines changed

library/std/src/io/stdio.rs

Lines changed: 78 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@
44
mod tests;
55

66
use crate::cell::{Cell, RefCell};
7-
use crate::fmt;
7+
use crate::ffi::OsStr;
88
use crate::fs::File;
99
use crate::io::prelude::*;
1010
use crate::io::{
1111
self, BorrowedCursor, BufReader, BufWriter, IoSlice, IoSliceMut, LineWriter, Lines,
1212
SpecReadByte,
1313
};
1414
use crate::panic::{RefUnwindSafe, UnwindSafe};
15+
use crate::str::FromStr as _;
1516
use crate::sync::atomic::{Atomic, AtomicBool, Ordering};
1617
use crate::sync::{Arc, Mutex, MutexGuard, OnceLock, ReentrantLock, ReentrantLockGuard};
1718
use crate::sys::stdio;
1819
use crate::thread::AccessError;
20+
use crate::{env, fmt};
1921

2022
type LocalStream = Arc<Mutex<Vec<u8>>>;
2123

@@ -579,32 +581,38 @@ impl fmt::Debug for StdinLock<'_> {
579581

580582
/// A buffered writer for stdout and stderr.
581583
///
582-
/// This writer may be either [line-buffered](LineWriter) or [block-buffered](BufWriter), depending
583-
/// on whether the underlying file is a terminal or not.
584+
/// This writer may be either [line-buffered](LineWriter),
585+
/// [block-buffered](BufWriter), or unbuffered.
584586
#[derive(Debug)]
585587
enum StdioBufWriter<W: Write> {
586588
LineBuffered(LineWriter<W>),
587589
BlockBuffered(BufWriter<W>),
590+
Unbuffered(W),
588591
}
589592

590-
impl<W: Write + IsTerminal> StdioBufWriter<W> {
593+
impl<W: Write> StdioBufWriter<W> {
591594
/// Wraps a writer using the most appropriate buffering method.
592-
///
593-
/// If `w` is a terminal, then the resulting `StdioBufWriter` will be line-buffered, otherwise
594-
/// it will be block-buffered.
595-
fn new(w: W) -> Self {
596-
if w.is_terminal() {
597-
Self::LineBuffered(LineWriter::new(w))
598-
} else {
599-
Self::BlockBuffered(BufWriter::new(w))
595+
fn from_env_value(stderr: bool, w: W, value: Option<&OsStr>) -> Self {
596+
if let Some(value) = value {
597+
if value == "L" {
598+
return StdioBufWriter::LineBuffered(LineWriter::new(w));
599+
}
600+
if let Some(size) = value.to_str().and_then(|v| usize::from_str(v).ok()) {
601+
if size == 0 {
602+
return StdioBufWriter::Unbuffered(w);
603+
} else {
604+
return StdioBufWriter::BlockBuffered(BufWriter::with_capacity(size, w));
605+
}
606+
}
600607
}
608+
Self::default_buffering(stderr, w)
601609
}
602-
}
603-
604-
impl<W: Write> StdioBufWriter<W> {
605-
/// Wraps a writer using a block-buffer with the given capacity.
606-
fn with_capacity(cap: usize, w: W) -> Self {
607-
Self::BlockBuffered(BufWriter::with_capacity(cap, w))
610+
fn default_buffering(stderr: bool, w: W) -> Self {
611+
if stderr {
612+
StdioBufWriter::Unbuffered(w)
613+
} else {
614+
StdioBufWriter::LineBuffered(LineWriter::new(w))
615+
}
608616
}
609617
}
610618

@@ -613,36 +621,42 @@ impl<W: Write> Write for StdioBufWriter<W> {
613621
match self {
614622
Self::LineBuffered(w) => w.write(buf),
615623
Self::BlockBuffered(w) => w.write(buf),
624+
Self::Unbuffered(w) => w.write(buf),
616625
}
617626
}
618627
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
619628
match self {
620629
Self::LineBuffered(w) => w.write_vectored(bufs),
621630
Self::BlockBuffered(w) => w.write_vectored(bufs),
631+
Self::Unbuffered(w) => w.write_vectored(bufs),
622632
}
623633
}
624634
fn is_write_vectored(&self) -> bool {
625635
match self {
626636
Self::LineBuffered(w) => w.is_write_vectored(),
627637
Self::BlockBuffered(w) => w.is_write_vectored(),
638+
Self::Unbuffered(w) => w.is_write_vectored(),
628639
}
629640
}
630641
fn flush(&mut self) -> io::Result<()> {
631642
match self {
632643
Self::LineBuffered(w) => w.flush(),
633644
Self::BlockBuffered(w) => w.flush(),
645+
Self::Unbuffered(w) => w.flush(),
634646
}
635647
}
636648
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
637649
match self {
638650
Self::LineBuffered(w) => w.write_all(buf),
639651
Self::BlockBuffered(w) => w.write_all(buf),
652+
Self::Unbuffered(w) => w.write_all(buf),
640653
}
641654
}
642655
fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> {
643656
match self {
644657
Self::LineBuffered(w) => w.write_all_vectored(bufs),
645658
Self::BlockBuffered(w) => w.write_all_vectored(bufs),
659+
Self::Unbuffered(w) => w.write_all_vectored(bufs),
646660
}
647661
}
648662
}
@@ -785,30 +799,46 @@ static STDOUT: OnceLock<ReentrantLock<RefCell<StdioBufWriter<StdoutRaw>>>> = Onc
785799
#[cfg_attr(not(test), rustc_diagnostic_item = "io_stdout")]
786800
pub fn stdout() -> Stdout {
787801
Stdout {
788-
inner: STDOUT
789-
.get_or_init(|| ReentrantLock::new(RefCell::new(StdioBufWriter::new(stdout_raw())))),
802+
inner: STDOUT.get_or_init(|| {
803+
ReentrantLock::new(RefCell::new(StdioBufWriter::from_env_value(
804+
false,
805+
stdout_raw(),
806+
env::var_os("_STDBUF_O").as_deref(),
807+
)))
808+
}),
790809
}
791810
}
792811

793812
// Flush the data and disable buffering during shutdown
794813
// by replacing the line writer by one with zero
795814
// buffering capacity.
796815
pub fn cleanup() {
797-
let mut initialized = false;
798-
let stdout = STDOUT.get_or_init(|| {
799-
initialized = true;
800-
ReentrantLock::new(RefCell::new(StdioBufWriter::with_capacity(0, stdout_raw())))
801-
});
802-
803-
if !initialized {
804-
// The buffer was previously initialized, overwrite it here.
805-
// We use try_lock() instead of lock(), because someone
806-
// might have leaked a StdoutLock, which would
807-
// otherwise cause a deadlock here.
808-
if let Some(lock) = stdout.try_lock() {
809-
*lock.borrow_mut() = StdioBufWriter::with_capacity(0, stdout_raw());
816+
fn cleanup<W: Write, F: Fn() -> W>(
817+
global: &'static OnceLock<ReentrantLock<RefCell<StdioBufWriter<W>>>>,
818+
init_writer: F,
819+
) {
820+
let mut initialized = false;
821+
let global = global.get_or_init(|| {
822+
initialized = true;
823+
ReentrantLock::new(RefCell::new(StdioBufWriter::Unbuffered(init_writer())))
824+
});
825+
826+
if !initialized {
827+
// The buffer was previously initialized, overwrite it here.
828+
// We use try_lock() instead of lock(), because someone
829+
// might have leaked a StdoutLock, which would
830+
// otherwise cause a deadlock here.
831+
if let Some(lock) = global.try_lock() {
832+
let mut lock = lock.borrow_mut();
833+
if !matches!(*lock, StdioBufWriter::Unbuffered(_)) {
834+
*lock = StdioBufWriter::Unbuffered(init_writer());
835+
}
836+
}
810837
}
811838
}
839+
840+
cleanup(&STDERR, stderr_raw);
841+
cleanup(&STDOUT, stdout_raw);
812842
}
813843

814844
impl Stdout {
@@ -960,7 +990,9 @@ impl fmt::Debug for StdoutLock<'_> {
960990
/// standard library or via raw Windows API calls, will fail.
961991
#[stable(feature = "rust1", since = "1.0.0")]
962992
pub struct Stderr {
963-
inner: &'static ReentrantLock<RefCell<StderrRaw>>,
993+
// FIXME: if this is not line buffered it should flush-on-panic or some
994+
// form of flush-on-abort.
995+
inner: &'static ReentrantLock<RefCell<StdioBufWriter<StderrRaw>>>,
964996
}
965997

966998
/// A locked reference to the [`Stderr`] handle.
@@ -982,9 +1014,11 @@ pub struct Stderr {
9821014
#[must_use = "if unused stderr will immediately unlock"]
9831015
#[stable(feature = "rust1", since = "1.0.0")]
9841016
pub struct StderrLock<'a> {
985-
inner: ReentrantLockGuard<'a, RefCell<StderrRaw>>,
1017+
inner: ReentrantLockGuard<'a, RefCell<StdioBufWriter<StderrRaw>>>,
9861018
}
9871019

1020+
static STDERR: OnceLock<ReentrantLock<RefCell<StdioBufWriter<StderrRaw>>>> = OnceLock::new();
1021+
9881022
/// Constructs a new handle to the standard error of the current process.
9891023
///
9901024
/// This handle is not buffered.
@@ -1033,13 +1067,15 @@ pub struct StderrLock<'a> {
10331067
#[stable(feature = "rust1", since = "1.0.0")]
10341068
#[cfg_attr(not(test), rustc_diagnostic_item = "io_stderr")]
10351069
pub fn stderr() -> Stderr {
1036-
// Note that unlike `stdout()` we don't use `at_exit` here to register a
1037-
// destructor. Stderr is not buffered, so there's no need to run a
1038-
// destructor for flushing the buffer
1039-
static INSTANCE: ReentrantLock<RefCell<StderrRaw>> =
1040-
ReentrantLock::new(RefCell::new(stderr_raw()));
1041-
1042-
Stderr { inner: &INSTANCE }
1070+
Stderr {
1071+
inner: STDERR.get_or_init(|| {
1072+
ReentrantLock::new(RefCell::new(StdioBufWriter::from_env_value(
1073+
true,
1074+
stderr_raw(),
1075+
env::var_os("_STDBUF_E").as_deref(),
1076+
)))
1077+
}),
1078+
}
10431079
}
10441080

10451081
impl Stderr {

library/std/src/io/stdio/tests.rs

Lines changed: 1 addition & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
use super::*;
2-
use crate::assert_matches::assert_matches;
3-
use crate::os::fd::{FromRawFd, OwnedFd};
42
use crate::panic::{RefUnwindSafe, UnwindSafe};
5-
use crate::sync::mpsc::{TryRecvError, sync_channel};
3+
use crate::sync::mpsc::sync_channel;
64
use crate::thread;
75

86
#[test]
@@ -166,95 +164,3 @@ where
166164
[Start1, Acquire1, Start2, Release1, Acquire2, Release2, Acquire1, Release1]
167165
);
168166
}
169-
170-
#[test]
171-
#[cfg(unix)]
172-
fn stdiobufwriter_line_buffered() {
173-
let mut fd1 = -1;
174-
let mut fd2 = -1;
175-
176-
if unsafe {
177-
libc::openpty(
178-
&raw mut fd1,
179-
&raw mut fd2,
180-
crate::ptr::null_mut(),
181-
crate::ptr::null(),
182-
crate::ptr::null(),
183-
)
184-
} < 0
185-
{
186-
panic!("openpty() failed with {:?}", io::Error::last_os_error());
187-
}
188-
189-
let writer = unsafe { File::from_raw_fd(fd1) };
190-
let mut reader = unsafe { File::from_raw_fd(fd2) };
191-
192-
assert!(writer.is_terminal(), "file descriptor returned by openpty() must be a terminal");
193-
assert!(reader.is_terminal(), "file descriptor returned by openpty() must be a terminal");
194-
195-
let (sender, receiver) = sync_channel(64);
196-
197-
thread::spawn(move || {
198-
loop {
199-
let mut buf = vec![0u8; 1024];
200-
let size = reader.read(&mut buf[..]).expect("read failed");
201-
buf.truncate(size);
202-
sender.send(buf);
203-
}
204-
});
205-
206-
let mut writer = StdioBufWriter::new(writer);
207-
assert_matches!(
208-
writer,
209-
StdioBufWriter::LineBuffered(_),
210-
"StdioBufWriter should be line-buffered when created from a terminal"
211-
);
212-
213-
writer.write_all(b"line 1\n").expect("write failed");
214-
assert_eq!(receiver.recv().expect("recv failed"), b"line 1\n");
215-
216-
writer.write_all(b"line 2\nextra ").expect("write failed");
217-
assert_eq!(receiver.recv().expect("recv failed"), b"line 2\n");
218-
219-
writer.write_all(b"line 3\n").expect("write failed");
220-
assert_eq!(receiver.recv().expect("recv failed"), b"extra line 3\n");
221-
}
222-
223-
#[test]
224-
fn stdiobufwriter_block_buffered() {
225-
let (mut reader, mut writer) = io::pipe().expect("pipe() failed");
226-
227-
// Need to convert to an OwnedFd and then into a File because PipeReader/PipeWriter don't
228-
// implement IsTerminal, but that is required by StdioBufWriter
229-
let mut reader = File::from(OwnedFd::from(reader));
230-
let mut writer = File::from(OwnedFd::from(writer));
231-
232-
assert!(!reader.is_terminal(), "file returned by pipe() cannot be a terminal");
233-
assert!(!writer.is_terminal(), "file returned by pipe() cannot be a terminal");
234-
235-
let (sender, receiver) = sync_channel(64);
236-
237-
thread::spawn(move || {
238-
loop {
239-
let mut buf = vec![0u8; 1024];
240-
let size = reader.read(&mut buf[..]).expect("read failed");
241-
buf.truncate(size);
242-
sender.send(buf);
243-
}
244-
});
245-
246-
let mut writer = StdioBufWriter::new(writer);
247-
assert_matches!(
248-
writer,
249-
StdioBufWriter::BlockBuffered(_),
250-
"StdioBufWriter should be block-buffered when created from a file that is not a terminal"
251-
);
252-
253-
writer.write_all(b"line 1\n").expect("write failed");
254-
writer.write_all(b"line 2\n").expect("write failed");
255-
writer.write_all(b"line 3\n").expect("write failed");
256-
assert_matches!(receiver.try_recv(), Err(TryRecvError::Empty));
257-
258-
writer.flush().expect("flush failed");
259-
assert_eq!(receiver.recv().expect("recv failed"), b"line 1\nline 2\nline 3\n");
260-
}

0 commit comments

Comments
 (0)