use std::ffi::c_void;
use std::io;
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
use std::time::Instant;
use super::{Opened, millis_until};
use crate::local::ProcessState;
type Handle = *mut c_void;
#[link(name = "kernel32")]
unsafe extern "system" {
fn OpenProcess(access: u32, inherit: i32, pid: u32) -> Handle;
fn WaitForSingleObject(handle: Handle, milliseconds: u32) -> u32;
#[cfg(feature = "async")]
fn RegisterWaitForSingleObject(
wait: *mut Handle,
object: Handle,
callback: Option<unsafe extern "system" fn(*mut c_void, u8)>,
context: *const c_void,
milliseconds: u32,
flags: u32,
) -> i32;
#[cfg(feature = "async")]
fn UnregisterWaitEx(wait: Handle, completion: Handle) -> i32;
}
const SYNCHRONIZE: u32 = 0x0010_0000;
const ERROR_INVALID_PARAMETER: i32 = 87;
const INFINITE: u32 = 0xFFFF_FFFF;
const WAIT_OBJECT_0: u32 = 0;
const WAIT_TIMEOUT: u32 = 258;
#[derive(Debug)]
pub(super) struct Event {
process: OwnedHandle,
}
pub(super) fn open(pid: u32) -> io::Result<Opened> {
let process = unsafe { OpenProcess(SYNCHRONIZE, 0, pid) };
if process.is_null() {
return opened_after(io::Error::last_os_error());
}
let process = unsafe { OwnedHandle::from_raw_handle(process) };
Ok(Opened::Watching(Event { process }))
}
fn opened_after(error: io::Error) -> io::Result<Opened> {
if error.raw_os_error() == Some(ERROR_INVALID_PARAMETER) {
Ok(Opened::Gone)
} else {
Err(error)
}
}
pub(super) fn unknown_state(_pid: u32) -> ProcessState {
ProcessState::Unknown
}
impl Event {
pub(super) fn wait(&mut self, deadline: Option<Instant>) -> io::Result<bool> {
loop {
let milliseconds = match deadline {
None => INFINITE,
Some(deadline) => u32::try_from(millis_until(deadline, Instant::now()))
.unwrap_or(INFINITE)
.min(INFINITE - 1),
};
match unsafe { WaitForSingleObject(self.process.as_raw_handle(), milliseconds) } {
WAIT_OBJECT_0 => return Ok(true),
WAIT_TIMEOUT if deadline.is_none_or(|deadline| Instant::now() >= deadline) => {
return Ok(false);
}
WAIT_TIMEOUT => {}
_ => return Err(io::Error::last_os_error()),
}
}
}
#[cfg(feature = "async")]
pub(super) async fn exited(&mut self) -> io::Result<()> {
use std::sync::Arc;
if self.wait(Some(Instant::now()))? {
return Ok(());
}
let exited = Arc::new(tokio::sync::Notify::new());
let _registration = Registration::new(&self.process, &exited)?;
exited.notified().await;
Ok(())
}
}
#[cfg(feature = "async")]
struct Registration {
wait: Handle,
context: *const tokio::sync::Notify,
}
#[cfg(feature = "async")]
unsafe impl Send for Registration {}
#[cfg(feature = "async")]
impl Registration {
fn new(
process: &OwnedHandle,
exited: &std::sync::Arc<tokio::sync::Notify>,
) -> io::Result<Self> {
const WT_EXECUTEONLYONCE: u32 = 0x0000_0008;
let context = std::sync::Arc::into_raw(std::sync::Arc::clone(exited));
let mut wait: Handle = std::ptr::null_mut();
let registered = unsafe {
RegisterWaitForSingleObject(
&mut wait,
process.as_raw_handle(),
Some(signal),
context.cast(),
INFINITE,
WT_EXECUTEONLYONCE,
)
};
if registered == 0 {
let error = io::Error::last_os_error();
drop(unsafe { std::sync::Arc::from_raw(context) });
return Err(error);
}
Ok(Self { wait, context })
}
}
#[cfg(feature = "async")]
impl Drop for Registration {
fn drop(&mut self) {
const INVALID_HANDLE_VALUE: Handle = -1isize as Handle;
let _ = unsafe { UnregisterWaitEx(self.wait, INVALID_HANDLE_VALUE) };
drop(unsafe { std::sync::Arc::from_raw(self.context) });
}
}
#[cfg(feature = "async")]
unsafe extern "system" fn signal(context: *mut c_void, _timed_out: u8) {
let exited = unsafe { &*context.cast::<tokio::sync::Notify>() };
exited.notify_one();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_a_missing_pid_is_gone() {
let from = |code| opened_after(io::Error::from_raw_os_error(code));
assert!(matches!(from(87), Ok(Opened::Gone)));
let error = from(5).err().unwrap();
assert_eq!(error.raw_os_error(), Some(5));
assert_eq!(unknown_state(1), ProcessState::Unknown);
}
}