//! Windows implementation of the platform primitives.
//!
//! Process liveness/termination use the Win32 process APIs; the graceful
//! "stop a runner" path has no SIGTERM analogue, so it is modelled as a
//! named manual-reset event that the runner waits on (see [`wait_stop_event`])
//! and `runner stop` signals (see [`request_graceful_stop`]). The event
//! name is derived from the runner's PID-file path so both sides agree
//! without threading the instance name through every layer.
use std::ffi::c_void;
use std::hash::{Hash, Hasher};
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use windows_sys::Win32::Foundation::{CloseHandle, HANDLE, WAIT_OBJECT_0};
use windows_sys::Win32::Security::{
GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY,
};
use windows_sys::Win32::System::Threading::{
CreateEventW, GetCurrentProcess, GetExitCodeProcess, OpenEventW, OpenProcess, OpenProcessToken,
SetEvent, TerminateProcess, WaitForSingleObject, INFINITE, PROCESS_QUERY_LIMITED_INFORMATION,
PROCESS_TERMINATE,
};
/// A process that is still running reports this sentinel exit code.
const STILL_ACTIVE: u32 = 259;
/// `OpenEventW` access right to signal an event.
const EVENT_MODIFY_STATE: u32 = 0x0002;
/// Encode a Rust string as a NUL-terminated UTF-16 buffer for `*W` APIs.
fn wide(s: &str) -> Vec<u16> {
std::ffi::OsStr::new(s)
.encode_wide()
.chain(std::iter::once(0))
.collect()
}
/// Test whether `pid` names a live process. Mirrors the Unix semantics:
/// an access-denied open (process exists but we can't query it) counts as
/// alive, matching the Unix EPERM case.
pub fn is_alive(pid: i32) -> bool {
if pid <= 0 {
return false;
}
unsafe {
let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid as u32);
if handle.is_null() {
// Couldn't open: ERROR_ACCESS_DENIED (5) means it exists but is
// protected; anything else (typically ERROR_INVALID_PARAMETER)
// means it's gone.
let err = windows_sys::Win32::Foundation::GetLastError();
return err == 5; // ERROR_ACCESS_DENIED
}
let mut code: u32 = 0;
let ok = GetExitCodeProcess(handle, &mut code);
CloseHandle(handle);
ok != 0 && code == STILL_ACTIVE
}
}
/// True when the current process holds an elevated (admin) token.
pub fn is_elevated() -> bool {
unsafe {
let mut token: HANDLE = std::ptr::null_mut();
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) == 0 {
return false;
}
let mut elevation = TOKEN_ELEVATION { TokenIsElevated: 0 };
let mut ret_len: u32 = 0;
let size = std::mem::size_of::<TOKEN_ELEVATION>() as u32;
let ok = GetTokenInformation(
token,
TokenElevation,
&mut elevation as *mut _ as *mut c_void,
size,
&mut ret_len,
);
CloseHandle(token);
ok != 0 && elevation.TokenIsElevated != 0
}
}
/// Forcibly terminate `pid`.
pub fn force_kill(pid: i32) -> std::io::Result<()> {
if pid <= 0 {
return Ok(());
}
unsafe {
let handle = OpenProcess(PROCESS_TERMINATE, 0, pid as u32);
if handle.is_null() {
return Err(std::io::Error::last_os_error());
}
let ok = TerminateProcess(handle, 1);
CloseHandle(handle);
if ok == 0 {
return Err(std::io::Error::last_os_error());
}
}
Ok(())
}
/// Ask a runner process to shut down gracefully by signalling the named
/// stop-event it waits on. `pid` is unused on Windows (the event, not the
/// PID, is the channel). Returns an error if no runner is listening on the
/// event, so the caller can fall back to force-kill.
pub fn request_graceful_stop(_pid: i32, pid_path: &Path) -> std::io::Result<()> {
let name = wide(&stop_event_name(pid_path));
unsafe {
let handle = OpenEventW(EVENT_MODIFY_STATE, 0, name.as_ptr());
if handle.is_null() {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"runner is not listening for a stop event",
));
}
let ok = SetEvent(handle);
CloseHandle(handle);
if ok == 0 {
return Err(std::io::Error::last_os_error());
}
}
Ok(())
}
/// No graceful per-child signal on Windows; the executor escalates to
/// `child.kill()` after its grace window.
pub fn request_child_terminate(_pid: u32) {}
/// On Windows, run arbitrary command strings through PowerShell.
pub fn bare_shell() -> (&'static str, Vec<&'static str>) {
(
"powershell",
vec!["-NoProfile", "-NonInteractive", "-Command"],
)
}
/// Deterministic name of the stop-event for a given PID-file path. Both the
/// runner (which creates+waits) and `runner stop` (which opens+signals)
/// derive it from the same path, so they rendezvous without sharing the
/// instance name. Normalised to lowercase so trivial path-casing
/// differences don't desync the two sides.
pub fn stop_event_name(pid_path: &Path) -> String {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
pid_path.to_string_lossy().to_lowercase().hash(&mut hasher);
// `Local\` namespace keeps it per-session, which is correct: a runner
// and its `stop` invocation share a session (or the service session).
format!("Local\\anvil-runner-stop-{:016x}", hasher.finish())
}
/// Create the named stop-event and block until it is signalled. Intended to
/// be driven from a blocking task; resolves once `request_graceful_stop`
/// (or anything else) sets the event.
pub fn wait_stop_event_blocking(pid_path: &Path) {
let name = wide(&stop_event_name(pid_path));
unsafe {
// Manual-reset, initially non-signalled. Creating it (vs opening)
// guarantees the named object exists for `stop` to open even if the
// runner reaches here first.
let handle = CreateEventW(std::ptr::null(), 1, 0, name.as_ptr());
if handle.is_null() {
// Can't create the event — degrade to never resolving; ctrl_c
// and (for services) the SCM stop path still work.
return;
}
WaitForSingleObject(handle, INFINITE);
let _ = WAIT_OBJECT_0; // documents the expected return; we wait INFINITE
CloseHandle(handle);
}
}