uucore/mods/panic.rs
1// This file is part of the uutils coreutils package.
2//
3// For the full copyright and license information, please view the LICENSE
4// file that was distributed with this source code.
5//! Custom panic hooks that allow silencing certain types of errors.
6//!
7//! Use the [`mute_sigpipe_panic`] function to silence panics caused by
8//! broken pipe errors. This can happen when a process is still
9//! producing data when the consuming process terminates and closes the
10//! pipe. For example,
11//!
12//! ```sh
13//! $ seq inf | head -n 1
14//! ```
15//!
16use std::panic::{self, PanicHookInfo};
17
18/// Decide whether a panic was caused by a broken pipe (SIGPIPE) error.
19fn is_broken_pipe(info: &PanicHookInfo) -> bool {
20 info.payload()
21 .downcast_ref::<String>()
22 .is_some_and(|res| res.contains("BrokenPipe") || res.contains("Broken pipe"))
23}
24
25/// Terminate without error on panics that occur due to broken pipe errors.
26///
27/// For background discussions on `SIGPIPE` handling, see
28///
29/// * `<https://github.com/uutils/coreutils/issues/374>`
30/// * `<https://github.com/uutils/coreutils/pull/1106>`
31/// * `<https://github.com/rust-lang/rust/issues/62569>`
32/// * `<https://github.com/BurntSushi/ripgrep/issues/200>`
33/// * `<https://github.com/crev-dev/cargo-crev/issues/287>`
34///
35pub fn mute_sigpipe_panic() {
36 let hook = panic::take_hook();
37 panic::set_hook(Box::new(move |info| {
38 if !is_broken_pipe(info) {
39 hook(info);
40 }
41 }));
42}
43
44/// Preserve inherited SIGPIPE settings from parent process.
45///
46/// Rust unconditionally sets SIGPIPE to SIG_IGN on startup. This function
47/// checks if the parent process (e.g., `env --default-signal=PIPE`) intended
48/// for SIGPIPE to be set to default by checking the RUST_SIGPIPE environment
49/// variable. If set to "default", it restores SIGPIPE to SIG_DFL.
50#[cfg(unix)]
51pub fn preserve_inherited_sigpipe() {
52 use nix::libc;
53
54 // Check if parent specified that SIGPIPE should be default
55 if std::env::var_os("RUST_SIGPIPE").is_some_and(|v| v == "default") {
56 unsafe { libc::signal(libc::SIGPIPE, libc::SIG_DFL) };
57 // Remove the environment variable so child processes don't inherit it incorrectly
58 unsafe { std::env::remove_var("RUST_SIGPIPE") };
59 }
60}
61
62#[cfg(not(unix))]
63pub fn preserve_inherited_sigpipe() {
64 // No-op on non-Unix platforms
65}