use std::ffi::c_void;
use std::fmt;
use std::io;
use std::os::windows::io::{
AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, OwnedHandle, RawHandle,
};
use std::ptr;
use windows_sys::Win32::Foundation::{
DUPLICATE_SAME_ACCESS, DuplicateHandle, ERROR_INVALID_HANDLE, FALSE, HANDLE,
};
use windows_sys::Win32::System::Threading::GetCurrentProcess;
mod pseudo {
pub const CURRENT_PROCESS: isize = -1;
pub const CURRENT_THREAD: isize = -2;
pub const RESERVED: isize = -3;
pub const CURRENT_PROCESS_TOKEN: isize = -4;
pub const CURRENT_THREAD_TOKEN: isize = -5;
pub const CURRENT_THREAD_EFFECTIVE_TOKEN: isize = -6;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum HandleCaptureFailure {
NullHandle,
InvalidHandleValue,
PseudoHandle,
DuplicateHandle,
}
#[derive(Debug)]
pub struct HandleCaptureError {
failure: HandleCaptureFailure,
source: io::Error,
}
impl HandleCaptureError {
fn new(failure: HandleCaptureFailure, source: io::Error) -> Self {
Self { failure, source }
}
fn invalid_handle(failure: HandleCaptureFailure) -> Self {
Self::new(
failure,
io::Error::from_raw_os_error(
i32::try_from(ERROR_INVALID_HANDLE).expect("ERROR_INVALID_HANDLE fits in i32"),
),
)
}
#[must_use]
pub fn failure(&self) -> HandleCaptureFailure {
self.failure
}
#[must_use]
pub fn raw_os_error(&self) -> Option<i32> {
self.source.raw_os_error()
}
}
impl fmt::Display for HandleCaptureError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let stage = match self.failure {
HandleCaptureFailure::NullHandle => "null source handle",
HandleCaptureFailure::InvalidHandleValue => "INVALID_HANDLE_VALUE source handle",
HandleCaptureFailure::PseudoHandle => "pseudo-handle source handle",
HandleCaptureFailure::DuplicateHandle => "DuplicateHandle",
};
write!(f, "{stage}: {}", self.source)
}
}
impl std::error::Error for HandleCaptureError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}
#[derive(Debug)]
#[must_use = "dropping the captured handle closes the duplicate"]
pub struct CapturedHandle {
duplicate: OwnedHandle,
}
impl CapturedHandle {
pub fn capture(source: BorrowedHandle<'_>) -> Result<Self, HandleCaptureError> {
unsafe { Self::capture_raw(source.as_raw_handle()) }
}
pub unsafe fn capture_raw(source: RawHandle) -> Result<Self, HandleCaptureError> {
if source.is_null() {
return Err(HandleCaptureError::invalid_handle(
HandleCaptureFailure::NullHandle,
));
}
match source as isize {
pseudo::CURRENT_PROCESS => {
return Err(HandleCaptureError::invalid_handle(
HandleCaptureFailure::InvalidHandleValue,
));
}
pseudo::CURRENT_THREAD
| pseudo::RESERVED
| pseudo::CURRENT_PROCESS_TOKEN
| pseudo::CURRENT_THREAD_TOKEN
| pseudo::CURRENT_THREAD_EFFECTIVE_TOKEN => {
return Err(HandleCaptureError::invalid_handle(
HandleCaptureFailure::PseudoHandle,
));
}
_ => {}
}
let mut duplicate: HANDLE = ptr::null_mut();
let duplicated = unsafe {
let process = GetCurrentProcess();
DuplicateHandle(
process,
source,
process,
&raw mut duplicate,
0,
FALSE,
DUPLICATE_SAME_ACCESS,
)
};
if duplicated == FALSE {
return Err(HandleCaptureError::new(
HandleCaptureFailure::DuplicateHandle,
io::Error::last_os_error(),
));
}
let duplicate = unsafe { OwnedHandle::from_raw_handle(duplicate) };
Ok(Self { duplicate })
}
pub fn try_clone(&self) -> Result<Self, HandleCaptureError> {
Self::capture(self.duplicate.as_handle())
}
#[must_use]
pub fn into_owned_handle(self) -> OwnedHandle {
self.duplicate
}
pub(crate) fn raw(&self) -> HANDLE {
self.duplicate.as_raw_handle().cast::<c_void>()
}
}
impl AsHandle for CapturedHandle {
fn as_handle(&self) -> BorrowedHandle<'_> {
self.duplicate.as_handle()
}
}
impl From<CapturedHandle> for OwnedHandle {
fn from(captured: CapturedHandle) -> Self {
captured.into_owned_handle()
}
}
#[cfg(test)]
pub(crate) mod tests;