use std::io;
use windows_sys::Win32::Foundation::{CloseHandle, FALSE, HANDLE, TRUE};
use windows_sys::Win32::System::Console::{CTRL_BREAK_EVENT, CTRL_C_EVENT, SetConsoleCtrlHandler};
use windows_sys::Win32::System::JobObjects::{
AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation,
SetInformationJobObject,
};
#[derive(Debug)]
pub struct Job {
handle: HANDLE,
}
unsafe impl Send for Job {}
impl Job {
pub fn create_kill_on_close() -> io::Result<Self> {
let handle = unsafe { CreateJobObjectW(std::ptr::null(), std::ptr::null()) };
if handle.is_null() {
return Err(io::Error::last_os_error());
}
let job = Self { handle };
let mut limits: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { std::mem::zeroed() };
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
let size = u32::try_from(size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>())
.map_err(|_| io::Error::other("job object limit structure is implausibly large"))?;
let ok = unsafe {
SetInformationJobObject(
job.handle,
JobObjectExtendedLimitInformation,
(&raw const limits).cast(),
size,
)
};
if ok == FALSE {
return Err(io::Error::last_os_error());
}
Ok(job)
}
pub unsafe fn assign(&self, process: HANDLE) -> io::Result<()> {
let ok = unsafe { AssignProcessToJobObject(self.handle, process) };
if ok == FALSE {
return Err(io::Error::last_os_error());
}
Ok(())
}
}
impl Drop for Job {
fn drop(&mut self) {
unsafe {
CloseHandle(self.handle);
}
}
}
pub fn install_console_ctrl_handler() -> io::Result<()> {
use std::sync::OnceLock;
static INSTALLED: OnceLock<Result<(), i32>> = OnceLock::new();
let outcome = INSTALLED.get_or_init(|| {
let ok = unsafe { SetConsoleCtrlHandler(Some(handler), TRUE) };
if ok == FALSE {
Err(io::Error::last_os_error().raw_os_error().unwrap_or(0))
} else {
Ok(())
}
});
outcome.map_err(io::Error::from_raw_os_error)
}
extern "system" fn handler(event: u32) -> windows_sys::core::BOOL {
match event {
CTRL_C_EVENT | CTRL_BREAK_EVENT => TRUE,
_ => FALSE,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_job_can_be_created_and_dropped() {
let job = Job::create_kill_on_close().expect("creating a job object must succeed");
drop(job);
}
#[test]
fn the_handler_claims_interrupts_and_declines_shutdown() {
use windows_sys::Win32::System::Console::{
CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT,
};
assert_eq!(handler(CTRL_C_EVENT), TRUE, "Ctrl-C must not kill prk before the child");
assert_eq!(handler(CTRL_BREAK_EVENT), TRUE);
for event in [CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT] {
assert_eq!(handler(event), FALSE, "event {event} must fall through to the default");
}
}
#[test]
fn installing_the_handler_is_idempotent() {
install_console_ctrl_handler().expect("first install");
install_console_ctrl_handler().expect("second install must be a no-op");
}
}