#![allow(dead_code, clippy::upper_case_acronyms)]
#[allow(non_snake_case, non_camel_case_types)]
mod win {
pub type DWORD = u32;
pub type HANDLE = *mut core::ffi::c_void;
pub const INVALID_HANDLE_VALUE: HANDLE = -1_isize as HANDLE;
pub const TH32CS_SNAPTHREAD: DWORD = 0x0000_0004;
pub const THREAD_QUERY_LIMITED_INFORMATION: DWORD = 0x0800;
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct THREADENTRY32 {
pub dwSize: DWORD,
pub cntUsage: DWORD,
pub th32ThreadID: DWORD,
pub th32OwnerProcessID: DWORD,
pub tpBasePri: i32,
pub tpDeltaPri: i32,
pub dwFlags: DWORD,
}
unsafe extern "system" {
pub fn CreateToolhelp32Snapshot(dwFlags: DWORD, th32ProcessID: DWORD) -> HANDLE;
pub fn Thread32First(hSnapshot: HANDLE, lpte: *mut THREADENTRY32) -> i32;
pub fn Thread32Next(hSnapshot: HANDLE, lpte: *mut THREADENTRY32) -> i32;
pub fn CloseHandle(hObject: HANDLE) -> i32;
pub fn GetCurrentProcessId() -> DWORD;
pub fn OpenThread(dwDesiredAccess: DWORD, bInheritHandle: i32, dwThreadId: DWORD)
-> HANDLE;
pub fn GetThreadDescription(hThread: HANDLE, ppszThreadDescription: *mut *mut u16) -> i32;
pub fn LocalFree(hMem: *mut core::ffi::c_void) -> *mut core::ffi::c_void;
}
}
struct Handle(win::HANDLE);
impl Drop for Handle {
fn drop(&mut self) {
unsafe {
win::CloseHandle(self.0);
}
}
}
fn thread_description(id: win::DWORD) -> Option<String> {
let raw = unsafe { win::OpenThread(win::THREAD_QUERY_LIMITED_INFORMATION, 0, id) };
if raw.is_null() || raw == win::INVALID_HANDLE_VALUE {
return None;
}
let thread = Handle(raw);
let mut text: *mut u16 = core::ptr::null_mut();
let result = unsafe { win::GetThreadDescription(thread.0, &raw mut text) };
if result < 0 || text.is_null() {
return None;
}
let mut length = 0_usize;
while unsafe { *text.add(length) } != 0 {
length = length.saturating_add(1);
}
let slice = unsafe { core::slice::from_raw_parts(text, length) };
let name = String::from_utf16_lossy(slice);
unsafe {
win::LocalFree(text.cast());
}
Some(name)
}
pub fn all_thread_ids() -> std::io::Result<std::collections::BTreeSet<u64>> {
thread_ids(false)
}
pub fn named_thread_ids() -> std::io::Result<std::collections::BTreeSet<u64>> {
thread_ids(true)
}
fn thread_ids(named_only: bool) -> std::io::Result<std::collections::BTreeSet<u64>> {
let raw = unsafe { win::CreateToolhelp32Snapshot(win::TH32CS_SNAPTHREAD, 0) };
if raw == win::INVALID_HANDLE_VALUE || raw.is_null() {
return Err(std::io::Error::last_os_error());
}
let snapshot = Handle(raw);
let process = unsafe { win::GetCurrentProcessId() };
let entry_size = u32::try_from(size_of::<win::THREADENTRY32>())
.map_err(|_| std::io::Error::other("THREADENTRY32 exceeds DWORD"))?;
let mut entry = win::THREADENTRY32 {
dwSize: entry_size,
..Default::default()
};
let mut ids = std::collections::BTreeSet::new();
let mut more = unsafe { win::Thread32First(snapshot.0, &raw mut entry) } != 0;
while more {
if entry.th32OwnerProcessID == process {
let keep = !named_only
|| thread_description(entry.th32ThreadID).is_some_and(|name| {
name.starts_with("ze-lifecycle-") || name.starts_with("ze-query-")
});
if keep {
ids.insert(u64::from(entry.th32ThreadID));
}
}
entry.dwSize = entry_size;
more = unsafe { win::Thread32Next(snapshot.0, &raw mut entry) } != 0;
}
Ok(ids)
}