use std::os::windows::io::{
AsRawHandle as _, FromRawHandle as _, HandleOrInvalid, HandleOrNull, OwnedHandle, RawHandle,
};
use std::os::windows::process::CommandExt as _;
use std::process::{Child, Command};
use std::time::Duration;
use windows_sys::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, TH32CS_SNAPTHREAD, THREADENTRY32, Thread32First, Thread32Next,
};
use windows_sys::Win32::System::JobObjects::{
AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectBasicAccountingInformation,
JobObjectExtendedLimitInformation, QueryInformationJobObject, SetInformationJobObject, TerminateJobObject,
};
use windows_sys::Win32::System::Threading::{CREATE_SUSPENDED, OpenThread, ResumeThread, THREAD_SUSPEND_RESUME};
pub(crate) fn suspend_new_process(command: &mut Command) {
command.creation_flags(CREATE_SUSPENDED);
}
#[allow(unsafe_code)]
fn owned_from_nullable(raw: RawHandle) -> std::io::Result<OwnedHandle> {
OwnedHandle::try_from(unsafe { HandleOrNull::from_raw_handle(raw) }).map_err(|_| std::io::Error::last_os_error())
}
#[allow(unsafe_code)]
fn owned_from_invalidable(raw: RawHandle) -> std::io::Result<OwnedHandle> {
OwnedHandle::try_from(unsafe { HandleOrInvalid::from_raw_handle(raw) }).map_err(|_| std::io::Error::last_os_error())
}
#[derive(Debug)]
pub(crate) struct WindowsJob(OwnedHandle);
impl WindowsJob {
pub(crate) fn attach(child: &Child) -> std::io::Result<Self> {
#[allow(unsafe_code)]
let job = owned_from_nullable(unsafe { CreateJobObjectW(std::ptr::null(), std::ptr::null()) })?;
let mut limits = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default();
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
#[allow(unsafe_code)]
let configured = unsafe {
SetInformationJobObject(
job.as_raw_handle().cast(),
JobObjectExtendedLimitInformation,
std::ptr::addr_of!(limits).cast(),
u32::try_from(std::mem::size_of_val(&limits)).expect("job limits size fits u32"),
)
};
if configured == 0 {
return Err(std::io::Error::last_os_error());
}
#[allow(unsafe_code)]
if unsafe { AssignProcessToJobObject(job.as_raw_handle().cast(), child.as_raw_handle().cast()) } == 0 {
return Err(std::io::Error::last_os_error());
}
Ok(Self(job))
}
pub(crate) fn terminate(&self) -> std::io::Result<()> {
#[allow(unsafe_code)]
if unsafe { TerminateJobObject(self.0.as_raw_handle().cast(), 1) } == 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(())
}
}
pub(crate) fn wait_empty(&self) -> std::io::Result<()> {
loop {
let mut accounting = JOBOBJECT_BASIC_ACCOUNTING_INFORMATION::default();
#[allow(unsafe_code)]
let queried = unsafe {
QueryInformationJobObject(
self.0.as_raw_handle().cast(),
JobObjectBasicAccountingInformation,
std::ptr::addr_of_mut!(accounting).cast(),
u32::try_from(std::mem::size_of_val(&accounting)).expect("job accounting size fits u32"),
std::ptr::null_mut(),
)
};
if queried == 0 {
return Err(std::io::Error::last_os_error());
}
if accounting.ActiveProcesses == 0 {
return Ok(());
}
std::thread::sleep(Duration::from_millis(10));
}
}
}
pub(crate) fn resume_suspended_process(child: &Child) -> std::io::Result<()> {
#[allow(unsafe_code)]
let snapshot = owned_from_invalidable(unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) })?;
let mut entry = THREADENTRY32 {
dwSize: u32::try_from(std::mem::size_of::<THREADENTRY32>()).expect("thread entry size fits u32"),
..THREADENTRY32::default()
};
let mut resumed = false;
#[allow(unsafe_code)]
let mut present = unsafe { Thread32First(snapshot.as_raw_handle().cast(), &raw mut entry) } != 0;
while present {
if entry.th32OwnerProcessID == child.id() {
#[allow(unsafe_code)]
let thread = owned_from_nullable(unsafe { OpenThread(THREAD_SUSPEND_RESUME, 0, entry.th32ThreadID) })?;
#[allow(unsafe_code)]
if unsafe { ResumeThread(thread.as_raw_handle().cast()) } == u32::MAX {
return Err(std::io::Error::last_os_error());
}
resumed = true;
}
#[allow(unsafe_code)]
{
present = unsafe { Thread32Next(snapshot.as_raw_handle().cast(), &raw mut entry) } != 0;
}
}
if resumed {
Ok(())
} else {
Err(std::io::Error::other("suspended worker primary thread was not found"))
}
}