use filedescriptor::OwnedHandle;
use std::io;
use std::os::windows::io::AsRawHandle;
use std::os::windows::io::FromRawHandle;
use std::os::windows::io::RawHandle;
use std::sync::Mutex;
use winapi::um::jobapi2::AssignProcessToJobObject;
use winapi::um::jobapi2::CreateJobObjectW;
use winapi::um::jobapi2::SetInformationJobObject;
use winapi::um::jobapi2::TerminateJobObject;
use winapi::um::winnt::JOB_OBJECT_LIMIT_BREAKAWAY_OK;
use winapi::um::winnt::JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
use winapi::um::winnt::JOBOBJECT_EXTENDED_LIMIT_INFORMATION;
use winapi::um::winnt::JobObjectExtendedLimitInformation;
#[derive(Debug)]
pub struct JobObject {
handle: OwnedHandle,
preserve_descendants: Mutex<bool>,
}
impl JobObject {
pub fn create() -> io::Result<Self> {
let handle = unsafe { CreateJobObjectW(std::ptr::null_mut(), std::ptr::null()) };
if handle.is_null() {
return Err(io::Error::last_os_error());
}
let handle = unsafe { OwnedHandle::from_raw_handle(handle.cast()) };
Self::set_limit_flags(
&handle,
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK,
)?;
Ok(Self {
handle,
preserve_descendants: Mutex::new(false),
})
}
fn set_limit_flags(handle: &OwnedHandle, flags: u32) -> io::Result<()> {
let mut limits: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { std::mem::zeroed() };
limits.BasicLimitInformation.LimitFlags = flags;
let configured = unsafe {
SetInformationJobObject(
handle.as_raw_handle().cast(),
JobObjectExtendedLimitInformation,
std::ptr::addr_of_mut!(limits).cast(),
std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
)
};
if configured == 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
pub(crate) fn assign_process(&self, process_handle: RawHandle) -> io::Result<()> {
let assigned = unsafe {
AssignProcessToJobObject(self.handle.as_raw_handle().cast(), process_handle.cast())
};
if assigned == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub fn preserve_descendants(&self) -> io::Result<()> {
let mut preserve_descendants = self
.preserve_descendants
.lock()
.map_err(|_| io::Error::other("job state lock poisoned"))?;
if *preserve_descendants {
return Ok(());
}
Self::set_limit_flags(&self.handle, JOB_OBJECT_LIMIT_BREAKAWAY_OK)?;
*preserve_descendants = true;
Ok(())
}
pub fn terminate(&self) -> io::Result<()> {
let preserve_descendants = self
.preserve_descendants
.lock()
.map_err(|_| io::Error::other("job state lock poisoned"))?;
if *preserve_descendants {
return Ok(());
}
let terminated = unsafe {
TerminateJobObject(self.handle.as_raw_handle().cast(), 1)
};
if terminated == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
}
impl AsRawHandle for JobObject {
fn as_raw_handle(&self) -> RawHandle {
self.handle.as_raw_handle()
}
}