use std::{
io,
mem::{size_of, zeroed},
os::windows::{
io::{AsRawHandle, FromRawHandle, OwnedHandle},
process::CommandExt,
},
process::{Child, Command},
};
use windows_sys::Win32::{
Foundation::INVALID_HANDLE_VALUE,
System::{
Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, TH32CS_SNAPTHREAD, THREADENTRY32, Thread32First, Thread32Next,
},
JobObjects::{
AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation,
SetInformationJobObject,
},
Threading::{CREATE_SUSPENDED, OpenThread, ResumeThread, THREAD_SUSPEND_RESUME},
},
};
pub(super) struct Job(OwnedHandle);
impl Job {
fn new() -> 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(unsafe { OwnedHandle::from_raw_handle(handle) });
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { zeroed() };
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if unsafe {
SetInformationJobObject(
job.0.as_raw_handle(),
JobObjectExtendedLimitInformation,
(&info as *const JOBOBJECT_EXTENDED_LIMIT_INFORMATION).cast(),
size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
)
} == 0
{
return Err(io::Error::last_os_error());
}
Ok(job)
}
fn assign_and_resume(&self, child: &Child) -> io::Result<()> {
if unsafe { AssignProcessToJobObject(self.0.as_raw_handle(), child.as_raw_handle()) } == 0 {
return Err(io::Error::last_os_error());
}
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) };
if snapshot == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
let snapshot = unsafe { OwnedHandle::from_raw_handle(snapshot) };
let mut entry: THREADENTRY32 = unsafe { zeroed() };
entry.dwSize = size_of::<THREADENTRY32>() as u32;
let mut found = unsafe { Thread32First(snapshot.as_raw_handle(), &mut entry) };
while found != 0 {
if entry.th32OwnerProcessID == child.id() {
let thread = unsafe { OpenThread(THREAD_SUSPEND_RESUME, 0, entry.th32ThreadID) };
if thread.is_null() {
return Err(io::Error::last_os_error());
}
let thread = unsafe { OwnedHandle::from_raw_handle(thread) };
if unsafe { ResumeThread(thread.as_raw_handle()) } == u32::MAX {
return Err(io::Error::last_os_error());
}
return Ok(());
}
found = unsafe { Thread32Next(snapshot.as_raw_handle(), &mut entry) };
}
Err(io::Error::new(io::ErrorKind::NotFound, "Git child thread was not found"))
}
}
pub(super) fn spawn(cmd: &mut Command, bounded: bool) -> io::Result<(Child, Option<Job>)> {
if !bounded {
return cmd.spawn().map(|child| (child, None));
}
let job = Job::new()?;
cmd.creation_flags(CREATE_SUSPENDED);
let mut child = cmd.spawn()?;
if let Err(error) = job.assign_and_resume(&child) {
drop(job);
let _ = child.kill();
let _ = child.wait();
return Err(error);
}
Ok((child, Some(job)))
}