process-wrap 10.0.0

Wrap a Command, to spawn processes in a group or session or job etc
Documentation
use std::io::{Error, Result};

use windows::{
	Win32::{
		Foundation::{CloseHandle, ERROR_NO_MORE_FILES, HANDLE},
		System::{
			Diagnostics::ToolHelp::{
				CreateToolhelp32Snapshot, TH32CS_SNAPTHREAD, THREADENTRY32, Thread32First,
				Thread32Next,
			},
			Threading::{OpenThread, ResumeThread, SuspendThread, THREAD_SUSPEND_RESUME},
		},
	},
	core::HRESULT,
};

struct OwnedHandle(HANDLE);

impl Drop for OwnedHandle {
	fn drop(&mut self) {
		unsafe { CloseHandle(self.0) }.ok();
	}
}

pub fn process_has_suspended_thread(pid: u32) -> Result<bool> {
	let snapshot = OwnedHandle(unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) }?);
	let mut entry = THREADENTRY32 {
		dwSize: std::mem::size_of::<THREADENTRY32>()
			.try_into()
			.expect("THREADENTRY32 is guaranteed to fit in a DWORD"),
		..Default::default()
	};
	unsafe { Thread32First(snapshot.0, &mut entry) }.map_err(Error::other)?;

	let mut found = false;
	let mut suspended = false;
	loop {
		if entry.th32OwnerProcessID == pid {
			found = true;
			let thread = OwnedHandle(unsafe {
				OpenThread(THREAD_SUSPEND_RESUME, false, entry.th32ThreadID)
			}?);
			let previous_count = unsafe { SuspendThread(thread.0) };
			if previous_count == u32::MAX {
				return Err(Error::last_os_error());
			}
			let balanced_count = unsafe { ResumeThread(thread.0) };
			if balanced_count == u32::MAX {
				return Err(Error::last_os_error());
			}
			if balanced_count != previous_count + 1 {
				return Err(Error::other("thread suspension probe was not balanced"));
			}
			suspended |= previous_count > 0;
		}

		match unsafe { Thread32Next(snapshot.0, &mut entry) } {
			Ok(()) => {}
			Err(error) if error.code() == HRESULT::from_win32(ERROR_NO_MORE_FILES.0) => break,
			Err(error) => return Err(Error::other(error)),
		}
	}

	if found {
		Ok(suspended)
	} else {
		Err(Error::other("no thread belonging to the child was found"))
	}
}