1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-30 04:27:45 +00:00

Merge pull request #4588 from anastygnome/fork

Refactor signal handling in yes, tee, and timeout
This commit is contained in:
Terts Diepraam 2023-03-26 17:16:50 +02:00 committed by GitHub
commit 9991ab28d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 56 additions and 86 deletions

1
Cargo.lock generated
View file

@ -3272,7 +3272,6 @@ name = "uu_yes"
version = "0.0.17" version = "0.0.17"
dependencies = [ dependencies = [
"clap", "clap",
"libc",
"nix", "nix",
"uucore", "uucore",
] ]

View file

@ -16,7 +16,7 @@ use uucore::{format_usage, help_about, help_section, help_usage, show_error};
// spell-checker:ignore nopipe // spell-checker:ignore nopipe
#[cfg(unix)] #[cfg(unix)]
use uucore::libc; use uucore::signals::{enable_pipe_errors, ignore_interrupts};
const ABOUT: &str = help_about!("tee.md"); const ABOUT: &str = help_about!("tee.md");
const USAGE: &str = help_usage!("tee.md"); const USAGE: &str = help_usage!("tee.md");
@ -135,44 +135,19 @@ pub fn uu_app() -> Command {
) )
} }
#[cfg(unix)]
fn ignore_interrupts() -> Result<()> {
let ret = unsafe { libc::signal(libc::SIGINT, libc::SIG_IGN) };
if ret == libc::SIG_ERR {
return Err(Error::new(ErrorKind::Other, ""));
}
Ok(())
}
#[cfg(not(unix))]
fn ignore_interrupts() -> Result<()> {
// Do nothing.
Ok(())
}
#[cfg(unix)]
fn enable_pipe_errors() -> Result<()> {
let ret = unsafe { libc::signal(libc::SIGPIPE, libc::SIG_DFL) };
if ret == libc::SIG_ERR {
return Err(Error::new(ErrorKind::Other, ""));
}
Ok(())
}
#[cfg(not(unix))]
fn enable_pipe_errors() -> Result<()> {
// Do nothing.
Ok(())
}
fn tee(options: &Options) -> Result<()> { fn tee(options: &Options) -> Result<()> {
if options.ignore_interrupts { #[cfg(unix)]
ignore_interrupts()?; {
} // ErrorKind::Other is raised by MultiWriter when all writers have exited.
if options.output_error.is_none() { // This is therefore just a clever way to stop all writers
enable_pipe_errors()?;
}
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))?;
}
}
let mut writers: Vec<NamedWriter> = options let mut writers: Vec<NamedWriter> = options
.files .files
.clone() .clone()

View file

@ -17,8 +17,14 @@ use std::time::Duration;
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::{UClapError, UResult, USimpleError, UUsageError}; use uucore::error::{UClapError, UResult, USimpleError, UUsageError};
use uucore::process::ChildExt; use uucore::process::ChildExt;
use uucore::signals::{signal_by_name_or_value, signal_name_by_value};
use uucore::{format_usage, show_error}; #[cfg(unix)]
use uucore::signals::enable_pipe_errors;
use uucore::{
format_usage, show_error,
signals::{signal_by_name_or_value, signal_name_by_value},
};
static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION."; static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION.";
const USAGE: &str = "{} [OPTION] DURATION COMMAND..."; const USAGE: &str = "{} [OPTION] DURATION COMMAND...";
@ -285,21 +291,6 @@ fn preserve_signal_info(signal: libc::c_int) -> libc::c_int {
signal signal
} }
#[cfg(unix)]
fn enable_pipe_errors() -> std::io::Result<()> {
let ret = unsafe { libc::signal(libc::SIGPIPE, libc::SIG_DFL) };
if ret == libc::SIG_ERR {
return Err(std::io::Error::new(std::io::ErrorKind::Other, ""));
}
Ok(())
}
#[cfg(not(unix))]
fn enable_pipe_errors() -> std::io::Result<()> {
// Do nothing.
Ok(())
}
/// TODO: Improve exit codes, and make them consistent with the GNU Coreutils exit codes. /// TODO: Improve exit codes, and make them consistent with the GNU Coreutils exit codes.
fn timeout( fn timeout(
@ -314,7 +305,7 @@ fn timeout(
if !foreground { if !foreground {
unsafe { libc::setpgid(0, 0) }; unsafe { libc::setpgid(0, 0) };
} }
#[cfg(unix)]
enable_pipe_errors()?; enable_pipe_errors()?;
let process = &mut process::Command::new(&cmd[0]) let process = &mut process::Command::new(&cmd[0])

View file

@ -16,12 +16,14 @@ path = "src/yes.rs"
[dependencies] [dependencies]
clap = { workspace=true } clap = { workspace=true }
libc = { workspace=true }
uucore = { workspace=true, features=["pipes"] }
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] [target.'cfg(unix)'.dependencies]
uucore = { workspace=true, features=["pipes", "signals"] }
nix = { workspace=true } nix = { workspace=true }
[target.'cfg(not(unix))'.dependencies]
uucore = { workspace=true, features=["pipes"] }
[[bin]] [[bin]]
name = "yes" name = "yes"
path = "src/main.rs" path = "src/main.rs"

View file

@ -7,13 +7,13 @@
/* last synced with: yes (GNU coreutils) 8.13 */ /* last synced with: yes (GNU coreutils) 8.13 */
use std::borrow::Cow;
use std::io::{self, Result, Write};
use clap::{Arg, ArgAction, Command}; use clap::{Arg, ArgAction, Command};
use std::borrow::Cow;
use std::io::{self, Write};
use uucore::error::{UResult, USimpleError}; use uucore::error::{UResult, USimpleError};
#[cfg(unix)]
use uucore::signals::enable_pipe_errors;
use uucore::{format_usage, help_about, help_usage}; use uucore::{format_usage, help_about, help_usage};
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
mod splice; mod splice;
@ -69,25 +69,10 @@ fn prepare_buffer<'a>(input: &'a str, buffer: &'a mut [u8; BUF_SIZE]) -> &'a [u8
} }
} }
#[cfg(unix)]
fn enable_pipe_errors() -> Result<()> {
let ret = unsafe { libc::signal(libc::SIGPIPE, libc::SIG_DFL) };
if ret == libc::SIG_ERR {
return Err(io::Error::new(io::ErrorKind::Other, ""));
}
Ok(())
}
#[cfg(not(unix))]
fn enable_pipe_errors() -> Result<()> {
// Do nothing.
Ok(())
}
pub fn exec(bytes: &[u8]) -> io::Result<()> { pub fn exec(bytes: &[u8]) -> io::Result<()> {
let stdout = io::stdout(); let stdout = io::stdout();
let mut stdout = stdout.lock(); let mut stdout = stdout.lock();
#[cfg(unix)]
enable_pipe_errors()?; enable_pipe_errors()?;
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]

View file

@ -49,7 +49,7 @@ sm3 = { workspace=true }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
walkdir = { workspace=true, optional=true } walkdir = { workspace=true, optional=true }
nix = { workspace=true, features = ["fs", "uio", "zerocopy"] } nix = { workspace=true, features = ["fs", "uio", "zerocopy", "signal"] }
[dev-dependencies] [dev-dependencies]
clap = { workspace=true } clap = { workspace=true }

View file

@ -7,7 +7,12 @@
// spell-checker:ignore (vars/api) fcntl setrlimit setitimer // spell-checker:ignore (vars/api) fcntl setrlimit setitimer
// spell-checker:ignore (vars/signals) ABRT ALRM CHLD SEGV SIGABRT SIGALRM SIGBUS SIGCHLD SIGCONT SIGEMT SIGFPE SIGHUP SIGILL SIGINFO SIGINT SIGIO SIGIOT SIGKILL SIGPIPE SIGPROF SIGPWR SIGQUIT SIGSEGV SIGSTOP SIGSYS SIGTERM SIGTRAP SIGTSTP SIGTHR SIGTTIN SIGTTOU SIGURG SIGUSR SIGVTALRM SIGWINCH SIGXCPU SIGXFSZ STKFLT PWR THR TSTP TTIN TTOU VTALRM XCPU XFSZ // spell-checker:ignore (vars/signals) ABRT ALRM CHLD SEGV SIGABRT SIGALRM SIGBUS SIGCHLD SIGCONT SIGEMT SIGFPE SIGHUP SIGILL SIGINFO SIGINT SIGIO SIGIOT SIGKILL SIGPIPE SIGPROF SIGPWR SIGQUIT SIGSEGV SIGSTOP SIGSYS SIGTERM SIGTRAP SIGTSTP SIGTHR SIGTTIN SIGTTOU SIGURG SIGUSR SIGVTALRM SIGWINCH SIGXCPU SIGXFSZ STKFLT PWR THR TSTP TTIN TTOU VTALRM XCPU XFSZ
#[cfg(unix)]
use nix::errno::Errno;
#[cfg(unix)]
use nix::sys::signal::{
signal, SigHandler::SigDfl, SigHandler::SigIgn, Signal::SIGINT, Signal::SIGPIPE,
};
pub static DEFAULT_SIGNAL: usize = 15; pub static DEFAULT_SIGNAL: usize = 15;
/* /*
@ -196,6 +201,19 @@ pub fn signal_name_by_value(signal_value: usize) -> Option<&'static str> {
ALL_SIGNALS.get(signal_value).copied() ALL_SIGNALS.get(signal_value).copied()
} }
#[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.
// SAFETY: this function is safe as long as we do not use a custom SigHandler -- we use the default one.
unsafe { signal(SIGPIPE, SigDfl) }.map(|_| ())
}
#[cfg(unix)]
pub fn ignore_interrupts() -> Result<(), Errno> {
// We pass the error as is, the return value would just be Ok(SigIgn), so we can safely ignore it.
// SAFETY: this function is safe as long as we do not use a custom SigHandler -- we use the default one.
unsafe { signal(SIGINT, SigIgn) }.map(|_| ())
}
#[test] #[test]
fn signal_by_value() { fn signal_by_value() {
assert_eq!(signal_by_name_or_value("0"), Some(0)); assert_eq!(signal_by_name_or_value("0"), Some(0));

View file

@ -522,7 +522,7 @@ impl From<std::io::Error> for Box<dyn UError> {
/// // prints "fix me please!: Permission denied" /// // prints "fix me please!: Permission denied"
/// println!("{}", uio_result.unwrap_err()); /// println!("{}", uio_result.unwrap_err());
/// ``` /// ```
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(unix)]
impl<T> FromIo<UResult<T>> for Result<T, nix::Error> { impl<T> FromIo<UResult<T>> for Result<T, nix::Error> {
fn map_err_context(self, context: impl FnOnce() -> String) -> UResult<T> { fn map_err_context(self, context: impl FnOnce() -> String) -> UResult<T> {
self.map_err(|e| { self.map_err(|e| {
@ -534,7 +534,7 @@ impl<T> FromIo<UResult<T>> for Result<T, nix::Error> {
} }
} }
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(unix)]
impl<T> FromIo<UResult<T>> for nix::Error { impl<T> FromIo<UResult<T>> for nix::Error {
fn map_err_context(self, context: impl FnOnce() -> String) -> UResult<T> { fn map_err_context(self, context: impl FnOnce() -> String) -> UResult<T> {
Err(Box::new(UIoError { Err(Box::new(UIoError {
@ -544,7 +544,7 @@ impl<T> FromIo<UResult<T>> for nix::Error {
} }
} }
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(unix)]
impl From<nix::Error> for UIoError { impl From<nix::Error> for UIoError {
fn from(f: nix::Error) -> Self { fn from(f: nix::Error) -> Self {
Self { Self {
@ -554,7 +554,7 @@ impl From<nix::Error> for UIoError {
} }
} }
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(unix)]
impl From<nix::Error> for Box<dyn UError> { impl From<nix::Error> for Box<dyn UError> {
fn from(f: nix::Error) -> Self { fn from(f: nix::Error) -> Self {
let u_error: UIoError = f.into(); let u_error: UIoError = f.into();
@ -751,7 +751,7 @@ impl Display for ClapErrorWrapper {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#[test] #[test]
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(unix)]
fn test_nix_error_conversion() { fn test_nix_error_conversion() {
use super::{FromIo, UIoError}; use super::{FromIo, UIoError};
use nix::errno::Errno; use nix::errno::Errno;