Skip to content

diff: exit 141 on a closed output pipe instead of panicking - #282

Open
MsfPablo wants to merge 1 commit into
uutils:mainfrom
MsfPablo:diff-broken-pipe-244
Open

MsfPablo wants to merge 1 commit into
uutils:mainfrom
MsfPablo:diff-broken-pipe-244

Conversation

@MsfPablo

Copy link
Copy Markdown

Summary

diff's output write is a bare .unwrap() (normal/context/unified/ed path in src/diff.rs:94, side-by-side path in src/side_diff.rs), so when the reader closes the pipe early (diff big1 big2 | head) the BrokenPipe error is unwrapped and the process panics/aborts (exit 134) instead of dying quietly to SIGPIPE like GNU (exit 141).

Fix

Added exit_on_broken_pipe_or_panic in src/utils.rs: on ErrorKind::BrokenPipe it exits with 141 (matching GNU); any other write error still panics, since that would be a genuine bug. Wired it into both write sites.

Test plan

  • cargo build — clean
  • cargo test — 21 passed
  • cargo fmt -- --check / cargo clippy --all-targets — clean (pre-existing warnings in unrelated test code untouched)
  • Manually reproduced with diff big1 big2 | head -1 — before: panic, exit 134; after: exit 141
  • Same for diff -y big1 big2 | head -1

Fixes #244

🤖 Generated with Claude Code

diff writes the full result buffer (and side-by-side writes each line)
with a bare .unwrap(), so a reader closing early (diff big1 big2 | head)
turns a BrokenPipe error into a panic and a core dump (exit 134). GNU
diff dies quietly to SIGPIPE (exit 141) in the same situation. Handle
BrokenPipe explicitly at both write sites and exit(141); any other
write error still panics since it would be a genuine bug.

Fixes uutils#244
Comment thread src/diff.rs
);
} else {
io::stdout().write_all(&result).unwrap();
} else if let Err(e) = io::stdout().write_all(&result) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the println! for --brief just above will still panic on a closed pipe, no?

Comment thread src/diff.rs
} else {
io::stdout().write_all(&result).unwrap();
} else if let Err(e) = io::stdout().write_all(&result) {
exit_on_broken_pipe_or_panic(e);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a test in tests/integration.rs (closed stdout pipe, and /dev/full)

Comment thread src/side_diff.rs
Result::Left(left_ln) => push_output(left_ln, b"", b'<', output, &config).unwrap(),
Result::Right(right_ln) => push_output(b"", right_ln, b'>', output, &config).unwrap(),
Result::Left(left_ln) => {
if let Err(e) = push_output(left_ln, b"", b'<', output, &config) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

almost the same code 3 times.
could side_diff::diff return io::Result and let diff.rs handle it once?

Comment thread src/utils.rs
/// Any other write error is a genuine bug, so it still panics.
pub fn exit_on_broken_pipe_or_panic(e: std::io::Error) -> ! {
if e.kind() == std::io::ErrorKind::BrokenPipe {
std::process::exit(141);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't want process::exit in the lib, diff should stay embeddable.
could you please propagate the error and return ExitCode::from(141) from diff::main instead?

Comment thread src/utils.rs
std::process::exit(141);
}
panic!("{e}");
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for panic

other write errors aren't bugs, e.g. diff a b > /dev/full.
GNU prints "write error" and exits 2, please do the same instead of panicking

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

diff panics (broken-pipe unwrap) when its stdout is closed early

2 participants