diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 74da35caccd..b1c5f9aac63 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -8,8 +8,10 @@ mod platform; use crate::platform::is_unsafe_overwrite; +use clap::{Arg, ArgAction, Command}; +use memchr::memchr2; use std::fs::{File, metadata}; -use std::io::{self, BufWriter, IsTerminal, Read, Write}; +use std::io::{self, BufWriter, ErrorKind, IsTerminal, Read, Write}; /// Unix domain socket support #[cfg(unix)] use std::net::Shutdown; @@ -19,12 +21,11 @@ use std::os::fd::AsFd; use std::os::unix::fs::FileTypeExt; #[cfg(unix)] use std::os::unix::net::UnixStream; - -use clap::{Arg, ArgAction, Command}; -use memchr::memchr2; use thiserror::Error; use uucore::display::Quotable; use uucore::error::UResult; +#[cfg(not(target_os = "windows"))] +use uucore::libc; use uucore::locale::get_message; use uucore::{fast_inc::fast_inc_one, format_usage}; @@ -220,6 +221,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 number_mode = if matches.get_flag(options::NUMBER_NONBLANK) { @@ -502,7 +512,9 @@ fn write_fast(handle: &mut InputHandle) -> CatResult<()> { if n == 0 { break; } - stdout_lock.write_all(&buf[..n])?; + stdout_lock + .write_all(&buf[..n]) + .inspect_err(handle_broken_pipe)?; } Err(e) => return Err(e.into()), } @@ -513,7 +525,7 @@ fn write_fast(handle: &mut InputHandle) -> CatResult<()> { // that will succeed, data pushed through splice will be output before // the data buffered in stdout.lock. Therefore additional explicit flush // is required here. - stdout_lock.flush()?; + stdout_lock.flush().inspect_err(handle_broken_pipe)?; Ok(()) } @@ -584,7 +596,7 @@ fn write_lines( // and not be buffered internally to the `cat` process. // Hence it's necessary to flush our buffer before every time we could potentially block // on a `std::io::Read::read` call. - writer.flush()?; + writer.flush().inspect_err(handle_broken_pipe)?; } Ok(()) @@ -704,11 +716,18 @@ fn write_end_of_line( ) -> CatResult<()> { writer.write_all(end_of_line)?; if is_interactive { - writer.flush()?; + writer.flush().inspect_err(handle_broken_pipe)?; } Ok(()) } +fn handle_broken_pipe(error: &io::Error) { + // SIGPIPE is not available on Windows. + if cfg!(target_os = "windows") && error.kind() == ErrorKind::BrokenPipe { + std::process::exit(13); + } +} + #[cfg(test)] mod tests { use std::io::{BufWriter, stdout}; diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 451d7f8f7f2..c3e25c6d010 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -119,6 +119,20 @@ fn test_closes_file_descriptors() { .succeeds(); } +#[test] +#[cfg(unix)] +fn test_broken_pipe() { + let mut cmd = new_ucmd!(); + let mut child = cmd + .set_stdin(Stdio::from(File::open("/dev/zero").unwrap())) + .set_stdout(Stdio::piped()) + .run_no_wait(); + // Dropping the stdout should not lead to an error. + // The "Broken pipe" error should be silently ignored. + child.close_stdout(); + child.wait().unwrap().fails_silently(); +} + #[test] #[cfg(unix)] fn test_piped_to_regular_file() { diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index ec4f46401d2..1e40a279146 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -4898,10 +4898,11 @@ fn test_when_piped_input_then_no_broken_pipe() { } #[test] +#[cfg(unix)] fn test_when_output_closed_then_no_broken_pie() { let mut cmd = new_ucmd!(); let mut child = cmd - .args(&[FOOBAR_TXT]) + .args(&["-c", "100000", "/dev/zero"]) .set_stdout(Stdio::piped()) .run_no_wait(); // Dropping the stdout should not lead to an error.