use std::ffi::c_void;
use std::mem::size_of;
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
use std::ptr::null_mut;
use anyhow::{bail, Result};
pub type RawHandle = *mut c_void;
type Handle = RawHandle;
type Dword = u32;
const JOB_OBJECT_EXTENDED_LIMIT_INFORMATION: Dword = 9;
pub const JOB_OBJECT_IO_RATE_CONTROL_INFORMATION: Dword = 37;
const JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE: Dword = 0x0000_2000;
const JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION: Dword = 0x0000_0400;
const INVALID_HANDLE_VALUE: Handle = -1isize as Handle;
#[repr(C)]
#[derive(Clone, Copy, Default, Debug)]
pub struct JobObjectIoRateControlInformation {
pub max_iops: i64,
pub max_bandwidth: i64,
pub reservation_iops: i64,
pub volume_name: *const u16,
pub base_io_size: u32,
pub control_flags: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
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, Default)]
struct BasicLimitInformation {
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, Default)]
struct ExtendedLimitInformation {
basic_limit_information: BasicLimitInformation,
io_info: IoCounters,
process_memory_limit: usize,
job_memory_limit: usize,
peak_process_memory_used: usize,
peak_job_memory_used: usize,
}
#[link(name = "kernel32")]
extern "system" {
fn CreateJobObjectW(attributes: *mut c_void, name: *const u16) -> Handle;
fn SetInformationJobObject(
job: Handle,
information_class: Dword,
information: *const c_void,
information_length: Dword,
) -> i32;
fn AssignProcessToJobObject(job: Handle, process: Handle) -> i32;
fn GetLastError() -> Dword;
}
pub struct JobObject {
handle: OwnedHandle,
}
impl std::fmt::Debug for JobObject {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("JobObject")
.field("handle", &self.handle.as_raw_handle())
.finish()
}
}
impl JobObject {
pub fn new_kill_on_close() -> Result<Self> {
let raw = unsafe { CreateJobObjectW(null_mut(), std::ptr::null()) };
if raw.is_null() || raw == INVALID_HANDLE_VALUE {
bail!("CreateJobObjectW failed with {}", unsafe { GetLastError() });
}
let mut limits = ExtendedLimitInformation::default();
limits.basic_limit_information.limit_flags =
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION;
let ok = unsafe {
SetInformationJobObject(
raw,
JOB_OBJECT_EXTENDED_LIMIT_INFORMATION,
(&limits as *const ExtendedLimitInformation).cast(),
size_of::<ExtendedLimitInformation>() as Dword,
)
};
if ok == 0 {
drop(unsafe { OwnedHandle::from_raw_handle(raw.cast()) });
bail!(
"SetInformationJobObject(KILL_ON_CLOSE) failed with {}",
unsafe { GetLastError() }
);
}
let handle = unsafe { OwnedHandle::from_raw_handle(raw.cast()) };
Ok(Self { handle })
}
pub unsafe fn assign_process(&self, process: RawHandle) -> Result<()> {
if process.is_null() || process == INVALID_HANDLE_VALUE {
bail!("cannot assign an invalid process handle to a Job Object");
}
let ok = unsafe { AssignProcessToJobObject(self.handle.as_raw_handle().cast(), process) };
if ok == 0 {
bail!("AssignProcessToJobObject failed with {}", unsafe {
GetLastError()
});
}
Ok(())
}
pub fn set_io_rate_limits(
&self,
max_iops: Option<u64>,
max_bandwidth_bytes: Option<u64>,
) -> Result<bool> {
if max_iops.is_none() && max_bandwidth_bytes.is_none() {
return Ok(true);
}
let info = JobObjectIoRateControlInformation {
max_iops: max_iops.map(|v| v as i64).unwrap_or(0),
max_bandwidth: max_bandwidth_bytes.map(|v| v as i64).unwrap_or(0),
reservation_iops: 0,
volume_name: std::ptr::null(),
base_io_size: 0,
control_flags: 0,
};
let ok = unsafe {
SetInformationJobObject(
self.handle.as_raw_handle().cast(),
JOB_OBJECT_IO_RATE_CONTROL_INFORMATION,
(&info as *const JobObjectIoRateControlInformation).cast(),
size_of::<JobObjectIoRateControlInformation>() as Dword,
)
};
if ok == 0 {
Ok(false)
} else {
Ok(true)
}
}
pub fn raw_handle(&self) -> RawHandle {
self.handle.as_raw_handle().cast()
}
}
pub const fn kill_contract() -> &'static str {
"JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | NO_BREAKAWAY"
}