Skip to content

Commit 50704da

Browse files
authored
Merge pull request #8336 from tgrez/main
cat: handle broken pipe gracefully
2 parents 4d08b9d + 11d6fe0 commit 50704da

File tree

3 files changed

+43
-9
lines changed

3 files changed

+43
-9
lines changed

src/uu/cat/src/cat.rs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
mod platform;
99

1010
use crate::platform::is_unsafe_overwrite;
11+
use clap::{Arg, ArgAction, Command};
12+
use memchr::memchr2;
1113
use std::fs::{File, metadata};
12-
use std::io::{self, BufWriter, IsTerminal, Read, Write};
14+
use std::io::{self, BufWriter, ErrorKind, IsTerminal, Read, Write};
1315
/// Unix domain socket support
1416
#[cfg(unix)]
1517
use std::net::Shutdown;
@@ -19,12 +21,11 @@ use std::os::fd::AsFd;
1921
use std::os::unix::fs::FileTypeExt;
2022
#[cfg(unix)]
2123
use std::os::unix::net::UnixStream;
22-
23-
use clap::{Arg, ArgAction, Command};
24-
use memchr::memchr2;
2524
use thiserror::Error;
2625
use uucore::display::Quotable;
2726
use uucore::error::UResult;
27+
#[cfg(not(target_os = "windows"))]
28+
use uucore::libc;
2829
use uucore::locale::get_message;
2930
use uucore::{fast_inc::fast_inc_one, format_usage};
3031

@@ -220,6 +221,15 @@ mod options {
220221

221222
#[uucore::main]
222223
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
224+
// When we receive a SIGPIPE signal, we want to terminate the process so
225+
// that we don't print any error messages to stderr. Rust ignores SIGPIPE
226+
// (see https://github.com/rust-lang/rust/issues/62569), so we restore it's
227+
// default action here.
228+
#[cfg(not(target_os = "windows"))]
229+
unsafe {
230+
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
231+
}
232+
223233
let matches = uu_app().try_get_matches_from(args)?;
224234

225235
let number_mode = if matches.get_flag(options::NUMBER_NONBLANK) {
@@ -502,7 +512,9 @@ fn write_fast<R: FdReadable>(handle: &mut InputHandle<R>) -> CatResult<()> {
502512
if n == 0 {
503513
break;
504514
}
505-
stdout_lock.write_all(&buf[..n])?;
515+
stdout_lock
516+
.write_all(&buf[..n])
517+
.inspect_err(handle_broken_pipe)?;
506518
}
507519
Err(e) => return Err(e.into()),
508520
}
@@ -513,7 +525,7 @@ fn write_fast<R: FdReadable>(handle: &mut InputHandle<R>) -> CatResult<()> {
513525
// that will succeed, data pushed through splice will be output before
514526
// the data buffered in stdout.lock. Therefore additional explicit flush
515527
// is required here.
516-
stdout_lock.flush()?;
528+
stdout_lock.flush().inspect_err(handle_broken_pipe)?;
517529
Ok(())
518530
}
519531

@@ -584,7 +596,7 @@ fn write_lines<R: FdReadable>(
584596
// and not be buffered internally to the `cat` process.
585597
// Hence it's necessary to flush our buffer before every time we could potentially block
586598
// on a `std::io::Read::read` call.
587-
writer.flush()?;
599+
writer.flush().inspect_err(handle_broken_pipe)?;
588600
}
589601

590602
Ok(())
@@ -704,11 +716,18 @@ fn write_end_of_line<W: Write>(
704716
) -> CatResult<()> {
705717
writer.write_all(end_of_line)?;
706718
if is_interactive {
707-
writer.flush()?;
719+
writer.flush().inspect_err(handle_broken_pipe)?;
708720
}
709721
Ok(())
710722
}
711723

724+
fn handle_broken_pipe(error: &io::Error) {
725+
// SIGPIPE is not available on Windows.
726+
if cfg!(target_os = "windows") && error.kind() == ErrorKind::BrokenPipe {
727+
std::process::exit(13);
728+
}
729+
}
730+
712731
#[cfg(test)]
713732
mod tests {
714733
use std::io::{BufWriter, stdout};

tests/by-util/test_cat.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,20 @@ fn test_closes_file_descriptors() {
119119
.succeeds();
120120
}
121121

122+
#[test]
123+
#[cfg(unix)]
124+
fn test_broken_pipe() {
125+
let mut cmd = new_ucmd!();
126+
let mut child = cmd
127+
.set_stdin(Stdio::from(File::open("/dev/zero").unwrap()))
128+
.set_stdout(Stdio::piped())
129+
.run_no_wait();
130+
// Dropping the stdout should not lead to an error.
131+
// The "Broken pipe" error should be silently ignored.
132+
child.close_stdout();
133+
child.wait().unwrap().fails_silently();
134+
}
135+
122136
#[test]
123137
#[cfg(unix)]
124138
fn test_piped_to_regular_file() {

tests/by-util/test_tail.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4898,10 +4898,11 @@ fn test_when_piped_input_then_no_broken_pipe() {
48984898
}
48994899

49004900
#[test]
4901+
#[cfg(unix)]
49014902
fn test_when_output_closed_then_no_broken_pie() {
49024903
let mut cmd = new_ucmd!();
49034904
let mut child = cmd
4904-
.args(&[FOOBAR_TXT])
4905+
.args(&["-c", "100000", "/dev/zero"])
49054906
.set_stdout(Stdio::piped())
49064907
.run_no_wait();
49074908
// Dropping the stdout should not lead to an error.

0 commit comments

Comments
 (0)