From dfc92caebb0116a7acd2cc674dce5b2c6561d8b2 Mon Sep 17 00:00:00 2001 From: Koxiaet <38139193+Koxiaet@users.noreply.github.com> Date: Mon, 28 Sep 2020 09:23:45 +0100 Subject: [PATCH] Add Windows support --- Cargo.toml | 8 ++-- README.md | 2 +- src/lib.rs | 44 ++++++++++----------- src/unix.rs | 5 ++- src/windows.rs | 102 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 132 insertions(+), 29 deletions(-) create mode 100644 src/windows.rs diff --git a/Cargo.toml b/Cargo.toml index 16dc108..01baad8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,9 +6,9 @@ authors = ["elichai2 "] repository = "https://github.com/elichai/stdio-override" readme = "README.md" edition = "2018" -description = "Rust library for overriding Stdin/Stdout/Stderr with a different File Descriptor" -categories = ["os::unix-apis", "development-tools::debugging"] -keywords = ["unix", "sockets", "fd", "file", "io"] +description = "Rust library for overriding Stdin/Stdout/Stderr with a different stream" +categories = ["os", "development-tools::debugging"] +keywords = ["crossplatform", "sockets", "fd", "file", "io"] [dependencies] @@ -16,6 +16,8 @@ doc-comment = { version = "0.3", optional = true } [target.'cfg(unix)'.dependencies] libc = "0.2" +[target.'cfg(windows)'.dependencies] +winapi = "0.3" [dev-dependencies] os_pipe = "0.9.2" diff --git a/README.md b/README.md index 21b5a80..adb69c0 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Documentation](https://docs.rs/stdio-override/badge.svg)](https://docs.rs/stdio-override) ![License](https://img.shields.io/crates/l/stdio-override.svg) -A Rust library to easily override Stdio file descriptors in Rust +A Rust library to easily override Stdio streams in Rust. It works on Unix and Windows platforms. * [Documentation](https://docs.rs/stdio-override) diff --git a/src/lib.rs b/src/lib.rs index 8eba500..fb3720c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,9 +3,9 @@ //! # Stdio-Override //! -//! This crate provides a library for overriding Stdio file descriptors.
-//! It provides a guard for the replacement so that when the guard is dropped the file descriptors are switched back -//! and the replacement File Descriptor will be closed. +//! This crate provides a library for overriding Stdio streams.
+//! It provides a guard for the replacement so that when the guard is dropped the streams are switched back +//! and the replacement stream will be closed. //! //! You can replace a standard stream twice, just keep in mind that each guard, when dropped, will //! replace stdout with the stdout that existed when it was created. This means that if you don't @@ -85,10 +85,11 @@ use std::io::{self, IoSlice, IoSliceMut, Read, Write}; use std::mem::ManuallyDrop; use std::path::Path; -#[cfg(not(any(unix)))] -compile_error!("stdio-override only supports Unix"); +#[cfg(not(any(unix, windows)))] +compile_error!("stdio-override only supports Unix and Windows"); #[cfg_attr(unix, path = "unix.rs")] +#[cfg_attr(windows, path = "windows.rs")] mod imp; /// An overridden standard input. @@ -101,17 +102,16 @@ pub struct StdinOverride { reset: bool, } impl StdinOverride { - /// Read standard input from the raw file descriptor. The file descriptor must be readable. + /// Read standard input from the raw file descriptor or handle. It must be readable. /// - /// The file descriptor is not owned, so it is your job to close it later. Closing it while - /// this exists will not close the standard error. + /// The stream is not owned, so it is your job to close it later. Closing it while this exists + /// will not close the standard error. pub fn from_raw(raw: imp::Raw) -> io::Result { Ok(Self { original: ManuallyDrop::new(imp::override_stdin(raw, false)?), reset: false }) } - /// Read standard input from the owned raw file descriptor. The file descriptor must be - /// readable. + /// Read standard input from the owned raw file descriptor or handle. It must be readable. /// - /// The file descriptor is owned, and so you must not use it after passing it to this function. + /// The stream is owned, and so you must not use it after passing it to this function. pub fn from_raw_owned(raw: imp::Raw) -> io::Result { Ok(Self { original: ManuallyDrop::new(imp::override_stdin(raw, true)?), reset: false }) } @@ -177,17 +177,16 @@ pub struct StdoutOverride { reset: bool, } impl StdoutOverride { - /// Redirect standard output to the raw file descriptor. The file descriptor must be writable. + /// Redirect standard output to the raw file descriptor or handle. It must be writable. /// - /// The file descriptor is not owned, so it is your job to close it later. Closing it while - /// this exists will not close the standard output. + /// The stream is not owned, so it is your job to close it later. Closing it while this exists + /// will not close the standard output. pub fn from_raw(raw: imp::Raw) -> io::Result { Ok(Self { original: ManuallyDrop::new(imp::override_stdout(raw, false)?), reset: false }) } - /// Redirect standard output to the owned raw file descriptor. The file descriptor must be - /// writable. + /// Redirect standard output to the owned raw file descriptor or handle. It must be writable. /// - /// The file descriptor is owned, and so you must not use it after passing it to this function. + /// The stream is owned, and so you must not use it after passing it to this function. pub fn from_raw_owned(raw: imp::Raw) -> io::Result { Ok(Self { original: ManuallyDrop::new(imp::override_stdout(raw, true)?), reset: false }) } @@ -259,17 +258,16 @@ pub struct StderrOverride { reset: bool, } impl StderrOverride { - /// Redirect standard error to the raw file descriptor. The file descriptor must be writable. + /// Redirect standard error to the raw file descriptor or handle. It must be writable. /// - /// The file descriptor is not owned, so it is your job to close it later. Closing it while - /// this exists will not close the standard error. + /// The stream is not owned, so it is your job to close it later. Closing it while this exists + /// will not close the standard error. pub fn from_raw(raw: imp::Raw) -> io::Result { Ok(Self { original: ManuallyDrop::new(imp::override_stderr(raw, false)?), reset: false }) } - /// Redirect standard error to the owned raw file descriptor. The file descriptor must be - /// writable. + /// Redirect standard error to the owned raw file descriptor or handle. It must be writable. /// - /// The file descriptor is owned, and so you must not use it after passing it to this function. + /// The stream is owned, and so you must not use it after passing it to this function. pub fn from_raw_owned(raw: imp::Raw) -> io::Result { Ok(Self { original: ManuallyDrop::new(imp::override_stderr(raw, true)?), reset: false }) } diff --git a/src/unix.rs b/src/unix.rs index e8eb48c..036870a 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -2,6 +2,7 @@ use std::fs::File; use std::io; use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; +use libc::c_int; use libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO}; pub(crate) use std::os::unix::io::{AsRawFd as AsRaw, IntoRawFd as IntoRaw, RawFd as Raw}; @@ -72,8 +73,8 @@ fn test_original() -> io::Result<()> { Ok(()) } -fn io_res>(res: T) -> io::Result { - if res == T::from(-1) { +fn io_res(res: c_int) -> io::Result { + if res == -1 { Err(io::Error::last_os_error()) } else { Ok(res) diff --git a/src/windows.rs b/src/windows.rs new file mode 100644 index 0000000..c92f546 --- /dev/null +++ b/src/windows.rs @@ -0,0 +1,102 @@ +use std::fs::File; +use std::io; +use std::os::windows::FromRawHandle; +use std::ptr; + +use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE}; +use winapi::um::handleapi::{CloseHandle, DuplicateHandle, GetHandleInformation, INVALID_HANDLE_VALUE}; +use winapi::um::processenv::{GetStdHandle, SetStdHandle}; +use winapi::um::processthreadsapi::GetCurrentProcess; +use winapi::um::winbase::HANDLE_FLAG_INHERIT; +use winapi::um::winbase::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE}; +use winapi::um::winnt::DUPLICATE_SAME_ACCESS; + +pub(crate) use std::os::windows::io::{AsRawHandle as AsRaw, IntoRawHandle as IntoRaw, RawHandle as Raw}; + +pub(crate) fn as_raw(io: &impl AsRawHandle) -> RawHandle { + io.as_raw_handle() +} +pub(crate) fn into_raw(io: impl IntoRawHandle) -> RawHandle { + io.into_raw_handle() +} + +pub(crate) fn override_stdin(io: RawHandle, owned: bool) -> io::Result { + override_stdio(STD_INPUT_HANDLE, owned) +} +pub(crate) fn override_stdout(io: RawHandle, owned: bool) -> io::Result { + override_stdio(STD_OUTPUT_HANDLE, owned) +} +pub(crate) fn override_stderr(io: RawHandle, owned: bool) -> io::Result { + override_stdio(STD_ERROR_HANDLE, owned) +} + +pub(crate) fn reset_stdin(old: RawHandle) -> io::Result<()> { + reset_stdio(STD_INPUT_HANDLE, old) +} +pub(crate) fn reset_stdout(old: RawHandle) -> io::Result<()> { + reset_stdio(STD_OUTPUT_HANDLE, old) +} +pub(crate) fn reset_stderr(old: RawHandle) -> io::Result<()> { + reset_stdio(STD_ERROR_HANDLE, old) +} + +fn override_stdio(stdio: DWORD, other: RawHandle, owned: bool) -> io::Result { + let original = handle_res(unsafe { GetStdHandle(stdio) })?; + + let other = if owned { + other + } else { + // If it isn't owned, duplicate the handle to prevent closing the original handle from + // closing the stdio handle. + + let process = unsafe { GetCurrentProcess() }; + + let mut handle_information = 0; + io_res(unsafe { GetHandleInformation(other, &mut handle_information as *mut DWORD) })?; + let inherit_handle = if handle_information & HANDLE_FLAG_INHERIT == HANDLE_FLAG_INHERIT { TRUE } else { FALSE }; + + let mut target = ptr::null_mut(); + io_res(unsafe { + DuplicateHandle( + process, + other, + process, + &mut target as *mut RawHandle, + 0, // ignored + inherit_handle, + DUPLICATE_SAME_ACCESS, + ) + })?; + + target + }; + + io_res(unsafe { SetStdHandle(stdio, other) })?; + + Ok(unsafe { File::from_raw_handle(original) }) +} +fn reset_stdio(stdio: DWORD, other: RawHandle) -> io::Result<()> { + let current = handle_res(unsafe { GetStdHandle(stdio) })?; + + io_res(unsafe { SetStdHandle(stdio, other) })?; + + io_res(unsafe { CloseHandle(current) })?; + + Ok(()) +} + +fn io_res(res: BOOL) -> io::Result<()> { + if res == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +fn handle_res(res: RawHandle) -> io::Result { + if res == INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else { + Ok(res) + } +}