Skip to main content

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}