Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/uu/yes/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions src/uu/yes/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
21 changes: 16 additions & 5 deletions src/uu/yes/src/yes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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),
Expand Down Expand Up @@ -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)?;
Expand Down
8 changes: 4 additions & 4 deletions tests/by-util/test_yes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand Down
Loading