use core::ffi::c_void;
use windows::Win32::Foundation::{CloseHandle, HANDLE};
use windows::Win32::System::JobObjects::{
AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
JobObjectExtendedLimitInformation, QueryInformationJobObject, SetInformationJobObject,
};
#[derive(Debug, Clone, Copy)]
pub enum JobError {
Create(i32),
Query(i32),
Set(i32),
Assign(i32),
}
impl JobError {
#[must_use]
pub const fn code(&self) -> i32 {
match *self {
Self::Create(code) | Self::Query(code) | Self::Set(code) | Self::Assign(code) => code,
}
}
#[must_use]
pub const fn message(&self) -> &'static str {
match self {
Self::Create(_) => "failed to create job object",
Self::Query(_) => "failed to query job object information",
Self::Set(_) => "failed to set job object information",
Self::Assign(_) => "failed to assign process to job object",
}
}
}
#[cfg(feature = "std")]
impl std::fmt::Display for JobError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} (os error {})", self.message(), self.code())
}
}
#[cfg(feature = "std")]
impl std::error::Error for JobError {}
pub struct Job {
handle: HANDLE,
}
impl Job {
#[allow(unsafe_code)]
pub fn new() -> Result<Self, JobError> {
let handle =
unsafe { CreateJobObjectW(None, None) }.map_err(|e| JobError::Create(e.code().0))?;
let job = Self { handle };
job.configure_limits()?;
Ok(job)
}
#[allow(unsafe_code)]
pub unsafe fn assign_process(&self, process_handle: HANDLE) -> Result<(), JobError> {
unsafe { AssignProcessToJobObject(self.handle, process_handle) }
.map_err(|e| JobError::Assign(e.code().0))
}
#[must_use]
pub const fn as_raw_handle(&self) -> HANDLE {
self.handle
}
#[allow(unsafe_code)]
fn configure_limits(&self) -> Result<(), JobError> {
let mut info = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default();
let info_size = u32::try_from(size_of_val(&info)).expect("job info size fits in u32");
unsafe {
QueryInformationJobObject(
Some(self.handle),
JobObjectExtendedLimitInformation,
(&raw mut info).cast::<c_void>(),
info_size,
None,
)
}
.map_err(|e| JobError::Query(e.code().0))?;
info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
unsafe {
SetInformationJobObject(
self.handle,
JobObjectExtendedLimitInformation,
(&raw const info).cast::<c_void>(),
info_size,
)
}
.map_err(|e| JobError::Set(e.code().0))
}
}
impl Drop for Job {
#[allow(unsafe_code)]
fn drop(&mut self) {
let _ = unsafe { CloseHandle(self.handle) };
}
}