diff --git a/Cargo.lock b/Cargo.lock index 30192f4cb6d..04a6f7e2fa7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3822,6 +3822,7 @@ dependencies = [ "clap", "codspeed-divan-compat", "fluent", + "nix", "num-bigint", "num-traits", "tempfile", diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index fd3fdab4df7..d668a417839 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -1635,6 +1635,7 @@ dependencies = [ "bigdecimal", "clap", "fluent", + "nix", "num-bigint", "num-traits", "thiserror", diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index 534b675e126..1d458464a81 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -21,6 +21,7 @@ path = "src/seq.rs" [dependencies] bigdecimal = { workspace = true } clap = { workspace = true } +nix = { workspace = true } num-bigint = { workspace = true } num-traits = { workspace = true } thiserror = { workspace = true } diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 6d8f0025808..5cd6353a689 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -5,6 +5,8 @@ // spell-checker:ignore (ToDO) bigdecimal extendedbigdecimal numberparse hexadecimalfloat biguint use std::ffi::{OsStr, OsString}; use std::io::{BufWriter, Write, stdout}; +#[cfg(unix)] +use std::sync::atomic::{AtomicBool, Ordering}; use clap::{Arg, ArgAction, Command}; use num_bigint::BigUint; @@ -225,9 +227,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { padding, ); - match result { - Ok(()) => Ok(()), - Err(err) if err.kind() == std::io::ErrorKind::BrokenPipe => { + let sigpipe_ignored = sigpipe_is_ignored(); + if let Err(err) = result { + if err.kind() == std::io::ErrorKind::BrokenPipe { // GNU seq prints the Broken pipe message but still exits with status 0 // unless SIGPIPE was explicitly ignored, in which case it should fail. let err = err.map_err_context(|| "write error".into()); @@ -238,8 +240,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } Ok(()) } - Err(err) => Err(err.map_err_context(|| "write error".into())), + return Err(err.map_err_context(|| "write error".into())); } + Ok(()) } pub fn uu_app() -> Command { @@ -353,6 +356,44 @@ fn fast_print_seq( Ok(()) } +#[cfg(unix)] +static SIGPIPE_WAS_IGNORED: AtomicBool = AtomicBool::new(false); + +#[cfg(unix)] +/// # Safety +/// This function runs once at process initialization and only observes the +/// current `SIGPIPE` handler, so there are no extra safety requirements for callers. +unsafe extern "C" fn capture_sigpipe_state() { + use nix::libc; + use std::{mem::MaybeUninit, ptr}; + + let mut current = MaybeUninit::::uninit(); + if unsafe { libc::sigaction(libc::SIGPIPE, ptr::null(), current.as_mut_ptr()) } == 0 { + let ignored = unsafe { current.assume_init() }.sa_sigaction == libc::SIG_IGN; + SIGPIPE_WAS_IGNORED.store(ignored, Ordering::Relaxed); + } +} + +#[cfg(all(unix, not(target_os = "macos")))] +#[used] +#[unsafe(link_section = ".init_array")] +static CAPTURE_SIGPIPE_STATE: unsafe extern "C" fn() = capture_sigpipe_state; + +#[cfg(all(unix, target_os = "macos"))] +#[used] +#[unsafe(link_section = "__DATA,__mod_init_func")] +static CAPTURE_SIGPIPE_STATE_APPLE: unsafe extern "C" fn() = capture_sigpipe_state; + +#[cfg(unix)] +fn sigpipe_is_ignored() -> bool { + SIGPIPE_WAS_IGNORED.load(Ordering::Relaxed) +} + +#[cfg(not(unix))] +const fn sigpipe_is_ignored() -> bool { + false +} + fn done_printing(next: &T, increment: &T, last: &T) -> bool { if increment >= &T::zero() { next > last