#![allow(dead_code)]
pub fn platform_supports_job_objects() -> bool {
cfg!(windows)
}
pub fn capability_summary() -> &'static str {
if platform_supports_job_objects() {
"windows_job_object:available (KILL_ON_JOB_CLOSE)"
} else {
"windows_job_object:stub (non-windows host)"
}
}
#[cfg(windows)]
pub fn create_and_assign(pid: u32) -> usize {
use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
use windows_sys::Win32::System::Threading::{
AssignProcessToJobObject, CreateJobObjectW, JobObjectExtendedLimitInformation, OpenProcess,
SetInformationJobObject, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, PROCESS_SET_QUOTA, PROCESS_TERMINATE,
};
if pid == 0 {
return 0;
}
unsafe {
let job: HANDLE = CreateJobObjectW(std::ptr::null(), std::ptr::null());
if job.is_null() {
return 0;
}
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = std::mem::zeroed();
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
let ok = SetInformationJobObject(
job,
JobObjectExtendedLimitInformation,
&info as *const _ as *const _,
std::mem::size_of_val(&info) as u32,
);
if ok == 0 {
let _ = CloseHandle(job);
return 0;
}
let proc: HANDLE = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, 0, pid);
if proc.is_null() {
let _ = CloseHandle(job);
return 0;
}
let assigned = AssignProcessToJobObject(job, proc);
let _ = CloseHandle(proc);
if assigned == 0 {
let _ = CloseHandle(job);
return 0;
}
job as usize
}
}
#[cfg(windows)]
pub fn terminate_job(handle: usize) {
use windows_sys::Win32::System::Threading::TerminateJobObject;
if handle == 0 {
return;
}
unsafe {
let _ = TerminateJobObject(handle as _, 1);
}
}
#[cfg(windows)]
pub fn close_job(handle: usize) {
use windows_sys::Win32::Foundation::CloseHandle;
if handle == 0 {
return;
}
unsafe {
let _ = CloseHandle(handle as _);
}
}
#[cfg(windows)]
pub fn terminate_pid(pid: u32) {
use windows_sys::Win32::Foundation::CloseHandle;
use windows_sys::Win32::System::Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE};
if pid == 0 {
return;
}
unsafe {
let proc = OpenProcess(PROCESS_TERMINATE, 0, pid);
if !proc.is_null() {
let _ = TerminateProcess(proc, 1);
let _ = CloseHandle(proc);
}
}
}
#[cfg(windows)]
pub fn validate_assign_current_process() -> Result<usize, String> {
use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
use windows_sys::Win32::System::Threading::{
AssignProcessToJobObject, CreateJobObjectW, GetCurrentProcess, GetCurrentProcessId,
OpenProcess, PROCESS_SET_QUOTA, PROCESS_TERMINATE,
};
let pid = unsafe { GetCurrentProcessId() };
unsafe {
let job: HANDLE = CreateJobObjectW(std::ptr::null(), std::ptr::null());
if job.is_null() {
return Err("CreateJobObjectW failed".into());
}
let proc: HANDLE = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, 0, pid);
if proc.is_null() {
let cur = GetCurrentProcess();
let assigned = AssignProcessToJobObject(job, cur);
if assigned == 0 {
let _ = CloseHandle(job);
return Err("AssignProcessToJobObject(current) failed".into());
}
return Ok(job as usize);
}
let assigned = AssignProcessToJobObject(job, proc);
let _ = CloseHandle(proc);
if assigned == 0 {
let _ = CloseHandle(job);
return Err("AssignProcessToJobObject failed".into());
}
Ok(job as usize)
}
}
#[cfg(not(windows))]
pub fn create_and_assign(_pid: u32) -> usize {
0
}
#[cfg(not(windows))]
pub fn terminate_job(_handle: usize) {}
#[cfg(not(windows))]
pub fn close_job(_handle: usize) {}
#[cfg(not(windows))]
pub fn terminate_pid(_pid: u32) {}
#[cfg(not(windows))]
pub fn validate_assign_current_process() -> Result<usize, String> {
Err("job objects unavailable on non-windows".into())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capability_summary_is_non_empty() {
assert!(!capability_summary().is_empty());
}
#[test]
fn stub_or_create_does_not_panic() {
let h = create_and_assign(0);
assert_eq!(h, 0, "pid 0 must never produce a job handle");
terminate_job(0);
close_job(0);
terminate_pid(0);
}
#[test]
fn platform_flag_matches_cfg() {
assert_eq!(platform_supports_job_objects(), cfg!(windows));
}
#[test]
fn validate_current_process_behavior() {
match validate_assign_current_process() {
Ok(h) => {
assert_ne!(h, 0);
close_job(h);
}
Err(e) => {
#[cfg(windows)]
assert!(!e.is_empty());
#[cfg(not(windows))]
assert!(e.contains("non-windows"));
}
}
}
}