use super::{ProcessControl, TreeReaper};
use std::io;
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
use std::ptr;
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::System::JobObjects::{
AssignProcessToJobObject, CreateJobObjectW, JobObjectExtendedLimitInformation,
SetInformationJobObject, TerminateJobObject, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
};
use windows_sys::Win32::System::Threading::{
OpenProcess, CREATE_NEW_PROCESS_GROUP, DETACHED_PROCESS, PROCESS_SET_QUOTA, PROCESS_TERMINATE,
};
#[derive(Debug, Clone, Copy, Default)]
pub struct WindowsProcessControl;
impl WindowsProcessControl {
pub const fn new() -> Self {
Self
}
}
impl crate::sealed::Sealed for WindowsProcessControl {}
impl ProcessControl for WindowsProcessControl {
type Reaper = WindowsReaper;
fn detach(&self, cmd: &mut tokio::process::Command) {
cmd.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP);
}
fn new_reaper(&self) -> io::Result<Self::Reaper> {
let job = unsafe { CreateJobObjectW(ptr::null(), ptr::null()) };
if job.is_null() {
return Err(io::Error::last_os_error());
}
let job = unsafe { OwnedHandle::from_raw_handle(job as _) };
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { std::mem::zeroed() };
info.BasicLimitInformation.LimitFlags =
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION;
let ok = unsafe {
SetInformationJobObject(
job.as_raw_handle() as HANDLE,
JobObjectExtendedLimitInformation,
(&raw const info).cast(),
size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
)
};
if ok == 0 {
return Err(io::Error::last_os_error());
}
Ok(WindowsReaper(job))
}
}
pub struct WindowsReaper(OwnedHandle);
impl TreeReaper for WindowsReaper {
const ADOPTION_IS_ATOMIC: bool = false;
fn adopt(&self, child: &tokio::process::Child) -> io::Result<()> {
let Some(pid) = child.id() else {
return Err(io::Error::other("the child has already been reaped"));
};
let handle = unsafe { OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, 0, pid) };
if handle.is_null() {
return Err(io::Error::last_os_error());
}
let handle = unsafe { OwnedHandle::from_raw_handle(handle as _) };
let ok = unsafe {
AssignProcessToJobObject(
self.0.as_raw_handle() as HANDLE,
handle.as_raw_handle() as HANDLE,
)
};
if ok == 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn kill_tree(&self) -> io::Result<()> {
if unsafe { TerminateJobObject(self.0.as_raw_handle() as HANDLE, 1) } == 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
}