diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index ff53df7673a..eeec8902528 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -218,13 +218,6 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - // When we receive a SIGPIPE signal, we want to terminate the process so - // that we don't print any error messages to stderr. Rust ignores SIGPIPE - // (see https://github.com/rust-lang/rust/issues/62569), so we restore it's - // default action here. - #[cfg(not(target_os = "windows"))] - let _ = uucore::signals::enable_pipe_errors(); - let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let number_mode = if matches.get_flag(options::NUMBER_NONBLANK) { diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 45fdf6f3d1f..fc1adc537fc 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -5,9 +5,6 @@ // spell-checker:ignore fname, ftype, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable oconv canonicalized fadvise Fadvise FADV DONTNEED ESPIPE bufferedoutput, SETFL -#[cfg(unix)] -uucore::init_startup_state_capture!(); - mod blocks; mod bufferedoutput; mod conversion_tables; diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 40f32b7656a..a5dd8a8d748 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -1095,10 +1095,6 @@ fn list_signal_handling(log: &SignalActionLog) { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - // Rust ignores SIGPIPE (see https://github.com/rust-lang/rust/issues/62569). - // We restore its default action here. - #[cfg(unix)] - let _ = uucore::signals::enable_pipe_errors(); EnvAppData::default().run_env(args) } diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index b931cc8b11d..29373a511f8 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -92,22 +92,8 @@ fn select_precision( } } -// Initialize SIGPIPE state capture at process startup (Unix only) -#[cfg(unix)] -uucore::init_startup_state_capture!(); - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - // Restore SIGPIPE to default if it wasn't explicitly ignored by parent. - // The Rust runtime ignores SIGPIPE, but we need to respect the parent's - // signal disposition for proper pipeline behavior (GNU compatibility). - #[cfg(unix)] - if !signals::sigpipe_was_ignored() { - // Ignore the return value: if setting signal handler fails, we continue anyway. - // The worst case is we don't get proper SIGPIPE behavior, but seq will still work. - let _ = signals::enable_pipe_errors(); - } - let matches = uucore::clap_localization::handle_clap_result(uu_app(), split_short_args_with_value(args))?; diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 6f290a7d5e4..c40a6a07ef0 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -54,7 +54,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; match Settings::from(&matches, obs_lines.as_deref()) { - Ok(settings) => split(&settings), + Ok(settings) => { + // When using --filter, we write to a child process's stdin which may + // close early. Disable SIGPIPE so we get EPIPE errors instead of + // being terminated, allowing graceful handling of broken pipes. + #[cfg(unix)] + if settings.filter.is_some() { + let _ = uucore::signals::disable_pipe_errors(); + } + split(&settings) + } Err(e) if e.requires_usage() => Err(UUsageError::new(1, format!("{e}"))), Err(e) => Err(USimpleError::new(1, format!("{e}"))), } diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index e1686f459b4..cba911a7e65 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -4,8 +4,6 @@ // file that was distributed with this source code. // spell-checker:ignore (ToDO) sbytes slen dlen memmem memmap Mmap mmap SIGBUS -#[cfg(unix)] -uucore::init_startup_state_capture!(); mod error; diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index c1cfb333adf..7b82e956615 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -38,18 +38,8 @@ use uucore::translate; use uucore::{show, show_error}; -#[cfg(unix)] -uucore::init_startup_state_capture!(); - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - // When we receive a SIGPIPE signal, we want to terminate the process so - // that we don't print any error messages to stderr. Rust ignores SIGPIPE - // (see https://github.com/rust-lang/rust/issues/62569), so we restore it's - // default action here. - #[cfg(not(target_os = "windows"))] - let _ = uucore::signals::enable_pipe_errors(); - let settings = parse_args(args)?; settings.check_warnings(); diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index cf3d89c0a98..026f7fd9515 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -19,7 +19,7 @@ use uucore::{format_usage, show_error}; #[cfg(target_os = "linux")] use uucore::signals::ensure_stdout_not_broken; #[cfg(unix)] -use uucore::signals::{enable_pipe_errors, ignore_interrupts}; +use uucore::signals::{disable_pipe_errors, ignore_interrupts}; mod options { pub const APPEND: &str = "append"; @@ -163,8 +163,8 @@ fn tee(options: &Options) -> Result<()> { if options.ignore_interrupts { ignore_interrupts().map_err(|_| Error::from(ErrorKind::Other))?; } - if options.output_error.is_none() { - enable_pipe_errors().map_err(|_| Error::from(ErrorKind::Other))?; + if options.output_error.is_some() { + disable_pipe_errors().map_err(|_| Error::from(ErrorKind::Other))?; } } let mut writers: Vec = options diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 22c839c42a1..2a917ae79c9 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -4,8 +4,6 @@ // file that was distributed with this source code. // spell-checker:ignore (ToDO) tstr sigstr cmdname setpgid sigchld getpid -#[cfg(unix)] -uucore::init_startup_state_capture!(); mod status; @@ -22,9 +20,6 @@ use uucore::parser::parse_time; use uucore::process::ChildExt; use uucore::translate; -#[cfg(unix)] -use uucore::signals::enable_pipe_errors; - use uucore::{ format_usage, show_error, signals::{signal_by_name_or_value, signal_name_by_value}, @@ -334,8 +329,6 @@ fn timeout( if !foreground { let _ = setpgid(Pid::from_raw(0), Pid::from_raw(0)); } - #[cfg(unix)] - enable_pipe_errors()?; let mut command = process::Command::new(&cmd[0]); command diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 2b20d29ce86..3a0ee625371 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -31,13 +31,6 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - // When we receive a SIGPIPE signal, we want to terminate the process so - // that we don't print any error messages to stderr. Rust ignores SIGPIPE - // (see https://github.com/rust-lang/rust/issues/62569), so we restore it's - // default action here. - #[cfg(not(target_os = "windows"))] - let _ = uucore::signals::enable_pipe_errors(); - let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let delete_flag = matches.get_flag(options::DELETE); diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 1469948b888..5bf5199a073 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -19,6 +19,11 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { + // Disable SIGPIPE so we can handle broken pipe errors gracefully + // and exit with code 3 instead of being killed by the signal. + #[cfg(unix)] + let _ = uucore::signals::disable_pipe_errors(); + let matches = uucore::clap_localization::handle_clap_result_with_exit_code(uu_app(), args, 2)?; let silent = matches.get_flag(options::SILENT); diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index a5aaa18a867..98ee5550c85 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -11,8 +11,6 @@ use std::ffi::OsString; use std::io::{self, Write}; use uucore::error::{UResult, USimpleError}; use uucore::format_usage; -#[cfg(unix)] -use uucore::signals::enable_pipe_errors; use uucore::translate; // it's possible that using a smaller or larger buffer might provide better performance on some @@ -113,8 +111,6 @@ fn prepare_buffer(buf: &mut Vec) { pub fn exec(bytes: &[u8]) -> io::Result<()> { let stdout = io::stdout(); let mut stdout = stdout.lock(); - #[cfg(unix)] - enable_pipe_errors()?; loop { stdout.write_all(bytes)?; diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 1cbc276e4a7..06b192dfeb6 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -120,7 +120,7 @@ windows-sys = { workspace = true, optional = true, default-features = false, fea utmp-classic = { workspace = true, optional = true } [features] -default = [] +default = ["signals"] # * non-default features backup-control = [] colors = [] diff --git a/src/uucore/src/lib/features/signals.rs b/src/uucore/src/lib/features/signals.rs index 25b91d585d1..6d0956b39ca 100644 --- a/src/uucore/src/lib/features/signals.rs +++ b/src/uucore/src/lib/features/signals.rs @@ -410,7 +410,7 @@ pub fn signal_name_by_value(signal_value: usize) -> Option<&'static str> { ALL_SIGNALS.get(signal_value).copied() } -/// Returns the default signal value. +/// Restores SIGPIPE to default behavior (process terminates on broken pipe). #[cfg(unix)] pub fn enable_pipe_errors() -> Result<(), Errno> { // We pass the error as is, the return value would just be Ok(SigDfl), so we can safely ignore it. @@ -418,6 +418,15 @@ pub fn enable_pipe_errors() -> Result<(), Errno> { unsafe { signal(SIGPIPE, SigDfl) }.map(|_| ()) } +/// Ignores SIGPIPE signal (broken pipe errors are returned instead of terminating). +/// Use this to override the default SIGPIPE handling when you need to handle +/// broken pipe errors gracefully (e.g., tee with --output-error). +#[cfg(unix)] +pub fn disable_pipe_errors() -> Result<(), Errno> { + // SAFETY: this function is safe as long as we do not use a custom SigHandler -- we use the default one. + unsafe { signal(SIGPIPE, SigIgn) }.map(|_| ()) +} + /// Ignores the SIGINT signal. #[cfg(unix)] pub fn ignore_interrupts() -> Result<(), Errno> { diff --git a/src/uucore_procs/src/lib.rs b/src/uucore_procs/src/lib.rs index e60e2b822c7..c73f542a98b 100644 --- a/src/uucore_procs/src/lib.rs +++ b/src/uucore_procs/src/lib.rs @@ -16,14 +16,34 @@ use quote::quote; //* ref: [path construction from LitStr](https://oschwald.github.io/maxminddb-rust/syn/struct.LitStr.html) @@ /// A procedural macro to define the main function of a uutils binary. +/// +/// This macro handles: +/// - SIGPIPE state capture at process startup (before Rust runtime overrides it) +/// - SIGPIPE restoration to default if parent didn't explicitly ignore it +/// - Disabling Rust signal handlers for proper core dumps +/// - Error handling and exit code management #[proc_macro_attribute] pub fn main(_args: TokenStream, stream: TokenStream) -> TokenStream { let stream = proc_macro2::TokenStream::from(stream); let new = quote!( + // Initialize SIGPIPE state capture at process startup (Unix only). + // This must be at module level to set up the .init_array static that runs + // before main() to capture whether SIGPIPE was ignored by the parent process. + #[cfg(unix)] + uucore::init_startup_state_capture!(); + pub fn uumain(args: impl uucore::Args) -> i32 { #stream + // Restore SIGPIPE to default if it wasn't explicitly ignored by parent. + // The Rust runtime ignores SIGPIPE, but we need to respect the parent's + // signal disposition for proper pipeline behavior (GNU compatibility). + #[cfg(unix)] + if !uucore::signals::sigpipe_was_ignored() { + let _ = uucore::signals::enable_pipe_errors(); + } + // disable rust signal handlers (otherwise processes don't dump core after e.g. one SIGSEGV) #[cfg(unix)] uucore::disable_rust_signal_handlers().expect("Disabling rust signal handlers failed");