diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index b29e7ea2337..4a771a1738b 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -20,18 +20,18 @@ const VERSION: &str = env!("CARGO_PKG_VERSION"); include!(concat!(env!("OUT_DIR"), "/uutils_map.rs")); fn usage(utils: &UtilityMap, name: &str) { - println!("{name} {VERSION} (multi-call binary)\n"); - println!("Usage: {name} [function [arguments...]]"); - println!(" {name} --list\n"); - println!("Options:"); - println!(" --list lists all defined functions, one per row\n"); - println!("Currently defined functions:\n"); + eprintln!("{name} {VERSION} (multi-call binary)\n"); + eprintln!("Usage: {name} [function [arguments...]]"); + eprintln!(" {name} --list\n"); + eprintln!("Options:"); + eprintln!(" --list lists all defined functions, one per row\n"); + eprintln!("Currently defined functions:\n"); #[allow(clippy::map_clone)] let mut utils: Vec<&str> = utils.keys().map(|&s| s).collect(); utils.sort_unstable(); let display_list = utils.join(", "); let width = cmp::min(textwrap::termwidth(), 100) - 4 * 2; // (opinion/heuristic) max 100 chars wide with 4 character side indentions - println!( + eprintln!( "{}", textwrap::indent(&textwrap::fill(&display_list, width), " ") ); diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index c0a41270f34..bbec5d42d16 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -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 { + if err_io.kind() == io::ErrorKind::BrokenPipe { + continue; + } + } + #[cfg(any(target_os = "linux", target_os = "android"))] + if let CatError::Nix(ref err_nix) = err { + // spell-checker:disable-next-line + if *err_nix == nix::errno::Errno::EPIPE { + continue; + } + } error_messages.push(format!("{}: {err}", path.maybe_quote())); } } diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 926befe72ff..750316adeab 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -716,3 +716,86 @@ fn test_child_when_pipe_in() { ts.ucmd().pipe_in("content").run().stdout_is("content"); } + +#[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::(&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 run_cat(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_silent_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_short(result: &CmdResult, contents: &str) { + let compare = result.stdout_str(); + assert!( + compare.len() < contents.len(), + "Too many bytes ({}) written to stdout (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 mut ucmd = new_ucmd!(); + + let proc = ucmd.set_stdout(make_broken_pipe()); + + let (content, output) = run_cat(proc); + + expect_silent_success(&output); + expect_short(&output, &content); + } +} diff --git a/tests/test_util_name.rs b/tests/test_util_name.rs index 7a8a076e893..79d000443fc 100644 --- a/tests/test_util_name.rs +++ b/tests/test_util_name.rs @@ -120,8 +120,8 @@ fn util_invalid_name_help() { .unwrap(); let output = child.wait_with_output().unwrap(); assert_eq!(output.status.code(), Some(0)); - assert_eq!(output.stderr, b""); - let output_str = String::from_utf8(output.stdout).unwrap(); + assert_eq!(output.stdout, b""); + let output_str = String::from_utf8(output.stderr).unwrap(); assert!(output_str.contains("(multi-call binary)"), "{output_str:?}"); assert!( output_str.contains("Usage: invalid_name [function "), @@ -159,8 +159,8 @@ fn util_non_utf8_name_help() { .unwrap(); let output = child.wait_with_output().unwrap(); assert_eq!(output.status.code(), Some(0)); - assert_eq!(output.stderr, b""); - let output_str = String::from_utf8(output.stdout).unwrap(); + assert_eq!(output.stdout, b""); + let output_str = String::from_utf8(output.stderr).unwrap(); assert!(output_str.contains("(multi-call binary)"), "{output_str:?}"); assert!( output_str.contains("Usage: [function "),