zsh/extensions/stdout_ferror.rs
1//! The error indicator of C's `stdout` FILE for builtin output.
2//!
3//! `execcmd_exec` flushes stdout after every builtin and, when the command
4//! did not redirect fd 1, reports `write error: %e` if `ferror(stdout)` is
5//! set (c:Src/exec.c:4298-4305).
6//!
7//! !!! WARNING: RUST-ONLY !!! `std::io::Stdout` treats `EBADF` as a
8//! successful write (std's `handle_ebadf`), so a write to a closed fd 1
9//! never reports an error to the caller and `ferror(stdout)` cannot be
10//! observed through it. print/echo/printf output goes through
11//! [`fwrite_stdout`], which writes with write(2) and records the failure.
12
13use std::io::Write as _;
14use std::sync::atomic::{AtomicI32, Ordering};
15
16/// errno of the failed stdout write; 0 = clear (C: `clearerr(stdout)`).
17pub static STDOUT_FERROR: AtomicI32 = AtomicI32::new(0);
18
19/// `fwrite(buf, 1, len, stdout)`. Bytes already buffered in `lk` are
20/// flushed first to keep output order.
21pub fn fwrite_stdout(lk: &mut std::io::StdoutLock<'_>, bufs: &[&[u8]]) {
22 let _ = lk.flush();
23 for buf in bufs {
24 let mut off = 0;
25 while off < buf.len() {
26 let n = unsafe {
27 libc::write(1, buf[off..].as_ptr() as *const libc::c_void, buf.len() - off)
28 };
29 if n < 0 {
30 let e = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
31 if e == libc::EINTR {
32 continue;
33 }
34 STDOUT_FERROR.store(e, Ordering::Relaxed);
35 return;
36 }
37 off += n as usize;
38 }
39 }
40}
41
42/// `ferror(stdout)` + `clearerr(stdout)`: the recorded errno, cleared.
43pub fn take_stdout_ferror() -> i32 {
44 STDOUT_FERROR.swap(0, Ordering::Relaxed)
45}