Skip to content

Conversation

@naoNao89
Copy link
Contributor

@naoNao89 naoNao89 commented Dec 4, 2025

When SIGPIPE is trapped (trap '' PIPE), write fails with EPIPE instead of signal. Print diagnostic message to stderr like GNU coreutils, instead of silently exiting.

Test with trapped SIGPIPE (the issue scenario)

$ (trap '' PIPE && ./target/debug/yes | :) 2>&1
yes: stdout: Broken pipe

Compare with GNU yes

$ (trap '' PIPE && /usr/bin/yes | :) 2>&1  
yes: stdout: Broken pipe  (matches)

Refs: POSIX Signal specs, GNU Signal handling

Fixes #7252

@github-actions
Copy link

github-actions bot commented Dec 4, 2025

GNU testsuite comparison:

Skip an intermittent issue tests/misc/tee (fails in this run but passes in the 'main' branch)

@naoNao89 naoNao89 force-pushed the fix-yes-sigpipe-trap-7252 branch from ee21361 to 9b6ca30 Compare December 4, 2025 21:39
@github-actions
Copy link

github-actions bot commented Dec 4, 2025

GNU testsuite comparison:

Skip an intermittent issue tests/misc/tee (fails in this run but passes in the 'main' branch)

@collinfunk
Copy link

While it looks correct in the case you list, it doesn't fix the handling of SIGPIPE.

See the following example:

$ yes | head -n 1
y
$ echo ${PIPESTATUS[0]} ${PIPESTATUS[1]} 
141 0
$ ./target/debug/yes | head -n 1
y
yes: stdout: Broken pipe
$ echo ${PIPESTATUS[0]} ${PIPESTATUS[1]} 
0 0

@naoNao89 naoNao89 force-pushed the fix-yes-sigpipe-trap-7252 branch from 9b6ca30 to 4c03f54 Compare December 5, 2025 06:03
@naoNao89
Copy link
Contributor Author

naoNao89 commented Dec 5, 2025

$ echo ${PIPESTATUS[0]} ${PIPESTATUS[1]}
$ ./target/debug/yes | head -n 1
y

@naoNao89 naoNao89 force-pushed the fix-yes-sigpipe-trap-7252 branch 2 times, most recently from 6a77438 to 7ada39a Compare December 5, 2025 06:11
@codspeed-hq
Copy link

codspeed-hq bot commented Dec 5, 2025

CodSpeed Performance Report

Merging #9560 will not alter performance

Comparing naoNao89:fix-yes-sigpipe-trap-7252 (1ffaedd) with main (7c62885)

Summary

✅ 127 untouched
⏩ 6 skipped1

Footnotes

  1. 6 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@naoNao89 naoNao89 force-pushed the fix-yes-sigpipe-trap-7252 branch from 7ada39a to 407b819 Compare December 5, 2025 06:13
@github-actions
Copy link

github-actions bot commented Dec 5, 2025

GNU testsuite comparison:

Skipping an intermittent issue tests/tail/overlay-headers (passes in this run but fails in the 'main' branch)

@collinfunk
Copy link

This looks incorrect. POSIX states [1]:

Signals set to the default action (SIG_DFL) in the calling process image shall be set to the default action in the new process image. Except for SIGCHLD, signals set to be ignored (SIG_IGN) by the calling process image shall be set to be ignored by the new process image.

If yes is created by a process where SIGPIPE is ignored, setting it to it's default would be incorrect.

See the following example:

$ env --ignore-signal=PIPE strace -o output-gnu yes | :
yes: standard output: Broken pipe
$ env --ignore-signal=PIPE strace -o output-uutils ./target/debug/yes | :
$ grep -F 'killed by SIGPIPE' output-gnu output-uutils 
output-uutils:+++ killed by SIGPIPE +++

So, I don't think this issue is fixable unless Rust has a way to remove the signal (SIGPIPE, SIG_IGN) call they add at startup.

[1] https://pubs.opengroup.org/onlinepubs/9799919799/functions/exec.html

@naoNao89
Copy link
Contributor Author

naoNao89 commented Dec 6, 2025

the rust problem

Rust sets SIGPIPE = SIG_IGN by default at startup, making it impossible to distinguish:

  1. Parent explicitly set SIG_IGN (must be preserved per POSIX)
  2. Rust's default SIG_IGN (could theoretically be changed)

@naoNao89 naoNao89 force-pushed the fix-yes-sigpipe-trap-7252 branch from 407b819 to 01ba47a Compare December 6, 2025 11:58
@github-actions
Copy link

github-actions bot commented Dec 6, 2025

GNU testsuite comparison:

Skip an intermittent issue tests/misc/tee (fails in this run but passes in the 'main' branch)

@naoNao89 naoNao89 force-pushed the fix-yes-sigpipe-trap-7252 branch from 01ba47a to 87c41c7 Compare December 7, 2025 02:58
@naoNao89 naoNao89 force-pushed the fix-yes-sigpipe-trap-7252 branch from 87c41c7 to 1ffaedd Compare December 7, 2025 03:11
@github-actions
Copy link

github-actions bot commented Dec 7, 2025

GNU testsuite comparison:

Skip an intermittent issue tests/misc/tee (fails in this run but passes in the 'main' branch)
Skip an intermittent issue tests/tail/overlay-headers (fails in this run but passes in the 'main' branch)

@collinfunk
Copy link

I still don't think this is correct. The error should not be printed in this case, and the exit status of yes should not be zero:

$ ./target/debug/yes  | head -n 1
y
yes: stdout: Broken pipe
$ echo ${PIPESTATUS[0]} ${PIPESTATUS[1]} 
0 0

Here is the correct behavior:

$ yes | head -n 1
y
$ echo ${PIPESTATUS[0]} ${PIPESTATUS[1]} 
141 0

This is an unfortunate limitation of Rust, that requires a feature to be stabilized [1].

It also appears that unless you use exec* in an unsafe block, Rust's library will create child processes that do not inherit the signal action for SIGPIPE from the parent [2].

[1] https://dev-doc.rust-lang.org/beta/unstable-book/language-features/unix-sigpipe.html#unix_sigpipe--sig_ign
[2] https://dev-doc.rust-lang.org/beta/unstable-book/language-features/unix-sigpipe.html#note-on-child-processes

@naoNao89
Copy link
Contributor Author

naoNao89 commented Dec 9, 2025

don't worry, i tested it on Rust nightly, and it's working. Hope for the next version

@oech3 oech3 mentioned this pull request Dec 16, 2025
@ChrisDryden
Copy link
Collaborator

This can be fixed with the approach in #9657

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.

yes: fails to trap SIGPIPE

4 participants