Files
tauri-plugins-workspace/plugins/shell/src/process/job_object.rs
T
2026-10-09 15:08:52 -03:00

91 lines
3.2 KiB
Rust

// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! Job object backing `process_group` on Windows, adapted from
//! [process-wrap](https://github.com/watchexec/process-wrap) (Apache-2.0 OR MIT).
use std::{io, mem, os::windows::io::AsRawHandle, process::Child, ptr};
use windows_sys::Win32::{
Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE},
System::{
Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, TH32CS_SNAPTHREAD, THREADENTRY32, Thread32First, Thread32Next,
},
JobObjects::{AssignProcessToJobObject, CreateJobObjectW, TerminateJobObject},
Threading::{OpenThread, ResumeThread, THREAD_SUSPEND_RESUME},
},
};
/// Owned job object handle. Closing it leaves the processes in the job running.
#[derive(Debug)]
pub(crate) struct JobObject(HANDLE);
// SAFETY: the handle is only ever passed to thread-safe Win32 calls.
unsafe impl Send for JobObject {}
unsafe impl Sync for JobObject {}
impl JobObject {
/// Creates a job object, assigns the `CREATE_SUSPENDED` `child` to it and resumes the child.
pub(crate) fn assign(child: &Child) -> io::Result<Self> {
let job = unsafe { CreateJobObjectW(ptr::null(), ptr::null()) };
if job.is_null() {
return Err(io::Error::last_os_error());
}
let job = Self(job);
if unsafe { AssignProcessToJobObject(job.0, child.as_raw_handle()) } == 0 {
return Err(io::Error::last_os_error());
}
resume_threads(child.id())?;
Ok(job)
}
pub(crate) fn terminate(&self) -> io::Result<()> {
if unsafe { TerminateJobObject(self.0, 1) } == 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
}
impl Drop for JobObject {
fn drop(&mut self) {
unsafe { CloseHandle(self.0) };
}
}
/// `std` closes the main thread handle right after `CreateProcess`, so a suspended
/// child can only be resumed by looking its threads up again.
fn resume_threads(pid: u32) -> io::Result<()> {
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) };
if snapshot == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
let result = resume_snapshot_threads(snapshot, pid);
unsafe { CloseHandle(snapshot) };
result
}
fn resume_snapshot_threads(snapshot: HANDLE, pid: u32) -> io::Result<()> {
let mut entry: THREADENTRY32 = unsafe { mem::zeroed() };
entry.dwSize = mem::size_of::<THREADENTRY32>() as u32;
let mut found = unsafe { Thread32First(snapshot, &mut entry) } != 0;
while found {
if entry.th32OwnerProcessID == pid {
let thread = unsafe { OpenThread(THREAD_SUSPEND_RESUME, 0, entry.th32ThreadID) };
if thread.is_null() {
return Err(io::Error::last_os_error());
}
if unsafe { ResumeThread(thread) } == u32::MAX {
let err = io::Error::last_os_error();
unsafe { CloseHandle(thread) };
return Err(err);
}
unsafe { CloseHandle(thread) };
}
found = unsafe { Thread32Next(snapshot, &mut entry) } != 0;
}
Ok(())
}