diff --git a/src/uu/tr/src/operation.rs b/src/uu/tr/src/operation.rs index 295bc4ffe..55ec9422c 100644 --- a/src/uu/tr/src/operation.rs +++ b/src/uu/tr/src/operation.rs @@ -708,12 +708,16 @@ where let filtered = buf.iter().filter_map(|&c| translator.translate(c)); output_buf.extend(filtered); + #[cfg(not(target_os = "windows"))] + output + .write_all(&output_buf) + .map_err_context(|| get_message("tr-error-write-error"))?; + + // SIGPIPE is not available on Windows. + #[cfg(target_os = "windows")] if let Err(err) = output.write_all(&output_buf) { - // Treat broken pipe as a successful termination, which is the - // expected behavior when stdout is a pipeline and the downstream - // process terminates. if err.kind() == std::io::ErrorKind::BrokenPipe { - break; + std::process::exit(13); } else { return Err(err.map_err_context(|| get_message("tr-error-write-error"))); } diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index b446fa3a9..c2e909109 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -13,10 +13,12 @@ use operation::{ }; use std::collections::HashMap; use std::ffi::OsString; -use std::io::{BufWriter, ErrorKind, Write, stdin, stdout}; +use std::io::{BufWriter, Write, stdin, stdout}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::fs::is_stdin_directory; +#[cfg(not(target_os = "windows"))] +use uucore::libc; use uucore::{format_usage, os_str_as_bytes, show}; use uucore::locale::{get_message, get_message_with_args}; @@ -31,6 +33,15 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { + // When we receive a SIGPIPE signal, we want to terminate the process so + // that we don't print any error messages to stderr. Rust ignores SIGPIPE + // (see https://github.com/rust-lang/rust/issues/62569), so we restore it's + // default action here. + #[cfg(not(target_os = "windows"))] + unsafe { + libc::signal(libc::SIGPIPE, libc::SIG_DFL); + } + let matches = uu_app().try_get_matches_from(args)?; let delete_flag = matches.get_flag(options::DELETE); @@ -157,10 +168,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { translate_input(&mut locked_stdin, &mut buffered_stdout, op)?; } - // Handle broken pipe errors gracefully during flush. + #[cfg(not(target_os = "windows"))] + buffered_stdout + .flush() + .map_err_context(|| get_message("tr-error-write-error"))?; + + // SIGPIPE is not available on Windows. + #[cfg(target_os = "windows")] match buffered_stdout.flush() { Ok(()) => {} - Err(err) if err.kind() == ErrorKind::BrokenPipe => {} + Err(err) if err.kind() == std::io::ErrorKind::BrokenPipe => std::process::exit(13), Err(err) => return Err(err.map_err_context(|| get_message("tr-error-write-error"))), } diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index e76fd84d7..0121f87aa 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -1561,6 +1561,5 @@ fn test_broken_pipe_no_error() { .args(&["e", "a"]) .pipe_in("hello".repeat(100)) .run_stdout_starts_with(b"") - .success() - .stderr_is(""); + .fails_silently(); }