use std::sync::Arc;
macro_rules! impl_native_convervions {
($nice:ident) => {
#[cfg(unix)]
impl std::os::fd::AsFd for $nice {
fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
unsafe { std::os::fd::BorrowedFd::borrow_raw(self.0.inner) }
}
}
#[cfg(unix)]
impl std::os::fd::AsRawFd for $nice {
fn as_raw_fd(&self) -> std::os::fd::RawFd {
self.0.inner
}
}
#[cfg(windows)]
impl std::os::windows::io::AsHandle for $nice {
fn as_handle(&self) -> std::os::windows::io::BorrowedHandle<'_> {
unsafe {
std::os::windows::io::BorrowedHandle::borrow_raw(self.0.inner)
}
}
}
#[cfg(windows)]
impl std::os::windows::io::AsRawHandle for $nice {
fn as_raw_handle(&self) -> RawHandle {
self.0.inner
}
}
};
}
#[cfg(unix)]
type Inner = std::os::fd::RawFd;
#[cfg(windows)]
type Inner = RawHandle;
struct Owned {
inner: Inner,
}
impl Owned {
fn new(inner: Inner) -> Self {
Self { inner }
}
}
impl Drop for Owned {
fn drop(&mut self) {
let _ = syscall!(close(self.inner));
}
}
#[derive(Clone)]
pub struct Resource(Arc<Owned>);
pub trait IntoResource {
fn into_resource(self) -> Resource;
}
pub trait AsResource {
fn as_resource(&self) -> &Resource;
}
impl AsResource for &Resource {
fn as_resource(&self) -> &Resource {
self
}
}
impl AsResource for Resource {
fn as_resource(&self) -> &Resource {
self
}
}
pub trait FromResource {
fn from_resource(resource: Resource) -> Self;
}
impl_native_convervions!(Resource);
#[cfg(unix)]
impl std::os::fd::FromRawFd for Resource {
unsafe fn from_raw_fd(fd: std::os::fd::RawFd) -> Self {
Resource(Arc::new(Owned::new(fd)))
}
}
#[cfg(windows)]
impl std::os::windows::io::FromRawHandle for Resource {
unsafe fn from_raw_handle(handle: RawHandle) -> Self {
Resource(Arc::new(Owned::new(handle)))
}
}
impl Resource {
#[cfg(unix)]
pub fn stdout() -> Self {
let fd = unsafe { libc::dup(libc::STDOUT_FILENO) };
assert!(fd >= 0, "Failed to dup stdout");
unsafe { <Self as std::os::fd::FromRawFd>::from_raw_fd(fd) }
}
#[cfg(windows)]
pub fn stdout() -> Self {
use windows_sys::Win32::Foundation::{
DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE, INVALID_HANDLE_VALUE,
};
use windows_sys::Win32::System::Console::{
GetStdHandle, STD_OUTPUT_HANDLE,
};
use windows_sys::Win32::System::Threading::GetCurrentProcess;
let handle = unsafe { GetStdHandle(STD_OUTPUT_HANDLE) };
assert!(handle != INVALID_HANDLE_VALUE, "Failed to get stdout handle");
let mut dup_handle: HANDLE = 0;
let current_process = unsafe { GetCurrentProcess() };
let result = unsafe {
DuplicateHandle(
current_process,
handle,
current_process,
&mut dup_handle,
0,
0, DUPLICATE_SAME_ACCESS,
)
};
assert!(result != 0, "Failed to duplicate stdout handle");
unsafe {
<Self as std::os::windows::io::FromRawHandle>::from_raw_handle(
dup_handle as *mut std::ffi::c_void,
)
}
}
#[cfg(unix)]
pub fn stderr() -> Self {
let fd = unsafe { libc::dup(libc::STDERR_FILENO) };
assert!(fd >= 0, "Failed to dup stderr");
unsafe { <Self as std::os::fd::FromRawFd>::from_raw_fd(fd) }
}
#[cfg(windows)]
pub fn stderr() -> Self {
use windows_sys::Win32::Foundation::{
DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE, INVALID_HANDLE_VALUE,
};
use windows_sys::Win32::System::Console::{GetStdHandle, STD_ERROR_HANDLE};
use windows_sys::Win32::System::Threading::GetCurrentProcess;
let handle = unsafe { GetStdHandle(STD_ERROR_HANDLE) };
assert!(handle != INVALID_HANDLE_VALUE, "Failed to get stderr handle");
let mut dup_handle: HANDLE = 0;
let current_process = unsafe { GetCurrentProcess() };
let result = unsafe {
DuplicateHandle(
current_process,
handle,
current_process,
&mut dup_handle,
0,
0, DUPLICATE_SAME_ACCESS,
)
};
assert!(result != 0, "Failed to duplicate stderr handle");
unsafe {
<Self as std::os::windows::io::FromRawHandle>::from_raw_handle(
dup_handle as *mut std::ffi::c_void,
)
}
}
#[cfg(unix)]
pub fn stdin() -> Self {
let fd = unsafe { libc::dup(libc::STDIN_FILENO) };
assert!(fd >= 0, "Failed to dup stdin");
unsafe { <Self as std::os::fd::FromRawFd>::from_raw_fd(fd) }
}
#[cfg(windows)]
pub fn stdin() -> Self {
use windows_sys::Win32::Foundation::{
DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE, INVALID_HANDLE_VALUE,
};
use windows_sys::Win32::System::Console::{GetStdHandle, STD_INPUT_HANDLE};
use windows_sys::Win32::System::Threading::GetCurrentProcess;
let handle = unsafe { GetStdHandle(STD_INPUT_HANDLE) };
assert!(handle != INVALID_HANDLE_VALUE, "Failed to get stdin handle");
let mut dup_handle: HANDLE = 0;
let current_process = unsafe { GetCurrentProcess() };
let result = unsafe {
DuplicateHandle(
current_process,
handle,
current_process,
&mut dup_handle,
0,
0, DUPLICATE_SAME_ACCESS,
)
};
assert!(result != 0, "Failed to duplicate stdin handle");
unsafe {
<Self as std::os::windows::io::FromRawHandle>::from_raw_handle(
dup_handle as *mut std::ffi::c_void,
)
}
}
pub fn count(&self) -> usize {
Arc::strong_count(&self.0)
}
pub fn will_close(&self) -> bool {
self.count() == 1
}
}
impl std::fmt::Debug for Resource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Resource")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stdout() {
let stdout = Resource::stdout();
assert_eq!(stdout.count(), 1);
let clone = stdout.clone();
assert_eq!(stdout.count(), 2);
assert_eq!(clone.count(), 2);
drop(clone);
assert_eq!(stdout.count(), 1);
drop(stdout);
}
#[test]
fn test_stderr() {
let stderr = Resource::stderr();
assert_eq!(stderr.count(), 1);
let clone = stderr.clone();
assert_eq!(stderr.count(), 2);
drop(clone);
assert_eq!(stderr.count(), 1);
drop(stderr);
}
#[test]
fn test_stdin() {
let stdin = Resource::stdin();
assert_eq!(stdin.count(), 1);
let clone = stdin.clone();
assert_eq!(stdin.count(), 2);
drop(clone);
assert_eq!(stdin.count(), 1);
drop(stdin);
}
#[test]
fn test_std_streams_are_independent() {
let stdout1 = Resource::stdout();
let stdout2 = Resource::stdout();
assert_eq!(stdout1.count(), 1);
assert_eq!(stdout2.count(), 1);
drop(stdout1);
assert_eq!(stdout2.count(), 1);
}
}