Skip to content

Commit 757d5b9

Browse files
committed
rust: add supervisor function to forward signals to child processes
1 parent 8e9526c commit 757d5b9

File tree

3 files changed

+128
-0
lines changed

3 files changed

+128
-0
lines changed

rust/bear/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ nom.workspace = true
4040
regex.workspace = true
4141
rand.workspace = true
4242
tempfile.workspace = true
43+
nix = { version = "0.29", optional = true, features = ["signal", "process"] }
44+
winapi = { version = "0.3", optional = true, features = ["processthreadsapi", "winnt", "handleapi"] }
45+
signal-hook = "0.3.17"
46+
47+
[target.'cfg(unix)'.dependencies]
48+
nix = { version = "0.29", features = ["signal", "process"] }
49+
50+
[target.'cfg(windows)'.dependencies]
51+
winapi = { version = "0.3", features = ["processthreadsapi", "winnt", "handleapi"] }
4352

4453
[profile.release]
4554
strip = true

rust/bear/src/intercept/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use std::sync::Arc;
1919
use std::{env, fmt, thread};
2020

2121
pub mod persistence;
22+
pub mod supervise;
2223
pub mod tcp;
2324

2425
/// Declare the environment variables used by the intercept mode.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
use anyhow::Result;
4+
use nix::libc::c_int;
5+
#[cfg(unix)]
6+
use nix::sys::signal::{kill, Signal};
7+
#[cfg(unix)]
8+
use nix::unistd::Pid;
9+
use std::process::{Command, ExitStatus};
10+
use std::sync::atomic::{AtomicBool, Ordering};
11+
use std::sync::Arc;
12+
use std::thread;
13+
use std::time::Duration;
14+
#[cfg(windows)]
15+
use winapi::shared::minwindef::FALSE;
16+
#[cfg(windows)]
17+
use winapi::um::processthreadsapi::{OpenProcess, TerminateProcess};
18+
#[cfg(windows)]
19+
use winapi::um::winnt::{PROCESS_TERMINATE, SYNCHRONIZE};
20+
21+
/// This method supervises the execution of a command.
22+
///
23+
/// It starts the command and waits for its completion. It also forwards
24+
/// signals to the child process. The method returns the exit status of the
25+
/// child process.
26+
pub fn supervise(command: &mut Command) -> Result<ExitStatus> {
27+
let mut child = command.spawn()?;
28+
29+
let child_pid = child.id();
30+
let running = Arc::new(AtomicBool::new(true));
31+
let running_in_thread = running.clone();
32+
33+
let mut signals = signal_hook::iterator::Signals::new([
34+
signal_hook::consts::SIGINT,
35+
signal_hook::consts::SIGTERM,
36+
])?;
37+
38+
#[cfg(unix)]
39+
{
40+
signals.add_signal(signal_hook::consts::SIGHUP)?;
41+
signals.add_signal(signal_hook::consts::SIGQUIT)?;
42+
signals.add_signal(signal_hook::consts::SIGALRM)?;
43+
signals.add_signal(signal_hook::consts::SIGUSR1)?;
44+
signals.add_signal(signal_hook::consts::SIGUSR2)?;
45+
signals.add_signal(signal_hook::consts::SIGCONT)?;
46+
signals.add_signal(signal_hook::consts::SIGSTOP)?;
47+
}
48+
49+
let handler = thread::spawn(move || {
50+
for signal in signals.forever() {
51+
log::debug!("Received signal: {:?}", signal);
52+
if forward_signal(signal, child_pid) {
53+
// If the signal caused termination, we should stop the process.
54+
running_in_thread.store(false, Ordering::SeqCst);
55+
break;
56+
}
57+
}
58+
});
59+
60+
while running.load(Ordering::SeqCst) {
61+
thread::sleep(Duration::from_millis(100));
62+
}
63+
handler.join().unwrap();
64+
65+
let exit_status = child.wait()?;
66+
67+
Ok(exit_status)
68+
}
69+
70+
#[cfg(windows)]
71+
fn forward_signal(_: c_int, child_pid: u32) -> bool {
72+
let process_handle = unsafe { OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, FALSE, child_pid) };
73+
if process_handle.is_null() {
74+
let err = unsafe { winapi::um::errhandling::GetLastError() };
75+
log::error!("Failed to open process: {}", err);
76+
// If the process handle is not valid, presume the process is not running anymore.
77+
return true;
78+
}
79+
80+
let terminated = unsafe { TerminateProcess(process_handle, 1) };
81+
if terminated == FALSE {
82+
let err = unsafe { winapi::um::errhandling::GetLastError() };
83+
log::error!("Failed to terminate process: {}", err);
84+
}
85+
86+
// Ensure proper handle closure
87+
unsafe { winapi::um::handleapi::CloseHandle(process_handle) };
88+
89+
// Return true if the process was terminated.
90+
terminated == TRUE
91+
}
92+
93+
#[cfg(unix)]
94+
fn forward_signal(signal: c_int, child_pid: u32) -> bool {
95+
// Forward the signal to the child process
96+
if let Err(e) = kill(
97+
Pid::from_raw(child_pid as i32),
98+
Signal::try_from(signal).ok(),
99+
) {
100+
log::error!("Error forwarding signal: {}", e);
101+
}
102+
103+
// Return true if the process was terminated.
104+
match kill(Pid::from_raw(child_pid as i32), None) {
105+
Ok(_) => {
106+
log::debug!("Checking if the process is still running... yes");
107+
false
108+
}
109+
Err(nix::Error::ESRCH) => {
110+
log::debug!("Checking if the process is still running... no");
111+
true
112+
}
113+
Err(_) => {
114+
log::debug!("Checking if the process is still running... presume dead");
115+
true
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)