-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
cat: Suppress Broken Pipe errors. #7921
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
GNU testsuite comparison:
|
@@ -450,6 +450,18 @@ fn cat_files(files: &[String], options: &OutputOptions) -> UResult<()> { | ||
|
||
for path in files { | ||
if let Err(err) = cat_path(path, options, &mut state, out_info.as_ref()) { | ||
if let CatError::Io(ref err_io) = err { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add comments to explain what you are doing
#[cfg(target_os = "linux")] | ||
mod linux_only { | ||
use uutests::util::{CmdResult, TestScenario, UCommand}; | ||
|
||
use std::fmt::Write; | ||
use std::fs::File; | ||
use std::process::Stdio; | ||
use uutests::new_ucmd; | ||
use uutests::util_name; | ||
|
||
fn make_broken_pipe() -> File { | ||
use libc::c_int; | ||
use std::os::unix::io::FromRawFd; | ||
|
||
let mut fds: [c_int; 2] = [0, 0]; | ||
assert!( | ||
(unsafe { libc::pipe(std::ptr::from_mut::<c_int>(&mut fds[0])) } == 0), | ||
"Failed to create pipe" | ||
); | ||
|
||
// Drop the read end of the pipe | ||
let _ = unsafe { File::from_raw_fd(fds[0]) }; | ||
|
||
// Make the write end of the pipe into a Rust File | ||
unsafe { File::from_raw_fd(fds[1]) } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sylvestre I stole this from the tee test code:
coreutils/tests/by-util/test_tee.rs
Lines 163 to 298 in 402907e
#[cfg(target_os = "linux")] | |
mod linux_only { | |
use uutests::util::{AtPath, CmdResult, TestScenario, UCommand}; | |
use std::fmt::Write; | |
use std::fs::File; | |
use std::process::Stdio; | |
use std::time::Duration; | |
use uutests::at_and_ucmd; | |
use uutests::new_ucmd; | |
use uutests::util_name; | |
fn make_broken_pipe() -> File { | |
use libc::c_int; | |
use std::os::unix::io::FromRawFd; | |
let mut fds: [c_int; 2] = [0, 0]; | |
assert!( | |
(unsafe { libc::pipe(std::ptr::from_mut::<c_int>(&mut fds[0])) } == 0), | |
"Failed to create pipe" | |
); | |
// Drop the read end of the pipe | |
let _ = unsafe { File::from_raw_fd(fds[0]) }; | |
// Make the write end of the pipe into a Rust File | |
unsafe { File::from_raw_fd(fds[1]) } | |
} | |
fn make_hanging_read() -> File { | |
use libc::c_int; | |
use std::os::unix::io::FromRawFd; | |
let mut fds: [c_int; 2] = [0, 0]; | |
assert!( | |
(unsafe { libc::pipe(std::ptr::from_mut::<c_int>(&mut fds[0])) } == 0), | |
"Failed to create pipe" | |
); | |
// PURPOSELY leak the write end of the pipe, so the read end hangs. | |
// Return the read end of the pipe | |
unsafe { File::from_raw_fd(fds[0]) } | |
} | |
fn run_tee(proc: &mut UCommand) -> (String, CmdResult) { | |
let content = (1..=100_000).fold(String::new(), |mut output, x| { | |
let _ = writeln!(output, "{x}"); | |
output | |
}); | |
let result = proc | |
.ignore_stdin_write_error() | |
.set_stdin(Stdio::piped()) | |
.run_no_wait() | |
.pipe_in_and_wait(content.as_bytes()); | |
(content, result) | |
} | |
fn expect_success(result: &CmdResult) { | |
assert!( | |
result.succeeded(), | |
"Command was expected to succeed.\nstdout = {}\n stderr = {}", | |
std::str::from_utf8(result.stdout()).unwrap(), | |
std::str::from_utf8(result.stderr()).unwrap(), | |
); | |
assert!( | |
result.stderr_str().is_empty(), | |
"Unexpected data on stderr.\n stderr = {}", | |
std::str::from_utf8(result.stderr()).unwrap(), | |
); | |
} | |
fn expect_failure(result: &CmdResult, message: &str) { | |
assert!( | |
!result.succeeded(), | |
"Command was expected to fail.\nstdout = {}\n stderr = {}", | |
std::str::from_utf8(result.stdout()).unwrap(), | |
std::str::from_utf8(result.stderr()).unwrap(), | |
); | |
assert!( | |
result.stderr_str().contains(message), | |
"Expected to see error message fragment {message} in stderr, but did not.\n stderr = {}", | |
std::str::from_utf8(result.stderr()).unwrap(), | |
); | |
} | |
fn expect_silent_failure(result: &CmdResult) { | |
assert!( | |
!result.succeeded(), | |
"Command was expected to fail.\nstdout = {}\n stderr = {}", | |
std::str::from_utf8(result.stdout()).unwrap(), | |
std::str::from_utf8(result.stderr()).unwrap(), | |
); | |
assert!( | |
result.stderr_str().is_empty(), | |
"Unexpected data on stderr.\n stderr = {}", | |
std::str::from_utf8(result.stderr()).unwrap(), | |
); | |
} | |
fn expect_correct(name: &str, at: &AtPath, contents: &str) { | |
assert!(at.file_exists(name)); | |
let compare = at.read(name); | |
assert_eq!(compare, contents); | |
} | |
fn expect_short(name: &str, at: &AtPath, contents: &str) { | |
assert!(at.file_exists(name)); | |
let compare = at.read(name); | |
assert!( | |
compare.len() < contents.len(), | |
"Too many bytes ({}) written to {name} (should be a short count from {})", | |
compare.len(), | |
contents.len() | |
); | |
assert!( | |
contents.starts_with(&compare), | |
"Expected truncated output to be a prefix of the correct output, but it isn't.\n Correct: {contents}\n Compare: {compare}" | |
); | |
} | |
#[test] | |
fn test_pipe_error_default() { | |
let (at, mut ucmd) = at_and_ucmd!(); | |
let file_out_a = "tee_file_out_a"; | |
let proc = ucmd.arg(file_out_a).set_stdout(make_broken_pipe()); | |
let (content, output) = run_tee(proc); | |
expect_silent_failure(&output); | |
expect_short(file_out_a, &at, content.as_str()); | |
} |
Is that OK, or would you prefer we find some way of hosting these in a shareable location, if the latter, any advice on preferred approach would be appreciated.
At present, the `cat` command unexpectedly prints an error message when it receives a broken pipe error. As an example, there are many workflows that make use of `cat` and `head` together to process only part of the data. The `head` command will stop reading after a configured number of bytes or lines, subsequently exposing `cat` to a broken pipe condition. Said workflows may fail when they unexpectedly get error messages in their output. Suppress broken pipe errors. Signed-off-by: Frode Nordahl <fnordahl@ubuntu.com>
46e7b9e
to
f2508fd
Compare
GNU testsuite comparison:
|
The Android / Test builds (ubuntu-latest, 4, 4096, 28, google_apis_playstore, x check appears to have failed intermittently during initialization and might be unrelated to the change at hand? |
@fnordahl yes, it looks unrelated. Anyway, I restarted it. |
At present, the
cat
command unexpectedly prints an error message when it receives a broken pipe error.As an example, there are many workflows that make use of
cat
andhead
together to process only part of the data. Thehead
command will stop reading after a configured number of bytes or lines, subsequently exposingcat
to a broken pipe condition.Said workflows may fail when they unexpectedly get error messages in their output.
Suppress broken pipe errors.