mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 11:07:44 +00:00
Merge pull request #6806 from Ecordonnier/eco/signal-handler
uucore: disable default signal-handlers added by Rust
This commit is contained in:
commit
bf6de81a1f
4 changed files with 142 additions and 4 deletions
|
@ -4,6 +4,8 @@
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
//! library ~ (core/bundler file)
|
//! library ~ (core/bundler file)
|
||||||
// #![deny(missing_docs)] //TODO: enable this
|
// #![deny(missing_docs)] //TODO: enable this
|
||||||
|
//
|
||||||
|
// spell-checker:ignore sigaction SIGBUS SIGSEGV
|
||||||
|
|
||||||
// * feature-gated external crates (re-shared as public internal modules)
|
// * feature-gated external crates (re-shared as public internal modules)
|
||||||
#[cfg(feature = "libc")]
|
#[cfg(feature = "libc")]
|
||||||
|
@ -100,6 +102,12 @@ pub use crate::features::fsxattr;
|
||||||
|
|
||||||
//## core functions
|
//## core functions
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use nix::errno::Errno;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use nix::sys::signal::{
|
||||||
|
sigaction, SaFlags, SigAction, SigHandler::SigDfl, SigSet, Signal::SIGBUS, Signal::SIGSEGV,
|
||||||
|
};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
@ -112,6 +120,25 @@ use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
/// Disables the custom signal handlers installed by Rust for stack-overflow handling. With those custom signal handlers processes ignore the first SIGBUS and SIGSEGV signal they receive.
|
||||||
|
/// See <https://github.com/rust-lang/rust/blob/8ac1525e091d3db28e67adcbbd6db1e1deaa37fb/src/libstd/sys/unix/stack_overflow.rs#L71-L92> for details.
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn disable_rust_signal_handlers() -> Result<(), Errno> {
|
||||||
|
unsafe {
|
||||||
|
sigaction(
|
||||||
|
SIGSEGV,
|
||||||
|
&SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
|
||||||
|
)
|
||||||
|
}?;
|
||||||
|
unsafe {
|
||||||
|
sigaction(
|
||||||
|
SIGBUS,
|
||||||
|
&SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
|
||||||
|
)
|
||||||
|
}?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute utility code for `util`.
|
/// Execute utility code for `util`.
|
||||||
///
|
///
|
||||||
/// This macro expands to a main function that invokes the `uumain` function in `util`
|
/// This macro expands to a main function that invokes the `uumain` function in `util`
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
//
|
//
|
||||||
// spell-checker:ignore backticks uuhelp
|
// spell-checker:ignore backticks uuhelp SIGSEGV
|
||||||
|
|
||||||
//! A collection of procedural macros for uutils.
|
//! A collection of procedural macros for uutils.
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
@ -25,6 +25,10 @@ pub fn main(_args: TokenStream, stream: TokenStream) -> TokenStream {
|
||||||
let new = quote!(
|
let new = quote!(
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
#stream
|
#stream
|
||||||
|
|
||||||
|
// 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");
|
||||||
let result = uumain(args);
|
let result = uumain(args);
|
||||||
match result {
|
match result {
|
||||||
Ok(()) => uucore::error::get_exit_code(),
|
Ok(()) => uucore::error::get_exit_code(),
|
||||||
|
|
|
@ -4,9 +4,11 @@
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
// spell-checker:ignore dont
|
// spell-checker:ignore dont SIGBUS SIGSEGV sigsegv sigbus
|
||||||
use crate::common::util::TestScenario;
|
use crate::common::util::TestScenario;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use nix::sys::signal::Signal::{SIGBUS, SIGSEGV};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -135,6 +137,40 @@ fn test_sleep_wrong_time() {
|
||||||
new_ucmd!().args(&["0.1s", "abc"]).fails();
|
new_ucmd!().args(&["0.1s", "abc"]).fails();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn test_sleep_stops_after_sigsegv() {
|
||||||
|
let mut child = new_ucmd!()
|
||||||
|
.arg("100")
|
||||||
|
.timeout(Duration::from_secs(10))
|
||||||
|
.run_no_wait();
|
||||||
|
|
||||||
|
child
|
||||||
|
.delay(100)
|
||||||
|
.kill_with_custom_signal(SIGSEGV)
|
||||||
|
.make_assertion()
|
||||||
|
.with_current_output()
|
||||||
|
.signal_is(SIGSEGV as i32) // make sure it was us who terminated the process
|
||||||
|
.no_output();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn test_sleep_stops_after_sigbus() {
|
||||||
|
let mut child = new_ucmd!()
|
||||||
|
.arg("100")
|
||||||
|
.timeout(Duration::from_secs(10))
|
||||||
|
.run_no_wait();
|
||||||
|
|
||||||
|
child
|
||||||
|
.delay(100)
|
||||||
|
.kill_with_custom_signal(SIGBUS)
|
||||||
|
.make_assertion()
|
||||||
|
.with_current_output()
|
||||||
|
.signal_is(SIGBUS as i32) // make sure it was us who terminated the process
|
||||||
|
.no_output();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sleep_when_single_input_exceeds_max_duration_then_no_error() {
|
fn test_sleep_when_single_input_exceeds_max_duration_then_no_error() {
|
||||||
let mut child = new_ucmd!()
|
let mut child = new_ucmd!()
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
//spell-checker: ignore (linux) rlimit prlimit coreutil ggroups uchild uncaptured scmd SHLVL canonicalized openpty
|
//spell-checker: ignore (linux) rlimit prlimit coreutil ggroups uchild uncaptured scmd SHLVL canonicalized openpty
|
||||||
//spell-checker: ignore (linux) winsize xpixel ypixel setrlimit FSIZE
|
//spell-checker: ignore (linux) winsize xpixel ypixel setrlimit FSIZE SIGBUS SIGSEGV sigbus
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
#![allow(
|
#![allow(
|
||||||
|
@ -17,6 +17,8 @@
|
||||||
use libc::mode_t;
|
use libc::mode_t;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use nix::pty::OpenptyResult;
|
use nix::pty::OpenptyResult;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use nix::sys;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use rlimit::setrlimit;
|
use rlimit::setrlimit;
|
||||||
|
@ -2095,7 +2097,7 @@ impl UChild {
|
||||||
self.delay(millis).make_assertion()
|
self.delay(millis).make_assertion()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to kill the child process and wait for it's termination.
|
/// Try to kill the child process and wait for its termination.
|
||||||
///
|
///
|
||||||
/// This method blocks until the child process is killed, but returns an error if `self.timeout`
|
/// This method blocks until the child process is killed, but returns an error if `self.timeout`
|
||||||
/// or the default of 60s was reached. If no such error happened, the process resources are
|
/// or the default of 60s was reached. If no such error happened, the process resources are
|
||||||
|
@ -2155,6 +2157,75 @@ impl UChild {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to kill the child process and wait for its termination.
|
||||||
|
///
|
||||||
|
/// This method blocks until the child process is killed, but returns an error if `self.timeout`
|
||||||
|
/// or the default of 60s was reached. If no such error happened, the process resources are
|
||||||
|
/// released, so there is usually no need to call `wait` or alike on unix systems although it's
|
||||||
|
/// still possible to do so.
|
||||||
|
///
|
||||||
|
/// # Platform specific behavior
|
||||||
|
///
|
||||||
|
/// On unix systems the child process resources will be released like a call to [`Child::wait`]
|
||||||
|
/// or alike would do.
|
||||||
|
///
|
||||||
|
/// # Error
|
||||||
|
///
|
||||||
|
/// If [`Child::kill`] returned an error or if the child process could not be terminated within
|
||||||
|
/// `self.timeout` or the default of 60s.
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn try_kill_with_custom_signal(
|
||||||
|
&mut self,
|
||||||
|
signal_name: sys::signal::Signal,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let start = Instant::now();
|
||||||
|
sys::signal::kill(
|
||||||
|
nix::unistd::Pid::from_raw(self.raw.id().try_into().unwrap()),
|
||||||
|
signal_name,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let timeout = self.timeout.unwrap_or(Duration::from_secs(60));
|
||||||
|
// As a side effect, we're cleaning up the killed child process with the implicit call to
|
||||||
|
// `Child::try_wait` in `self.is_alive`, which reaps the process id on unix systems. We
|
||||||
|
// always fail with error on timeout if `self.timeout` is set to zero.
|
||||||
|
while self.is_alive() || timeout == Duration::ZERO {
|
||||||
|
if start.elapsed() < timeout {
|
||||||
|
self.delay(10);
|
||||||
|
} else {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("kill: Timeout of '{}s' reached", timeout.as_secs_f64()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
hint::spin_loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Terminate the child process using custom signal parameter and wait for the termination.
|
||||||
|
///
|
||||||
|
/// Ignores any errors happening during [`Child::kill`] (i.e. child process already exited) but
|
||||||
|
/// still panics on timeout.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If the child process could not be terminated within `self.timeout` or the default of 60s.
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn kill_with_custom_signal(&mut self, signal_name: sys::signal::Signal) -> &mut Self {
|
||||||
|
self.try_kill_with_custom_signal(signal_name)
|
||||||
|
.or_else(|error| {
|
||||||
|
// We still throw the error on timeout in the `try_kill` function
|
||||||
|
if error.kind() == io::ErrorKind::Other {
|
||||||
|
Err(error)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Wait for the child process to terminate and return a [`CmdResult`].
|
/// Wait for the child process to terminate and return a [`CmdResult`].
|
||||||
///
|
///
|
||||||
/// See [`UChild::wait_with_output`] for details on timeouts etc. This method can also be run if
|
/// See [`UChild::wait_with_output`] for details on timeouts etc. This method can also be run if
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue