diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 4763755160e..e99b992aeba 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -39,6 +39,7 @@ uucore = { workspace = true, features = [ "parser-size", "version-cmp", "i18n-decimal", + "signals" ] } fluent = { workspace = true } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index ddbf225762c..805a6e989af 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1745,9 +1745,17 @@ fn emit_debug_warnings( } } +#[cfg(unix)] +uucore::init_sigpipe_capture!(); + #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { + #[cfg(unix)] + if !uucore::signals::sigpipe_was_ignored() { + let _ = uucore::signals::enable_pipe_errors(); + } + let mut settings = GlobalSettings::default(); let (processed_args, mut legacy_warnings) = preprocess_legacy_args(args); @@ -1771,7 +1779,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut files: Vec = if matches.contains_id(options::FILES0_FROM) { let files0_from: PathBuf = matches .get_one::(options::FILES0_FROM) - .map(|v| v.into()) + .map(std::convert::Into::into) .unwrap_or_default(); // Cannot combine FILES with FILES0_FROM @@ -1972,7 +1980,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { || matches!( matches .get_one::(options::check::CHECK) - .map(|s| s.as_str()), + .map(String::as_str), Some(options::check::SILENT | options::check::QUIET) ) { @@ -2663,7 +2671,7 @@ enum Month { fn month_parse(line: &[u8]) -> Month { let line = line.trim_ascii_start(); - match line.get(..3).map(|x| x.to_ascii_uppercase()).as_deref() { + match line.get(..3).map(<[u8]>::to_ascii_uppercase).as_deref() { Some(b"JAN") => Month::January, Some(b"FEB") => Month::February, Some(b"MAR") => Month::March, @@ -2694,7 +2702,7 @@ fn print_sorted<'a, T: Iterator>>( ) -> UResult<()> { let output_name = output .as_output_name() - .unwrap_or(OsStr::new("standard output")) + .unwrap_or_else(|| OsStr::new("standard output")) .to_owned(); let ctx = || translate!("sort-error-write-failed", "output" => output_name.maybe_quote()); diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index b478912dd19..a08dc637f64 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (words) ints (linux) NOFILE dfgi +// spell-checker:ignore (words) ints (linux) NOFILE dfgi PIPESTATUS #![allow(clippy::cast_possible_wrap)] use std::env; @@ -13,6 +13,7 @@ use std::time::Duration; use uutests::at_and_ucmd; use uutests::new_ucmd; use uutests::util::TestScenario; +use uutests::util_name; fn test_helper(file_name: &str, possible_args: &[&str]) { for args in possible_args { @@ -1218,6 +1219,48 @@ fn test_sigpipe_panic() { child.wait().unwrap().no_stderr(); } +// TODO: When SIGPIPE is trapped/ignored, GNU returns exit code 2 for IO failures, +// but uutils currently returns 1 in that mode. +#[test] +#[cfg(unix)] +fn test_broken_pipe_no_stderr_and_expected_status() { + use std::io::Write; + use std::process::{Command, Stdio}; + + #[cfg(not(target_os = "macos"))] + use std::os::unix::process::ExitStatusExt; + + let scene = TestScenario::new(util_name!()); + let bin = scene.bin_path.clone(); + + let mut child = Command::new(bin) + .arg("sort") + .arg("-n") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("spawn sort"); + + drop(child.stdout.take().expect("take stdout")); + + { + let mut stdin = child.stdin.take().expect("take stdin"); + for i in 1..=10000 { + writeln!(stdin, "{i}").expect("write stdin"); + } + } + + let output = child.wait_with_output().expect("wait"); + + assert!(output.stderr.is_empty()); + + // NOTE: On macOS CI this is flaky to assert because `sort` can complete + // and exit 0 before the closed-pipe condition is observed (timing/buffering). + #[cfg(not(target_os = "macos"))] + assert_eq!(output.status.signal(), Some(libc::SIGPIPE)); +} + #[test] fn test_conflict_check_out() { let cases = [