diff --git a/src/uu/yes/locales/en-US.ftl b/src/uu/yes/locales/en-US.ftl index 9daaaa820b0..7bd3e6acf37 100644 --- a/src/uu/yes/locales/en-US.ftl +++ b/src/uu/yes/locales/en-US.ftl @@ -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 diff --git a/src/uu/yes/locales/fr-FR.ftl b/src/uu/yes/locales/fr-FR.ftl index c3272b80903..5f9142682c3 100644 --- a/src/uu/yes/locales/fr-FR.ftl +++ b/src/uu/yes/locales/fr-FR.ftl @@ -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 diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index a5aaa18a867..508659c5c6a 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -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) { 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)?; diff --git a/tests/by-util/test_yes.rs b/tests/by-util/test_yes.rs index db460c998fc..2aa9bb6603c 100644 --- a/tests/by-util/test_yes.rs +++ b/tests/by-util/test_yes.rs @@ -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))]