use std::io;
use tokio::process::{Child, Command};
#[derive(Debug)]
pub struct EmulatorJob {
job: win32job::Job,
}
impl EmulatorJob {
pub fn spawn(mut cmd: Command) -> io::Result<(Self, Child)> {
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, TH32CS_SNAPTHREAD, THREADENTRY32, Thread32First, Thread32Next,
};
use windows::Win32::System::Threading::{
CREATE_SUSPENDED, OpenProcess, OpenThread, PROCESS_SET_QUOTA, PROCESS_TERMINATE,
ResumeThread, THREAD_SUSPEND_RESUME,
};
let job = win32job::Job::create().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("Failed to create job: {}", e))
})?;
let mut info = job.query_extended_limit_info().map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed to query job info: {}", e),
)
})?;
info.limit_kill_on_job_close();
job.set_extended_limit_info(&mut info).map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed to set job info: {}", e),
)
})?;
cmd.creation_flags(CREATE_SUSPENDED.0);
let child = cmd.spawn()?;
let process_id = child
.id()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Child process has no ID"))?;
unsafe {
let h_process = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, false, process_id)
.map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed to open process: {}", e),
)
})?;
job.assign_process(h_process.0 as _).map_err(|e| {
let _ = CloseHandle(h_process);
io::Error::new(
io::ErrorKind::Other,
format!("Failed to assign process to job: {}", e),
)
})?;
let _ = CloseHandle(h_process);
}
unsafe {
let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0).map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed to create snapshot: {}", e),
)
})?;
let mut thread_entry: THREADENTRY32 = std::mem::zeroed();
thread_entry.dwSize = std::mem::size_of::<THREADENTRY32>() as u32;
let mut thread_id = None;
if Thread32First(snapshot, &mut thread_entry).is_ok() {
loop {
if thread_entry.th32OwnerProcessID == process_id {
thread_id = Some(thread_entry.th32ThreadID);
break;
}
if Thread32Next(snapshot, &mut thread_entry).is_err() {
break;
}
}
}
let _ = CloseHandle(snapshot);
if let Some(tid) = thread_id {
let thread_handle = OpenThread(THREAD_SUSPEND_RESUME, false, tid).map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed to open thread: {}", e),
)
})?;
let _ = ResumeThread(thread_handle);
let _ = CloseHandle(thread_handle);
} else {
return Err(io::Error::new(
io::ErrorKind::Other,
"Could not find main thread to resume",
));
}
}
Ok((Self { job }, child))
}
pub fn kill(&self) -> io::Result<()> {
use windows::Win32::Foundation::HANDLE;
use windows::Win32::System::JobObjects::TerminateJobObject;
let job_handle = self.job.handle() as *mut std::ffi::c_void;
unsafe {
TerminateJobObject(HANDLE(job_handle), 1).map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed to terminate job: {}", e),
)
})?;
}
Ok(())
}
}