#[cfg(windows)]
use std::process::Stdio;
use std::process::{Child, Command};
use std::sync::{Mutex, OnceLock};
#[cfg(unix)]
pub(super) fn set_own_process_group(cmd: &mut Command) {
use std::os::unix::process::CommandExt as _;
cmd.process_group(0);
}
#[cfg(windows)]
pub(super) fn set_own_process_group(cmd: &mut Command) {
use std::os::windows::process::CommandExt as _;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
cmd.creation_flags(CREATE_NEW_PROCESS_GROUP);
}
#[cfg(not(any(unix, windows)))]
pub(super) fn set_own_process_group(_cmd: &mut Command) {}
#[derive(Clone, Copy)]
pub(super) struct ChildTree {
pub(super) pid: i32,
#[cfg(windows)]
pub(super) job: Option<windows_job::JobHandle>,
}
impl ChildTree {
fn reap(self, signal: i32) {
#[cfg(unix)]
{
unsafe {
libc::kill(-self.pid, signal);
}
}
#[cfg(windows)]
{
let _ = signal; match self.job {
Some(job) => job.terminate(),
None => taskkill_tree(self.pid),
}
}
}
}
#[cfg(windows)]
fn taskkill_tree(pid: i32) {
let taskkill = std::env::var_os("SystemRoot")
.map(|root| {
std::path::Path::new(&root)
.join("System32")
.join("taskkill.exe")
})
.unwrap_or_else(|| std::path::PathBuf::from("taskkill.exe"));
let _ = std::process::Command::new(taskkill)
.args(["/T", "/F", "/PID", &pid.to_string()])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
}
pub(super) fn kill_child_tree(child: &mut Child, tree: ChildTree) {
#[cfg(unix)]
tree.reap(libc::SIGKILL);
#[cfg(windows)]
tree.reap(0);
let _ = child.kill();
}
#[cfg(windows)]
pub(super) mod windows_job {
use std::ffi::c_void;
use std::os::windows::io::AsRawHandle as _;
use std::process::Child;
type Handle = *mut c_void;
type Bool = i32;
type Dword = u32;
const JOB_OBJECT_EXTENDED_LIMIT_INFORMATION: i32 = 9;
const JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE: Dword = 0x0000_2000;
const JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION: Dword = 0x0000_0400;
#[repr(C)]
#[derive(Clone, Copy)]
#[allow(dead_code)]
struct JobObjectBasicLimitInformation {
per_process_user_time_limit: i64,
per_job_user_time_limit: i64,
limit_flags: Dword,
minimum_working_set_size: usize,
maximum_working_set_size: usize,
active_process_limit: Dword,
affinity: usize,
priority_class: Dword,
scheduling_class: Dword,
}
#[repr(C)]
#[derive(Clone, Copy)]
#[allow(dead_code)]
struct IoCounters {
read_operation_count: u64,
write_operation_count: u64,
other_operation_count: u64,
read_transfer_count: u64,
write_transfer_count: u64,
other_transfer_count: u64,
}
#[repr(C)]
#[derive(Clone, Copy)]
#[allow(dead_code)]
struct JobObjectExtendedLimitInformation {
basic_limit_information: JobObjectBasicLimitInformation,
io_info: IoCounters,
process_memory_limit: usize,
job_memory_limit: usize,
peak_process_memory_used: usize,
peak_job_memory_used: usize,
}
unsafe extern "system" {
fn CreateJobObjectW(attrs: *mut c_void, name: *const u16) -> Handle;
fn SetInformationJobObject(
job: Handle,
class: i32,
info: *const c_void,
len: Dword,
) -> Bool;
fn AssignProcessToJobObject(job: Handle, process: Handle) -> Bool;
fn TerminateJobObject(job: Handle, exit_code: Dword) -> Bool;
fn CloseHandle(object: Handle) -> Bool;
}
#[derive(Clone, Copy)]
pub struct JobHandle(isize);
unsafe impl Send for JobHandle {}
unsafe impl Sync for JobHandle {}
impl JobHandle {
pub fn terminate(self) {
unsafe {
let _ = TerminateJobObject(self.0 as Handle, 1);
}
}
pub fn close(self) {
unsafe {
let _ = CloseHandle(self.0 as Handle);
}
}
}
pub fn enclose_child(child: &Child) -> Option<JobHandle> {
unsafe {
let job = CreateJobObjectW(std::ptr::null_mut(), std::ptr::null());
if job.is_null() {
return None;
}
let mut info: JobObjectExtendedLimitInformation = std::mem::zeroed();
info.basic_limit_information.limit_flags =
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION;
if SetInformationJobObject(
job,
JOB_OBJECT_EXTENDED_LIMIT_INFORMATION,
std::ptr::addr_of!(info) as *const c_void,
std::mem::size_of::<JobObjectExtendedLimitInformation>() as Dword,
) == 0
{
let _ = CloseHandle(job);
return None;
}
if AssignProcessToJobObject(job, child.as_raw_handle() as Handle) == 0 {
let _ = CloseHandle(job);
return None;
}
Some(JobHandle(job as isize))
}
}
}
static LIVE_CHILD_TREES: OnceLock<Mutex<std::collections::HashMap<i32, ChildTree>>> =
OnceLock::new();
pub(super) fn live_child_trees() -> &'static Mutex<std::collections::HashMap<i32, ChildTree>> {
LIVE_CHILD_TREES.get_or_init(|| Mutex::new(std::collections::HashMap::new()))
}
pub(super) fn register_child_tree(tree: ChildTree) {
live_child_trees()
.lock()
.unwrap_or_else(|p| p.into_inner())
.insert(tree.pid, tree);
}
pub(super) fn deregister_child_tree(pid: i32) {
live_child_trees()
.lock()
.unwrap_or_else(|p| p.into_inner())
.remove(&pid);
}
pub(super) struct TreeRegistration(pub(super) ChildTree);
impl Drop for TreeRegistration {
fn drop(&mut self) {
deregister_child_tree(self.0.pid);
#[cfg(windows)]
if let Some(job) = self.0.job {
job.close();
}
}
}
pub(super) fn terminate_all_child_trees() -> usize {
let trees: Vec<ChildTree> = {
let guard = live_child_trees().lock().unwrap_or_else(|p| p.into_inner());
guard.values().copied().collect()
};
for tree in trees.iter().copied() {
#[cfg(unix)]
tree.reap(libc::SIGTERM);
#[cfg(windows)]
tree.reap(0);
}
trees.len()
}
pub fn install_termination_handler() {
static INSTALLED: OnceLock<()> = OnceLock::new();
if INSTALLED.set(()).is_err() {
return; }
#[cfg(unix)]
unix_termination::install();
#[cfg(windows)]
windows_termination::install();
}
#[cfg(unix)]
mod unix_termination {
use super::terminate_all_child_trees;
use std::os::unix::io::RawFd;
use std::sync::atomic::{AtomicI32, Ordering};
static WAKE_WRITE_FD: AtomicI32 = AtomicI32::new(-1);
static FIRED_SIGNAL: AtomicI32 = AtomicI32::new(0);
extern "C" fn on_signal(sig: libc::c_int) {
FIRED_SIGNAL.store(sig, Ordering::SeqCst);
let fd = WAKE_WRITE_FD.load(Ordering::SeqCst);
if fd >= 0 {
let byte: u8 = 1;
unsafe {
let _ = libc::write(fd, &byte as *const u8 as *const libc::c_void, 1);
}
}
}
pub fn install() {
let mut fds: [RawFd; 2] = [-1, -1];
if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
return;
}
let (read_fd, write_fd) = (fds[0], fds[1]);
WAKE_WRITE_FD.store(write_fd, Ordering::SeqCst);
unsafe {
let mut sa: libc::sigaction = std::mem::zeroed();
sa.sa_sigaction = on_signal as *const () as usize;
libc::sigemptyset(&mut sa.sa_mask);
sa.sa_flags = 0;
libc::sigaction(libc::SIGTERM, &sa, std::ptr::null_mut());
libc::sigaction(libc::SIGINT, &sa, std::ptr::null_mut());
}
std::thread::Builder::new()
.name("anodizer-sigwatch".into())
.spawn(move || watcher(read_fd))
.ok();
}
fn watcher(read_fd: RawFd) -> ! {
let mut byte = [0u8; 1];
loop {
let n = unsafe { libc::read(read_fd, byte.as_mut_ptr() as *mut libc::c_void, 1) };
if n != 0 {
break; }
}
terminate_all_child_trees();
let sig = FIRED_SIGNAL.load(Ordering::SeqCst);
let sig = if sig == 0 { libc::SIGTERM } else { sig };
unsafe {
let mut sa: libc::sigaction = std::mem::zeroed();
sa.sa_sigaction = libc::SIG_DFL;
libc::sigemptyset(&mut sa.sa_mask);
sa.sa_flags = 0;
libc::sigaction(sig, &sa, std::ptr::null_mut());
libc::raise(sig);
}
std::process::exit(128 + sig);
}
}
#[cfg(windows)]
mod windows_termination {
use super::terminate_all_child_trees;
use std::sync::atomic::{AtomicBool, Ordering};
type Bool = i32;
type Dword = u32;
const TRUE: Bool = 1;
const CTRL_C_EVENT: Dword = 0;
const CTRL_BREAK_EVENT: Dword = 1;
const CTRL_CLOSE_EVENT: Dword = 2;
const CTRL_LOGOFF_EVENT: Dword = 5;
const CTRL_SHUTDOWN_EVENT: Dword = 6;
static FIRED: AtomicBool = AtomicBool::new(false);
unsafe extern "system" {
fn SetConsoleCtrlHandler(handler: Option<HandlerRoutine>, add: Bool) -> Bool;
}
type HandlerRoutine = unsafe extern "system" fn(ctrl_type: Dword) -> Bool;
unsafe extern "system" fn on_ctrl(ctrl_type: Dword) -> Bool {
match ctrl_type {
CTRL_C_EVENT | CTRL_BREAK_EVENT | CTRL_CLOSE_EVENT | CTRL_LOGOFF_EVENT
| CTRL_SHUTDOWN_EVENT => {
FIRED.store(true, Ordering::SeqCst);
terminate_all_child_trees();
0
}
_ => 0,
}
}
pub fn install() {
unsafe {
SetConsoleCtrlHandler(Some(on_ctrl), TRUE);
}
}
}