8000 timeout: catch TERM signal · uutils/coreutils@c354e53 · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit c354e53

Browse files
committed
timeout: catch TERM signal
1 parent 625e5ee commit c354e53

File tree

3 files changed

+45
-7
lines changed

3 files changed

+45
-7
lines changed

src/uu/timeout/src/status.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ pub(crate) enum ExitStatus {
3030

3131
/// When there is a failure while waiting for the child process to terminate.
3232
WaitingFailed,
33+
34+
/// When `SIGTERM` signal received.
35+
Terminated,
3336
}
3437

3538
impl From<ExitStatus> for i32 {
@@ -39,6 +42,7 @@ impl From<ExitStatus> for i32 {
3942
ExitStatus::TimeoutFailed => 125,
4043
ExitStatus::SignalSent(s) => 128 + s as Self,
4144
ExitStatus::WaitingFailed => 124,
45+
ExitStatus::Terminated => 143,
4246
}
4347
}
4448
}

src/uu/timeout/src/timeout.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::collections::HashMap;
1212
use std::io::ErrorKind;
1313
use std::os::unix::process::ExitStatusExt;
1414
use std::process::{self, Child, Stdio};
15+
use std::sync::atomic::{self, AtomicBool};
1516
use std::time::Duration;
1617
use uucore::display::Quotable;
1718
use uucore::error::{UClapError, UResult, USimpleError, UUsageError};
@@ -185,6 +186,23 @@ fn unblock_sigchld() {
185186
}
186187
}
187188

189+
/// We should terminate child process when receiving TERM signal.
190+
static SIGTERMED: AtomicBool = AtomicBool::new(false);
191+
192+
fn catch_sigterm() {
193+
use nix::sys::signal;
194+
195+
extern "C" fn handle_sigterm(signal: libc::c_int) {
196+
let signal = signal::Signal::try_from(signal).unwrap();
197+
if signal == signal::Signal::SIGTERM {
198+
SIGTERMED.store(true, atomic::Ordering::Relaxed);
199+
}
200+
}
201+
202+
let handler = signal::SigHandler::Handler(handle_sigterm);
203+
unsafe { signal::signal(signal::Signal::SIGTERM, handler) }.unwrap();
204+
}
205+
188206
/// Report that a signal is being sent if the verbose flag is set.
189207
fn report_if_verbose(signal: usize, cmd: &str, verbose: bool) {
190208
if verbose {
@@ -246,7 +264,8 @@ fn wait_or_kill_process(
246264
foreground: bool,
247265
verbose: bool,
248266
) -> std::io::Result<i32> {
249-
match process.wait_or_timeout(duration) {
267+
// ignore `SIGTERM` here
268+
match process.wait_or_timeout(duration, None) {
250269
Ok(Some(status)) => {
251270
if preserve_status {
252271
Ok(status.code().unwrap_or_else(|| status.signal().unwrap()))
@@ -330,6 +349,7 @@ fn timeout(
330349
)
331350
})?;
332351
unblock_sigchld();
352+
catch_sigterm();
333353
// Wait for the child process for the specified time period.
334354
//
335355
// If the process exits within the specified time period (the
@@ -341,7 +361,7 @@ fn timeout(
341361
// TODO The structure of this block is extremely similar to the
342362
// structure of `wait_or_kill_process()`. They can probably be
343363
// refactored into some common function.
344-
match process.wait_or_timeout(duration) {
364+
match process.wait_or_timeout(duration, Some(&SIGTERMED)) {
345365
Ok(Some(status)) => Err(status
346366
.code()
347367
.unwrap_or_else(|| preserve_signal_info(status.signal().unwrap()))
@@ -352,7 +372,9 @@ fn timeout(
352372
match kill_after {
353373
None => {
354374
let status = process.wait()?;
355-
if preserve_status {
375+
if SIGTERMED.load(atomic::Ordering::Relaxed) {
376+
Err(ExitStatus::Terminated.into())
377+
} else if preserve_status {
356378
if let Some(ec) = status.code() {
357379
Err(ec.into())
358380
} else if let Some(sc) = status.signal() {

src/uucore/src/lib/features/process.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use nix::errno::Errno;
1313
use std::io;
1414
use std::process::Child;
1515
use std::process::ExitStatus;
16+
use std::sync::atomic;
17+
use std::sync::atomic::AtomicBool;
1618
use std::thread;
1719
use std::time::{Duration, Instant};
1820

@@ -88,7 +90,11 @@ pub trait ChildExt {
8890

8991
/// Wait for a process to finish or return after the specified duration.
9092
/// A `timeout` of zero disables the timeout.
91-
fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result<Option<ExitStatus>>;
93+
fn wait_or_timeout(
94+
&mut self,
95+
timeout: Duration,
96+
signaled: Option<&AtomicBool>,
97+
) -> io::Result<Option<ExitStatus>>;
9298
}
9399

94100
impl ChildExt for Child {
@@ -102,7 +108,7 @@ impl ChildExt for Child {
102108

103109
fn send_signal_group(&mut self, signal: usize) -> io::Result<()> {
104110
// Ignore the signal, so we don't go into a signal loop.
105-
if unsafe { libc::signal(signal as i32, libc::SIG_IGN) } != 0 {
111+
if unsafe { libc::signal(signal as i32, libc::SIG_IGN) } == usize::MAX {
106112
return Err(io::Error::last_os_error());
107113
}
108114
if unsafe { libc::kill(0, signal as i32) } == 0 {
@@ -112,7 +118,11 @@ impl ChildExt for Child {
112118
}
113119
}
114120

115-
fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result<Option<ExitStatus>> {
121+
fn wait_or_timeout(
122+
&mut self,
123+
timeout: Duration,
124+
signaled: Option<&AtomicBool>,
125+
) -> io::Result<Option<ExitStatus>> {
116126
if timeout == Duration::from_micros(0) {
117127
return self.wait().map(Some);
118128
}
@@ -125,7 +135,9 @@ impl ChildExt for Child {
125135
return Ok(Some(status));
126136
}
127137

128-
if start.elapsed() >= timeout {
138+
if start.elapsed() >= timeout
139+
|| signaled.is_some_and(|sigtermed| sigtermed.load(atomic::Ordering::Relaxed))
140+
{
129141
break;
130142
}
131143

0 commit comments

Comments
 (0)
0