use super::{Job, JobId, OwnedHandle, QUERY, pin, unavailable};
use crate::core::error::{AppError, Result};
use crate::native::process::{Identity, Pinned};
use serde::{Deserialize, Serialize};
use std::time::Instant;
use windows::Win32::Foundation::{DUPLICATE_HANDLE_OPTIONS, DuplicateHandle, HANDLE};
use windows::Win32::System::JobObjects::IsProcessInJob;
use windows::Win32::System::Threading::{
GetCurrentProcess, OpenProcess, PROCESS_DUP_HANDLE, PROCESS_QUERY_LIMITED_INFORMATION,
PROCESS_SYNCHRONIZE,
};
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct Retained {
schema_version: u8,
job: JobId,
target: Identity,
remote_handle: u64,
}
impl Retained {
pub(crate) fn validate_for(&self, job: &JobId, target: &Identity) -> Result<()> {
if self.schema_version != 1
|| &self.job != job
|| &self.target != target
|| self.remote_handle == 0
|| self.remote_handle == u64::MAX
{
return Err(AppError::storage());
}
Ok(())
}
pub fn target(&self) -> &Identity {
&self.target
}
}
impl Job {
pub fn retain_in(&self, process: &Pinned, deadline: Instant) -> Result<Retained> {
crate::core::deadline::check(deadline)?;
self.check_limits(deadline)?;
if !self.contains(process, deadline)? {
return Err(not_member());
}
let target = unsafe {
OpenProcess(
PROCESS_DUP_HANDLE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_SYNCHRONIZE,
false,
process.identity().process_id(),
)
}
.map_err(|_| unavailable())?;
let target = pin(OwnedHandle(target), deadline)?;
if target.identity() != process.identity() {
return Err(not_member());
}
let raw = HANDLE(target.native_handle().ptr());
member(self, raw, deadline)?;
process.verify(deadline)?;
target.verify(deadline)?;
let mut remote = HANDLE::default();
crate::core::deadline::check(deadline)?;
unsafe {
DuplicateHandle(
GetCurrentProcess(),
self.handle.0,
raw,
&mut remote,
QUERY,
false,
DUPLICATE_HANDLE_OPTIONS(0),
)
}
.map_err(|_| unknown())?;
if remote.is_invalid() {
return Err(unknown());
}
member(self, raw, deadline).map_err(|_| unknown())?;
target.verify(deadline).map_err(|_| unknown())?;
process.verify(deadline).map_err(|_| unknown())?;
Ok(Retained {
schema_version: 1,
job: self.id().clone(),
target: target.identity().clone(),
remote_handle: remote.0 as usize as u64,
})
}
}
fn member(job: &Job, process: HANDLE, deadline: Instant) -> Result<()> {
crate::core::deadline::check(deadline)?;
let mut found = false.into();
unsafe { IsProcessInJob(process, Some(job.handle.0), &mut found) }
.map_err(|_| unavailable())?;
if found.as_bool() {
Ok(())
} else {
Err(not_member())
}
}
fn not_member() -> AppError {
AppError::new(
"JOB_MEMBER_MISMATCH",
"The pinned process is not a verified member of this run; no handle was retained.",
4,
)
}
fn unknown() -> AppError {
let mut error = AppError::new(
"RETENTION_OUTCOME_UNKNOWN",
"A process handle-table operation may have occurred; do not repeat it automatically.",
5,
);
error.outcome = Some(crate::core::contracts::write::Outcome::Unknown);
error
}
#[cfg(test)]
mod tests;