#![allow(unsafe_code)]
use anyhow::{Context, Result, anyhow};
use once_cell::sync::OnceCell;
use windows_sys::Win32::{
Foundation::{CloseHandle, HANDLE},
System::{
JobObjects::{
AssignProcessToJobObject,
CreateJobObjectW,
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
JobObjectExtendedLimitInformation,
SetInformationJobObject,
},
Threading::{OpenProcess, PROCESS_ALL_ACCESS},
},
};
struct Job(HANDLE);
unsafe impl Send for Job {}
unsafe impl Sync for Job {}
impl Job {
fn create_kill_on_close() -> Result<Self> {
let handle = unsafe { CreateJobObjectW(std::ptr::null_mut(), std::ptr::null()) } as HANDLE;
if handle.is_null() {
return Err(anyhow!("CreateJobObjectW failed"));
}
let mut limits: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { core::mem::zeroed() };
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
let ok = unsafe {
SetInformationJobObject(
handle,
JobObjectExtendedLimitInformation,
&mut limits as *mut _ as *mut _,
std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
)
};
if ok == 0 {
unsafe { CloseHandle(handle) };
return Err(anyhow!("SetInformationJobObject failed"));
}
Ok(Job(handle))
}
fn add_process(&self, pid: u32) -> Result<()> {
let proc = unsafe { OpenProcess(PROCESS_ALL_ACCESS, 0, pid) } as HANDLE;
if proc.is_null() {
return Err(anyhow!("OpenProcess({pid}) failed")).context("child already exited?");
}
let ok = unsafe { AssignProcessToJobObject(self.0, proc) };
if ok == 0 {
return Err(anyhow!("AssignProcessToJobObject failed"));
}
Ok(())
}
}
impl Drop for Job {
fn drop(&mut self) {
unsafe { CloseHandle(self.0) };
}
}
static JOB: OnceCell<Job> = OnceCell::new();
fn global_job() -> Result<&'static Job> {
JOB.get_or_try_init(Job::create_kill_on_close)
}
pub fn attach_to_global_job(pid: u32) -> Result<()> {
match global_job()?.add_process(pid) {
Ok(()) => Ok(()),
Err(e) => {
eprintln!("[windows_kill_tree] WARN: {e:?} – falling back to manual kill");
Ok(())
}
}
}