use crate::core::error::{AppError, Result};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::time::Instant;
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct Identity {
process_id: u32,
created: u64,
image: PathBuf,
}
impl Identity {
pub fn process_id(&self) -> u32 {
self.process_id
}
pub fn created(&self) -> u64 {
self.created
}
pub fn image(&self) -> &Path {
&self.image
}
fn validate(&self) -> Result<()> {
if self.process_id == 0
|| self.created == 0
|| !self.image.is_absolute()
|| self
.image
.to_str()
.is_none_or(|s| s.chars().any(char::is_control))
{
return Err(changed());
}
Ok(())
}
}
pub struct Pinned {
identity: Identity,
#[cfg(windows)]
handle: winsafe::guard::CloseHandleGuard<winsafe::HPROCESS>,
}
impl Pinned {
#[cfg(windows)]
pub(crate) fn native_handle(&self) -> &winsafe::HPROCESS {
&self.handle
}
#[cfg(windows)]
pub(crate) fn from_handle(
handle: winsafe::guard::CloseHandleGuard<winsafe::HPROCESS>,
deadline: Instant,
) -> Result<Self> {
let identity = snapshot(&handle, deadline)?;
Ok(Self { identity, handle })
}
pub fn observe(process_id: u32, deadline: Instant) -> Result<Self> {
crate::core::deadline::check(deadline)?;
if process_id == 0 {
return Err(AppError::invalid());
}
#[cfg(windows)]
{
let handle = winsafe::HPROCESS::OpenProcess(
winsafe::co::PROCESS::QUERY_LIMITED_INFORMATION | winsafe::co::PROCESS::SYNCHRONIZE,
false,
process_id,
)
.map_err(|_| unavailable())?;
let identity = snapshot(&handle, deadline)?;
if identity.process_id != process_id {
return Err(changed());
}
Ok(Self { identity, handle })
}
#[cfg(not(windows))]
{
Err(AppError::new(
"PLATFORM_UNSUPPORTED",
"Native process identity is supported only on Windows.",
3,
))
}
}
pub fn reopen(expected: &Identity, deadline: Instant) -> Result<Self> {
crate::core::deadline::check(deadline)?;
expected.validate()?;
let process = Self::observe(expected.process_id, deadline)?;
if &process.identity != expected {
return Err(changed());
}
Ok(process)
}
pub fn identity(&self) -> &Identity {
&self.identity
}
pub fn verify(&self, deadline: Instant) -> Result<()> {
crate::core::deadline::check(deadline)?;
#[cfg(windows)]
{
let observed = snapshot(&self.handle, deadline)?;
if observed != self.identity {
return Err(changed());
}
Ok(())
}
#[cfg(not(windows))]
{
Err(AppError::new(
"PLATFORM_UNSUPPORTED",
"Native process identity is supported only on Windows.",
3,
))
}
}
}
#[cfg(windows)]
fn snapshot(handle: &winsafe::HPROCESS, deadline: Instant) -> Result<Identity> {
alive(handle, deadline)?;
let process_id = handle
.GetProcessId()
.map_err(|_| failed_observation(handle, deadline))?;
crate::core::deadline::check(deadline)?;
let (creation, _, _, _) = handle
.GetProcessTimes()
.map_err(|_| failed_observation(handle, deadline))?;
crate::core::deadline::check(deadline)?;
let image = handle
.QueryFullProcessImageName(winsafe::co::PROCESS_NAME::WIN32)
.map_err(|_| failed_observation(handle, deadline))?;
let identity = Identity {
process_id,
created: u64::from(creation),
image: image.into(),
};
identity.validate()?;
alive(handle, deadline)?;
Ok(identity)
}
#[cfg(windows)]
fn failed_observation(handle: &winsafe::HPROCESS, deadline: Instant) -> AppError {
alive(handle, deadline).err().unwrap_or_else(unavailable)
}
#[cfg(windows)]
fn alive(handle: &winsafe::HPROCESS, deadline: Instant) -> Result<()> {
crate::core::deadline::check(deadline)?;
match handle
.WaitForSingleObject(Some(0))
.map_err(|_| unavailable())?
{
winsafe::co::WAIT::TIMEOUT => Ok(()),
winsafe::co::WAIT::OBJECT_0 => Err(AppError::new(
"PROCESS_EXITED",
"The observed process has exited; no replacement was adopted.",
4,
)),
_ => Err(unavailable()),
}
}
#[cfg(windows)]
fn unavailable() -> AppError {
AppError::new(
"PROCESS_UNVERIFIABLE",
"Process identity could not be verified; no ownership was inferred.",
4,
)
}
fn changed() -> AppError {
AppError::new(
"PROCESS_IDENTITY_CHANGED",
"Process identity does not match its recorded creation and image; no replacement was adopted.",
4,
)
}