tr: handle broken pipe gracefully

This commit is contained in:
yuankunzhang 2025-07-02 22:26:11 +08:00
parent d04d0cd987
commit 31a5b54dc6
3 changed files with 27 additions and 7 deletions

View file

@ -708,9 +708,16 @@ where
let filtered = buf.iter().filter_map(|&c| translator.translate(c));
output_buf.extend(filtered);
output
.write_all(&output_buf)
.map_err_context(|| get_message("tr-error-write-error"))?;
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;
} else {
return Err(err.map_err_context(|| get_message("tr-error-write-error")));
}
}
buf.clear();
output_buf.clear();

View file

@ -13,7 +13,7 @@ use operation::{
};
use std::collections::HashMap;
use std::ffi::OsString;
use std::io::{BufWriter, Write, stdin, stdout};
use std::io::{BufWriter, ErrorKind, Write, stdin, stdout};
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
use uucore::fs::is_stdin_directory;
@ -157,9 +157,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
translate_input(&mut locked_stdin, &mut buffered_stdout, op)?;
}
buffered_stdout
.flush()
.map_err_context(|| get_message("tr-error-write-error"))?;
// Handle broken pipe errors gracefully during flush.
match buffered_stdout.flush() {
Ok(()) => {}
Err(err) if err.kind() == ErrorKind::BrokenPipe => {}
Err(err) => return Err(err.map_err_context(|| get_message("tr-error-write-error"))),
}
Ok(())
}

View file

@ -1554,3 +1554,13 @@ fn test_failed_write_is_reported() {
.fails()
.stderr_is("tr: write error: No space left on device\n");
}
#[test]
fn test_broken_pipe_no_error() {
new_ucmd!()
.args(&["e", "a"])
.pipe_in("hello".repeat(100))
.run_stdout_starts_with(b"")
.success()
.stderr_is("");
}