use crate::error::{Error, Result};
use windows::Win32::Foundation::{
CloseHandle, DuplicateHandle, DUPLICATE_SAME_ACCESS, HANDLE, INVALID_HANDLE_VALUE,
};
#[derive(Debug)]
pub struct OwnedHandle {
handle: HANDLE,
}
impl OwnedHandle {
#[inline]
pub fn new(handle: HANDLE) -> Result<Self> {
if handle.is_invalid() || handle.0.is_null() {
return Err(Error::invalid_handle(
"Cannot create OwnedHandle from invalid handle",
));
}
Ok(Self { handle })
}
pub fn new_allow_null(handle: HANDLE) -> Result<Self> {
if handle == INVALID_HANDLE_VALUE {
return Err(Error::invalid_handle(
"Cannot create OwnedHandle from INVALID_HANDLE_VALUE",
));
}
Ok(Self { handle })
}
#[inline]
pub unsafe fn new_unchecked(handle: HANDLE) -> Self {
Self { handle }
}
#[inline]
pub fn as_raw(&self) -> HANDLE {
self.handle
}
#[inline]
pub fn into_raw(self) -> HANDLE {
let handle = self.handle;
std::mem::forget(self);
handle
}
pub fn try_clone(&self) -> Result<Self> {
use windows::Win32::System::Threading::GetCurrentProcess;
let mut new_handle = HANDLE::default();
let current_process = unsafe { GetCurrentProcess() };
unsafe {
DuplicateHandle(
current_process,
self.handle,
current_process,
&mut new_handle,
0,
false,
DUPLICATE_SAME_ACCESS,
)?;
}
Self::new(new_handle)
}
}
impl Drop for OwnedHandle {
fn drop(&mut self) {
if !self.handle.is_invalid() && !self.handle.0.is_null() {
unsafe {
let _ = CloseHandle(self.handle);
}
}
}
}
impl AsRef<HANDLE> for OwnedHandle {
fn as_ref(&self) -> &HANDLE {
&self.handle
}
}
#[derive(Clone, Copy, Debug)]
pub struct BorrowedHandle<'a> {
handle: HANDLE,
_marker: std::marker::PhantomData<&'a ()>,
}
impl<'a> BorrowedHandle<'a> {
#[inline]
pub unsafe fn new(handle: HANDLE) -> Self {
Self {
handle,
_marker: std::marker::PhantomData,
}
}
#[inline]
pub fn from_owned(owned: &'a OwnedHandle) -> Self {
Self {
handle: owned.handle,
_marker: std::marker::PhantomData,
}
}
#[inline]
pub fn as_raw(&self) -> HANDLE {
self.handle
}
}
impl<'a> From<&'a OwnedHandle> for BorrowedHandle<'a> {
fn from(owned: &'a OwnedHandle) -> Self {
BorrowedHandle::from_owned(owned)
}
}
pub trait HandleExt {
fn is_valid(&self) -> bool;
}
impl HandleExt for HANDLE {
#[inline]
fn is_valid(&self) -> bool {
!self.is_invalid() && !self.0.is_null()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_handle_rejected() {
let result = OwnedHandle::new(INVALID_HANDLE_VALUE);
assert!(result.is_err());
}
#[test]
fn test_null_handle_rejected() {
let result = OwnedHandle::new(HANDLE::default());
assert!(result.is_err());
}
#[test]
fn test_handle_extension_trait() {
assert!(!INVALID_HANDLE_VALUE.is_valid());
assert!(!HANDLE::default().is_valid());
}
#[test]
fn test_new_allow_null_rejects_invalid() {
let result = OwnedHandle::new_allow_null(INVALID_HANDLE_VALUE);
assert!(result.is_err());
}
#[test]
fn test_handle_into_raw_prevents_double_close() {
use crate::fs::OpenOptions;
use std::env;
let temp_path = env::temp_dir().join("handle_test_into_raw.tmp");
if let Ok(handle) = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&temp_path)
{
let raw = handle.into_raw();
assert!(raw.is_valid());
unsafe {
let _ = CloseHandle(raw);
}
let _ = std::fs::remove_file(&temp_path);
}
}
#[test]
fn test_handle_try_clone() {
use crate::fs::OpenOptions;
use std::env;
let temp_path = env::temp_dir().join("handle_test_clone.tmp");
if let Ok(handle) = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&temp_path)
{
let cloned = handle.try_clone();
assert!(cloned.is_ok());
let cloned = cloned.unwrap();
assert!(handle.as_raw().is_valid());
assert!(cloned.as_raw().is_valid());
assert_ne!(handle.as_raw().0, cloned.as_raw().0);
drop(handle);
drop(cloned);
let _ = std::fs::remove_file(&temp_path);
}
}
#[test]
fn test_borrowed_handle_from_owned() {
use crate::fs::OpenOptions;
use std::env;
let temp_path = env::temp_dir().join("handle_test_borrow.tmp");
if let Ok(handle) = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&temp_path)
{
let borrowed = BorrowedHandle::from_owned(&handle);
assert_eq!(borrowed.as_raw().0, handle.as_raw().0);
let borrowed2: BorrowedHandle = (&handle).into();
assert_eq!(borrowed2.as_raw().0, handle.as_raw().0);
drop(handle);
let _ = std::fs::remove_file(&temp_path);
}
}
#[test]
fn test_stress_handle_creation_and_cleanup() {
use crate::fs::OpenOptions;
use std::env;
for i in 0..100 {
let temp_path = env::temp_dir().join(format!("handle_stress_{}.tmp", i));
if let Ok(_handle) = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&temp_path)
{
}
let _ = std::fs::remove_file(&temp_path);
}
}
#[test]
fn test_stress_handle_cloning() {
use crate::fs::OpenOptions;
use std::env;
let temp_path = env::temp_dir().join("handle_stress_clone.tmp");
if let Ok(handle) = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&temp_path)
{
let mut clones = Vec::new();
for _ in 0..50 {
if let Ok(cloned) = handle.try_clone() {
clones.push(cloned);
}
}
for clone in &clones {
assert!(clone.as_raw().is_valid());
}
drop(clones);
drop(handle);
let _ = std::fs::remove_file(&temp_path);
}
}
#[test]
fn test_multiple_handles_same_file() {
use crate::fs::OpenOptions;
use std::env;
let temp_path = env::temp_dir().join("handle_multiple.tmp");
let h1 = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.share_read(true)
.share_write(true)
.open(&temp_path);
let h2 = OpenOptions::new()
.read(true)
.share_read(true)
.share_write(true)
.open(&temp_path);
let h3 = OpenOptions::new()
.read(true)
.share_read(true)
.share_write(true)
.open(&temp_path);
if let (Ok(h1), Ok(h2), Ok(h3)) = (h1, h2, h3) {
assert!(h1.as_raw().is_valid());
assert!(h2.as_raw().is_valid());
assert!(h3.as_raw().is_valid());
assert_ne!(h1.as_raw().0, h2.as_raw().0);
assert_ne!(h2.as_raw().0, h3.as_raw().0);
}
let _ = std::fs::remove_file(&temp_path);
}
#[test]
fn test_handle_drop_order() {
use crate::fs::OpenOptions;
use std::env;
let temp_path1 = env::temp_dir().join("handle_order_1.tmp");
let temp_path2 = env::temp_dir().join("handle_order_2.tmp");
let temp_path3 = env::temp_dir().join("handle_order_3.tmp");
let h1 = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&temp_path1)
.ok();
let h2 = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&temp_path2)
.ok();
let h3 = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&temp_path3)
.ok();
drop(h3);
drop(h1);
drop(h2);
let _ = std::fs::remove_file(&temp_path1);
let _ = std::fs::remove_file(&temp_path2);
let _ = std::fs::remove_file(&temp_path3);
}
}