use std::io; use std::os::windows::io::AsRawHandle as _; use windows::{ Win32::{ Foundation::{CloseHandle, HANDLE}, System::JobObjects::{AssignProcessToJobObject, CreateJobObjectW, TerminateJobObject}, }, core::PCWSTR, }; use thiserror::Error; #[derive(Error, Debug)] #[non_exhaustive] pub enum Error { #[error("Failed to create job: {0}")] Create(io::Error), #[error("Failed to assign job: {0}")] Assign(io::Error), #[error("Failed to terminate job: {0}")] Terminate(io::Error), } impl From for io::Error { fn from(value: Error) -> Self { use Error::*; match value { Create(error) | Assign(error) | Terminate(error) => error, } } } /// Wraps a handle to a job object. /// See also . #[derive(Debug)] #[repr(transparent)] pub struct Job { handle: HANDLE, } impl Job { /// Create a new, empty, job object. /// See also . pub fn new() -> Result { #[allow(non_snake_case)] // To match the naming in the documentation of `CreateJobObjectW` for clarity. let lpJobAttributes = None; #[allow(non_snake_case)] // To match the naming in the documentation of `CreateJobObjectW` for clarity. let lpName = PCWSTR::null(); // SAFETY: We argue that calling `CrateJobObjectW` is safe based on // its documentation (see documentation of this function): // Both parameters take their `null`/empty value. // For `lpJobAttributes`, this is immediate as we use `None`, and the // wrapper in the `windows` crate ensures safety. // For `lpName`, we pass `name`, which was initialized to // `PCWSTR::null()` (see above) which we rely on to obtain a sane // `null` value. // For the case of `null`/empty arguments, the documentation of // `CreateJobObjectW` does not specify any further preconditions. let result = unsafe { CreateJobObjectW(lpJobAttributes, lpName) }; match result { Ok(handle) => Ok(Self { handle }), Err(e) => Err(Error::Create(e.into())), } } /// Assign a process to the job object. /// See also . pub fn assign(&self, child: &std::process::Child) -> Result<(), Error> { #[allow(non_snake_case)] // To match the naming in the documentation of `AssignProcessToJobObject` for clarity. let hJob = self.handle; #[allow(non_snake_case)] // To match the naming in the documentation of `AssignProcessToJobObject` for clarity. let hProcess = HANDLE(child.as_raw_handle()); // SAFETY: We argue that calling `AssignProcessToJobObject` is safe based on // its documentation (see documentation of this function): // First, we argue that the argument // For `hJob`, consider that its value is the same as `self.handle` (see above), // and that `self.handle` is only assigned in `Self::new` via a call to `CreateJobObjectW`. // For `hProcess`, we pass the raw handle of the child process we were given. // Thus, we rely on `impl std::os::windows::io::AsRawHandle for std::process::Child` // for its value to be a valid handle to a process. // Note that the documentation of `AssignProcessToJobObject` specifies further preconditions // on the arguments, especially concerning: // - Job Object Security (see ) // - Process Security and Access Rights (see ) // We assume that violations of these preconditions will be reflected in // the return value of `AssignProcessToJobObject`, and will not result in safety violations. let result = unsafe { AssignProcessToJobObject(hJob, hProcess) }; result.map_err(|e| Error::Assign(e.into())) } /// Terminate a job object. /// See also . pub fn terminate(self, exit_code: u32) -> Result<(), Error> { #[allow(non_snake_case)] // To match the naming in the documentation of `TerminateJobObject` for clarity. let hJob = self.handle; #[allow(non_snake_case)] // To match the naming in the documentation of `TerminateJobObject` for clarity. let uExitCode = exit_code; // SAFETY: We argue that calling `TerminateJobObject` is safe based on // its documentation (see documentation of this function): // For `hJob`, consider that its value is the same as `self.handle`, // and that `self.handle` is only assigned in `Self::new` via a call to `CreateJobObjectW`. // For `uExitCode`, there are no preconditions to satisfy. // Note that the documentation of `TerminateJobObject` specifies further preconditions // on the arguments, especially concerning: // - Job Object Security (see ) // - Process Security and Access Rights (see ) // We assume that violations of these preconditions will be reflected in // the return value of `AssignProcessToJobObject`, and will not result in safety violations. let result = unsafe { TerminateJobObject(hJob, uExitCode) }; result.map_err(|e| Error::Terminate(e.into())) } /// Convenience method to create a new job and assign a child process to it. /// See also [`Job::new`] and [`Job::assign`]. pub fn for_child(child: &std::process::Child) -> Result { let job = Self::new()?; job.assign(child)?; Ok(job) } } impl Drop for Job { /// Close the handle to the job object. /// See also . fn drop(&mut self) { #[allow(non_snake_case)] // To match the naming in the documentation of `CloseHandle` for clarity. let hObject = self.handle; // SAFETY: We argue that calling `CloseHandle` is safe based on // its documentation (see documentation of this function): // For `hObject`, consider that its value is the same as `self.handle`, // and that `self.handle` is only assigned in `Self::new` via a call to `CreateJobObjectW`, // thus is a valid handle to a job object. let _ = unsafe { CloseHandle(hObject) }; } }