mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
uucore: centralize SIGPIPE handling in main macro (#10354)
Instead of adding this code to every utility, hoping to make it centralized and then addressing all of the special cases in the individual utilities. There's definitely a chance that a special case is missed and could be a regression, but this should solve a much larger number of issues for all utilities. Fixes https://github.com/uutils/coreutils/issues/10325 Fixes https://github.com/uutils/coreutils/issues/10260 Fixes https://github.com/uutils/coreutils/issues/10230 Fixes https://github.com/uutils/coreutils/issues/10214 Fixes https://github.com/uutils/coreutils/issues/9936 Fixes https://github.com/uutils/coreutils/issues/8919 Fixes https://github.com/uutils/coreutils/issues/7252 Fixes https://github.com/uutils/coreutils/issues/4965
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Vendored
-4
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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))?;
|
||||
|
||||
|
||||
@@ -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}"))),
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<NamedWriter> = options
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<u8>) {
|
||||
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)?;
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -16,14 +16,34 @@ use quote::quote;
|
||||
//* ref: [path construction from LitStr](https://oschwald.github.io/maxminddb-rust/syn/struct.LitStr.html) @@ <http://archive.is/8YDua>
|
||||
|
||||
/// 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");
|
||||
|
||||
Reference in New Issue
Block a user