1#![allow(clippy::disallowed_macros, clippy::disallowed_methods)]
30
31use std::cell::Cell;
32use std::fmt;
33use std::io::{self, ErrorKind, IsTerminal, Write};
34use std::sync::Mutex;
35use std::time::Duration;
36
37#[derive(Default)]
39struct State {
40 broken: bool,
42 failure: Option<(ErrorKind, String)>,
44}
45
46struct Channel {
49 state: Mutex<State>,
50}
51
52impl Channel {
53 const fn new() -> Self {
54 Channel {
55 state: Mutex::new(State {
56 broken: false,
57 failure: None,
58 }),
59 }
60 }
61
62 fn lock(&self) -> std::sync::MutexGuard<'_, State> {
63 self.state
66 .lock()
67 .unwrap_or_else(std::sync::PoisonError::into_inner)
68 }
69
70 fn run<T>(&self, write: impl FnOnce() -> io::Result<T>) -> io::Result<T> {
73 {
74 let state = self.lock();
75 if state.broken {
76 return Err(ErrorKind::BrokenPipe.into());
77 }
78 if let Some((kind, message)) = &state.failure {
79 return Err(io::Error::new(*kind, message.clone()));
80 }
81 }
82 let result = write();
83 if let Err(err) = &result {
84 let mut state = self.lock();
85 if err.kind() == ErrorKind::BrokenPipe {
86 state.broken = true;
87 } else if state.failure.is_none() {
88 state.failure = Some((err.kind(), err.to_string()));
89 }
90 }
91 result
92 }
93
94 fn usable(&self) -> bool {
97 let state = self.lock();
98 !state.broken && state.failure.is_none()
99 }
100
101 fn failure(&self) -> Option<String> {
103 self.lock()
104 .failure
105 .as_ref()
106 .map(|(_, message)| message.clone())
107 }
108}
109
110static STDOUT: Channel = Channel::new();
111static STDERR: Channel = Channel::new();
112
113#[derive(Clone, Copy)]
115enum Stream {
116 Stdout,
117 Stderr,
118}
119
120#[derive(Clone, Copy)]
123pub struct Out {
124 stream: Stream,
125}
126
127impl Out {
128 fn channel(self) -> &'static Channel {
129 match self.stream {
130 Stream::Stdout => &STDOUT,
131 Stream::Stderr => &STDERR,
132 }
133 }
134}
135
136impl Write for Out {
137 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
138 self.write_all(buf).map(|()| buf.len())
139 }
140
141 fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
142 self.channel().run(|| match self.stream {
143 Stream::Stdout => io::stdout().write_all(buf),
144 Stream::Stderr => io::stderr().write_all(buf),
145 })
146 }
147
148 fn flush(&mut self) -> io::Result<()> {
149 self.channel().run(|| match self.stream {
150 Stream::Stdout => io::stdout().flush(),
151 Stream::Stderr => io::stderr().flush(),
152 })
153 }
154}
155
156thread_local! {
157 static STDOUT_ON_STDERR: Cell<bool> = const { Cell::new(false) };
159}
160
161pub fn stdout() -> Out {
163 Out {
164 stream: if STDOUT_ON_STDERR.get() {
165 Stream::Stderr
166 } else {
167 Stream::Stdout
168 },
169 }
170}
171
172pub fn on_stderr<T>(show: impl FnOnce() -> T) -> T {
178 struct Reset(bool);
180 impl Drop for Reset {
181 fn drop(&mut self) {
182 STDOUT_ON_STDERR.set(self.0);
183 }
184 }
185 let _reset = Reset(STDOUT_ON_STDERR.replace(true));
186 show()
187}
188
189pub fn stdout_usable() -> bool {
191 STDOUT.usable()
192}
193
194pub fn stderr_usable() -> bool {
196 STDERR.usable()
197}
198
199pub struct Unrecorded {
205 to_stderr: bool,
206}
207
208impl Write for Unrecorded {
209 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
210 self.write_all(buf).map(|()| buf.len())
211 }
212
213 fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
214 if self.to_stderr {
215 io::stderr().write_all(buf)
216 } else {
217 io::stdout().write_all(buf)
218 }
219 }
220
221 fn flush(&mut self) -> io::Result<()> {
222 if self.to_stderr {
223 io::stderr().flush()
224 } else {
225 io::stdout().flush()
226 }
227 }
228}
229
230pub fn stdout_unrecorded() -> Unrecorded {
232 Unrecorded { to_stderr: false }
233}
234
235pub fn stderr_unrecorded() -> Unrecorded {
237 Unrecorded { to_stderr: true }
238}
239
240pub fn stderr() -> Out {
242 Out {
243 stream: Stream::Stderr,
244 }
245}
246
247pub fn stdout_is_terminal() -> bool {
249 io::stdout().is_terminal()
250}
251
252pub fn stderr_is_terminal() -> bool {
254 io::stderr().is_terminal()
255}
256
257pub fn reader_gone_within(span: Duration) -> bool {
266 !STDOUT.usable() || hung_up_within(span)
267}
268
269#[cfg(unix)]
270fn hung_up_within(span: Duration) -> bool {
271 poll_hangup(libc::STDOUT_FILENO, span)
272}
273
274#[cfg(not(unix))]
275fn hung_up_within(span: Duration) -> bool {
276 std::thread::sleep(span);
277 false
278}
279
280#[cfg(unix)]
283fn poll_hangup(fd: i32, span: Duration) -> bool {
284 let deadline = std::time::Instant::now() + span;
285 let outcome = std::iter::repeat_with(|| poll_once(fd, deadline)).find_map(|done| done);
287 match outcome {
288 Some((0, _, _)) => false,
289 Some((ready, events, _)) if ready > 0 && events & (libc::POLLERR | libc::POLLHUP) != 0 => {
290 true
291 }
292 other => {
295 std::thread::sleep(other.map_or(Duration::ZERO, |(_, _, left)| left));
296 false
297 }
298 }
299}
300
301#[cfg(target_os = "netbsd")]
306const POLL_SLICE: Duration = Duration::from_millis(100);
307#[cfg(all(unix, not(target_os = "netbsd")))]
308const POLL_SLICE: Duration = Duration::MAX;
309
310#[cfg(unix)]
315fn poll_once(fd: i32, deadline: std::time::Instant) -> Option<(i32, i16, Duration)> {
316 let left = deadline.saturating_duration_since(std::time::Instant::now());
317 let millis = left
319 .min(POLL_SLICE)
320 .as_micros()
321 .div_ceil(1000)
322 .min(i32::MAX as u128) as i32;
323 let mut watched = libc::pollfd {
326 fd,
327 events: libc::POLLHUP,
328 revents: 0,
329 };
330 let ready = unsafe { libc::poll(&mut watched, 1, millis) };
332 let interrupted = ready < 0 && io::Error::last_os_error().kind() == ErrorKind::Interrupted;
333 let slice_ended = ready == 0 && std::time::Instant::now() < deadline;
334 (!interrupted && !slice_ended).then_some((ready, watched.revents, left))
335}
336
337pub fn recorded(result: io::Result<()>) {
340 drop(result);
341}
342
343pub fn print_stdout(args: fmt::Arguments<'_>, newline: bool) {
346 print_to(&mut stdout(), args, newline);
347}
348
349pub fn print_stderr(args: fmt::Arguments<'_>, newline: bool) {
352 print_to(&mut stderr(), args, newline);
353}
354
355fn print_to(out: &mut impl Write, args: fmt::Arguments<'_>, newline: bool) {
356 let written = out.write_fmt(args);
357 recorded(written);
358 if newline {
359 recorded(out.write_all(b"\n"));
360 }
361}
362
363pub fn through_stdout(print: impl FnOnce() -> io::Result<()>) {
367 recorded(STDOUT.run(print));
368}
369
370pub fn write_stdout(bytes: &[u8]) {
372 recorded(stdout().write_all(bytes));
373}
374
375#[macro_export]
377macro_rules! out_print {
378 ($($arg:tt)*) => {
379 $crate::ui::out::print_stdout(::std::format_args!($($arg)*), false)
380 };
381}
382
383#[macro_export]
385macro_rules! out_println {
386 () => {
387 $crate::ui::out::print_stdout(::std::format_args!(""), true)
388 };
389 ($($arg:tt)*) => {
390 $crate::ui::out::print_stdout(::std::format_args!($($arg)*), true)
391 };
392}
393
394#[macro_export]
396macro_rules! err_print {
397 ($($arg:tt)*) => {
398 $crate::ui::out::print_stderr(::std::format_args!($($arg)*), false)
399 };
400}
401
402#[macro_export]
404macro_rules! err_println {
405 () => {
406 $crate::ui::out::print_stderr(::std::format_args!(""), true)
407 };
408 ($($arg:tt)*) => {
409 $crate::ui::out::print_stderr(::std::format_args!($($arg)*), true)
410 };
411}
412
413pub fn finish(code: u8) -> u8 {
419 recorded(stdout().flush());
420 resolve(code, &STDOUT, &STDERR, &mut stderr())
421}
422
423fn resolve(code: u8, stdout: &Channel, stderr: &Channel, report: &mut impl Write) -> u8 {
424 let stdout_failure = stdout.failure();
425 if let Some(message) = &stdout_failure {
426 recorded(writeln!(report, "proc: cannot write to stdout: {message}"));
427 }
428 if code == 0 && (stdout_failure.is_some() || stderr.failure().is_some()) {
429 1
430 } else {
431 code
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438
439 fn broken() -> io::Result<()> {
440 Err(ErrorKind::BrokenPipe.into())
441 }
442
443 fn full() -> io::Result<()> {
444 Err(io::Error::other("no space left on device"))
445 }
446
447 #[test]
448 fn a_broken_pipe_stops_the_stream_without_a_failure() {
449 let channel = Channel::new();
450 assert!(channel.run(|| Ok(())).is_ok());
451 assert_eq!(
452 channel.run(broken).unwrap_err().kind(),
453 ErrorKind::BrokenPipe
454 );
455 let err = channel
457 .run(|| -> io::Result<()> { panic!("must not write after a broken pipe") })
458 .unwrap_err();
459 assert_eq!(err.kind(), ErrorKind::BrokenPipe);
460 assert_eq!(channel.failure(), None);
461 }
462
463 #[test]
464 fn another_write_error_is_a_failure_that_sticks() {
465 let channel = Channel::new();
466 assert_eq!(
467 channel.run(full).unwrap_err().to_string(),
468 "no space left on device"
469 );
470 let err = channel
471 .run(|| -> io::Result<()> { panic!("must not write after a failure") })
472 .unwrap_err();
473 assert_eq!(err.to_string(), "no space left on device");
474 assert_eq!(
475 channel.failure().as_deref(),
476 Some("no space left on device")
477 );
478 }
479
480 #[test]
481 fn the_first_failure_is_the_one_kept() {
482 let channel = Channel::new();
483 let _ = channel.run(full);
484 let _ = channel.run(|| Err::<(), _>(io::Error::new(ErrorKind::BrokenPipe, "late")));
486 assert_eq!(
487 channel.failure().as_deref(),
488 Some("no space left on device")
489 );
490 }
491
492 #[test]
493 fn a_poisoned_lock_is_still_usable() {
494 let channel = Channel::new();
495 std::thread::scope(|scope| {
496 let _ = scope
497 .spawn(|| {
498 let _held = channel.state.lock().unwrap();
499 panic!("poison the lock");
500 })
501 .join();
502 });
503 assert!(channel.state.is_poisoned());
504 assert!(channel.run(|| Ok(())).is_ok());
505 }
506
507 #[test]
508 fn the_exit_code_follows_the_command_unless_a_write_failed() {
509 let (stdout, stderr) = (Channel::new(), Channel::new());
510 let mut report = Vec::new();
511 assert_eq!(resolve(0, &stdout, &stderr, &mut report), 0);
512 assert_eq!(resolve(2, &stdout, &stderr, &mut report), 2);
513 assert!(report.is_empty());
514
515 let _ = stdout.run(broken);
517 let _ = stderr.run(broken);
518 assert_eq!(resolve(0, &stdout, &stderr, &mut report), 0);
519 assert_eq!(resolve(2, &stdout, &stderr, &mut report), 2);
520 assert!(report.is_empty());
521 }
522
523 #[test]
524 fn a_failed_stdout_is_exit_1_and_is_reported() {
525 let (stdout, stderr) = (Channel::new(), Channel::new());
526 let _ = stdout.run(full);
527 let mut report = Vec::new();
528 assert_eq!(resolve(0, &stdout, &stderr, &mut report), 1);
529 assert_eq!(
530 String::from_utf8(report).unwrap(),
531 "proc: cannot write to stdout: no space left on device\n"
532 );
533 assert_eq!(resolve(2, &stdout, &stderr, &mut Vec::new()), 2);
535 }
536
537 #[test]
538 fn a_failed_stderr_is_exit_1_and_says_nothing() {
539 let (stdout, stderr) = (Channel::new(), Channel::new());
540 let _ = stderr.run(full);
541 let mut report = Vec::new();
542 assert_eq!(resolve(0, &stdout, &stderr, &mut report), 1);
543 assert!(report.is_empty());
544 assert_eq!(resolve(3, &stdout, &stderr, &mut report), 3);
545 }
546
547 #[test]
548 fn the_message_is_lost_quietly_when_the_report_fails_too() {
549 let (stdout, stderr) = (Channel::new(), Channel::new());
550 let _ = stdout.run(full);
551 let mut gone = crate::test_support::FailsAfter {
552 left: 0,
553 written: Vec::new(),
554 };
555 assert_eq!(resolve(0, &stdout, &stderr, &mut gone), 1);
556 }
557
558 #[test]
559 fn the_real_streams_take_an_empty_write() {
560 for mut out in [stdout(), stderr()] {
561 assert_eq!(out.write(b"").unwrap(), 0);
562 out.flush().unwrap();
563 }
564 }
565
566 #[test]
567 fn a_stream_is_usable_until_something_goes_wrong_on_it() {
568 let channel = Channel::new();
569 assert!(channel.usable());
570 let _ = channel.run(broken);
571 assert!(!channel.usable());
572
573 let channel = Channel::new();
574 let _ = channel.run(full);
575 assert!(!channel.usable());
576 }
577
578 #[test]
579 fn on_stderr_sends_stdout_to_stderr_and_puts_it_back() {
580 let on = || matches!(stdout().stream, Stream::Stderr);
581 assert!(!on());
582 assert!(on_stderr(on));
583 assert!(on_stderr(|| {
585 on_stderr(|| ());
586 on()
587 }));
588 assert!(!on());
589 let caught = std::panic::catch_unwind(|| on_stderr(|| panic!("a list that panics")));
591 assert!(caught.is_err());
592 assert!(!on());
593 }
594
595 #[test]
596 fn unrecorded_writers_take_an_empty_write() {
597 for mut out in [stdout_unrecorded(), stderr_unrecorded()] {
598 assert_eq!(out.write(b"").unwrap(), 0);
599 out.flush().unwrap();
600 }
601 }
602
603 #[test]
604 fn a_wait_with_a_reader_still_there_lasts_the_whole_time() {
605 let started = std::time::Instant::now();
606 assert!(!reader_gone_within(Duration::from_millis(40)));
607 assert!(started.elapsed() >= Duration::from_millis(40));
608 }
609
610 #[cfg(unix)]
611 mod hangup {
612 use super::super::*;
613 use std::os::fd::{AsRawFd, IntoRawFd};
614 use std::time::Instant;
615
616 #[test]
617 fn a_pipe_whose_reader_left_ends_the_wait_at_once() {
618 let (reader, writer) = std::io::pipe().unwrap();
619 let fd = writer.as_raw_fd();
620
621 let started = Instant::now();
623 assert!(!poll_hangup(fd, Duration::from_millis(40)));
624 assert!(started.elapsed() >= Duration::from_millis(40));
625
626 drop(reader);
627 let started = Instant::now();
628 assert!(poll_hangup(fd, Duration::from_secs(30)));
629 assert!(started.elapsed() < Duration::from_secs(5));
630 }
631
632 #[test]
633 fn a_file_has_no_reader_to_lose() {
634 let file = std::fs::File::open("/dev/null").unwrap();
635 let started = Instant::now();
636 assert!(!poll_hangup(file.as_raw_fd(), Duration::from_millis(40)));
637 assert!(started.elapsed() >= Duration::from_millis(40));
638 }
639
640 #[test]
641 fn a_descriptor_that_is_not_open_just_waits() {
642 let (reader, _writer) = std::io::pipe().unwrap();
643 let closed = reader.into_raw_fd();
644 unsafe { libc::close(closed) };
646 let started = Instant::now();
647 assert!(!poll_hangup(closed, Duration::from_millis(40)));
648 assert!(started.elapsed() >= Duration::from_millis(40));
649 }
650 }
651
652 #[test]
653 fn print_to_writes_the_text_and_the_newline() {
654 let mut out = Vec::new();
655 print_to(&mut out, format_args!("{} {}", "a", 1), true);
656 print_to(&mut out, format_args!("b"), false);
657 assert_eq!(out, b"a 1\nb");
658 }
659
660 #[test]
661 fn print_to_survives_a_failing_writer() {
662 let mut gone = crate::test_support::FailsAfter {
663 left: 1,
664 written: Vec::new(),
665 };
666 print_to(&mut gone, format_args!("abc"), true);
667 assert_eq!(gone.written, b"a");
668 }
669}