yes: print error when SIGPIPE is trapped

Fixes #7252
This commit is contained in:
naoNao89 2025-12-06 11:26:33 +07:00
parent b4423b9691
commit 1ffaedd170
4 changed files with 22 additions and 9 deletions

View file

@ -3,4 +3,5 @@ yes-usage = yes [STRING]...
# Error messages
yes-error-standard-output = standard output: { $error }
yes-error-stdout-broken-pipe = yes: stdout: Broken pipe
yes-error-invalid-utf8 = arguments contain invalid UTF-8

View file

@ -3,4 +3,5 @@ yes-usage = yes [CHAÎNE]...
# Messages d'erreur
yes-error-standard-output = sortie standard : { $error }
yes-error-stdout-broken-pipe = yes: stdout: Tube cassé
yes-error-invalid-utf8 = les arguments contiennent de l'UTF-8 invalide

View file

@ -11,8 +11,6 @@ use std::ffi::OsString;
use std::io::{self, Write};
use uucore::error::{UResult, USimpleError};
use uucore::format_usage;
#[cfg(unix)]
use uucore::signals::enable_pipe_errors;
use uucore::translate;
// it's possible that using a smaller or larger buffer might provide better performance on some
@ -29,7 +27,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
match exec(&buffer) {
Ok(()) => Ok(()),
Err(err) if err.kind() == io::ErrorKind::BrokenPipe => Ok(()),
Err(err) if err.kind() == io::ErrorKind::BrokenPipe => {
// When SIGPIPE is trapped (SIG_IGN), write operations return EPIPE.
// GNU coreutils prints an error message in this case.
eprintln!("{}", translate!("yes-error-stdout-broken-pipe"));
Ok(())
}
Err(err) => Err(USimpleError::new(
1,
translate!("yes-error-standard-output", "error" => err),
@ -113,8 +116,16 @@ fn prepare_buffer(buf: &mut Vec<u8>) {
pub fn exec(bytes: &[u8]) -> io::Result<()> {
let stdout = io::stdout();
let mut stdout = stdout.lock();
#[cfg(unix)]
enable_pipe_errors()?;
// SIGPIPE handling:
// Rust ignores SIGPIPE by default (rust-lang/rust#62569). When write() fails
// because the pipe is closed, it returns EPIPE error instead of being killed by
// the signal. We catch this error in uumain() and print a diagnostic message,
// matching GNU coreutils behavior.
//
// Key point: We do NOT restore SIGPIPE to SIG_DFL. This preserves POSIX signal
// inheritance semantics - if the parent set SIGPIPE to SIG_IGN (e.g., `trap '' PIPE`),
// we respect that setting and rely on EPIPE error handling.
loop {
stdout.write_all(bytes)?;

View file

@ -5,14 +5,14 @@
use std::ffi::OsStr;
use std::process::ExitStatus;
#[cfg(unix)]
use std::os::unix::process::ExitStatusExt;
use uutests::new_ucmd;
#[cfg(unix)]
fn check_termination(result: ExitStatus) {
assert_eq!(result.signal(), Some(libc::SIGPIPE));
// yes should exit successfully (code 0) when the pipe breaks.
// Rust ignores SIGPIPE by default, so write() returns EPIPE error,
// which is caught and handled gracefully. This matches GNU coreutils behavior.
assert!(result.success(), "yes should exit successfully");
}
#[cfg(not(unix))]