Skip to content

SIGPIPE handling is missing in many utilities #8919

@naoNao89

Description

@naoNao89

I noticed that when you pipe output and close it early (like seq inf | head -n 1), many utilities panic with error messages instead of exiting silently like GNU coreutils does.

Looks like we've known about this since 2014 (#374) but only 7 out of 102 utilities handle it properly.

Why it matters

POSIX 1003.1-2001 says:

SIGPIPE shall be sent to a process that attempts to write to a pipe when there are no readers. Default action: Terminate the process.

GNU Coreutils does this:

'PIPE': Write on a pipe with no one to read it.

POSIX ref | GNU ref

They both say utilities should just quit silently. We should probably do the same.

Why this hasn't been fixed yet

I did some digging. Here's what I think happened:

  1. Rust ignores SIGPIPE by default (rust-lang/rust#62569)
  2. The bug only shows up in specific cases, so not many people report it
  3. Since it affects all utilities, nobody took ownership
  4. Different people tried different fixes over the years
  5. There was no documentation on how to handle it (until recently)

Good news: cat (4406b403), tail (f04ed45a), and tr (c1eeed61) were fixed in 2025, so there's momentum now.

What's working and what's not

Already fixed: cat, env, tail, tee, timeout, tr, yes (7 utilities)
Still broken: seq, head, echo, ls, wc, cut, sort, uniq, nl, and more (>17+ utilities)

Suggested fix

Looking at how other utilities handle this, most use the signal handler approach from uucore::signals:

Examples from the codebase:

They all do this:

use uucore::signals::enable_pipe_errors;

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
    #[cfg(unix)]
    enable_pipe_errors()?;
    
    // rest of the code
}

note: cat uses a different approach with direct libc calls 4406b40, but the enable_pipe_errors() method seems to be the pattern most utilities follow.

For testing, cat has a good example: (tests/by-util/test_cat.rs:122-135)

#[test]
fn test_broken_pipe() {
    let mut child = new_ucmd!()
        .args(&["alpha.txt"])
        .set_stdout(Stdio::piped())
        .run_no_wait();
    
    child.close_stdout();
    child.wait().unwrap().fails_silently();
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions