use std::io;
use std::io::{Stdout, Stderr};
use std::fs::{File, OpenOptions};
pub trait Redirectable<T: ?Sized>
{
fn redirect(&mut self, destination: &T) -> io::Result<()>;
}
#[cfg(any(unix))]
mod platform
{
use super::*;
use std::os::fd::{AsRawFd, RawFd};
pub type Descriptor = RawFd;
pub trait Descriptable: AsRawFd {}
impl<T: AsRawFd> Descriptable for T {}
impl<T1: Descriptable, T2: Descriptable> Redirectable<T2> for T1 {
fn redirect(&mut self, destination: &T2) -> io::Result<()> {
let src_fd = self.as_raw_fd();
let dst_fd = destination.as_raw_fd();
return libc_common::redirect_fd_to_fd(src_fd, dst_fd);
}
}
}
#[cfg(any(target_os = "windows"))]
mod platform
{
use super::*;
use std::os::windows::io::AsRawHandle;
pub trait Descriptable: AsRawHandle {}
impl<T: AsRawHandle> Descriptable for T {}
#[cfg(feature = "libc_on_windows")]
mod libc_backend
{
use std::os::windows::io::RawHandle;
use super::*;
use crate::{Descriptable, Redirectable};
use libc::{c_int, get_osfhandle, open_osfhandle};
pub type Descriptor = c_int;
impl<T: Descriptable> Redirectable<T> for File {
fn redirect(&mut self, destination: &T) -> io::Result<()> {
let src_handle = self.as_raw_handle() as isize;
let dst_handle = destination.as_raw_handle() as isize;
let src_fd = unsafe { open_osfhandle(src_handle, 0) };
if src_fd < 0 {
return Err(io::Error::last_os_error());
}
let dst_fd = unsafe { open_osfhandle(dst_handle, 0) };
if dst_fd < 0 {
return Err(io::Error::last_os_error());
}
libc_common::redirect_fd_to_fd(src_fd, dst_fd)?;
let new_src_handle = unsafe { get_osfhandle(src_fd) };
if new_src_handle < 0 {
return Err(io::Error::last_os_error());
}
unsafe {
let handle_ptr = (self as *mut File) as *mut RawHandle;
*handle_ptr = new_src_handle as RawHandle;
}
return Ok(());
}
}
}
#[cfg(feature = "libc_on_windows")]
pub use libc_backend::*;
#[cfg(feature = "windows-sys")]
mod windows_sys_backend
{
use super::*;
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::System::Console::{SetStdHandle, STD_ERROR_HANDLE, STD_HANDLE, STD_OUTPUT_HANDLE};
impl<T: Descriptable> Redirectable<T> for Stdout {
fn redirect(&mut self, destination: &T) -> io::Result<()> {
redirect_using_setstdhandle(STD_OUTPUT_HANDLE, destination)
}
}
impl<T: Descriptable> Redirectable<T> for Stderr {
fn redirect(&mut self, destination: &T) -> io::Result<()> {
redirect_using_setstdhandle(STD_ERROR_HANDLE, destination)
}
}
fn redirect_using_setstdhandle<T: Descriptable>(std_handle: STD_HANDLE, destination: &T) -> io::Result<()> {
let dst_handle = destination.as_raw_handle() as HANDLE;
let result = unsafe { SetStdHandle(std_handle, dst_handle) };
if result == 0 {
return Err(io::Error::last_os_error());
}
return Ok(());
}
}
#[cfg(feature = "windows-sys")]
pub use windows_sys_backend::*;
}
#[cfg(any(all(unix, feature = "libc_on_unix"), all(target_os = "windows", feature = "libc_on_windows")))]
mod libc_common
{
use super::*;
use crate::platform::Descriptor;
use libc::dup2;
pub fn redirect_fd_to_fd(src: Descriptor, dst: Descriptor) -> io::Result<()> {
let result = unsafe {
dup2(dst, src)
};
if result < 0 {
return Err(io::Error::last_os_error());
}
return Ok(());
}
}
#[cfg(any(all(unix, feature = "libc_on_unix"), all(target_os = "windows", feature = "libc_on_windows")))]
mod libc_convenience
{
use super::*;
use std::fs::OpenOptions;
use std::path::Path;
impl<T: Redirectable<File>> Redirectable<Path> for T {
fn redirect(&mut self, destination: &Path) -> io::Result<()> {
let dst = OpenOptions::new().read(false).write(true).create(true).append(true).open(destination)?;
let result = self.redirect(&dst);
if result.is_ok() {
std::mem::forget(dst);
}
return result;
}
}
}
#[cfg(any(all(unix, feature = "libc_on_unix"), all(target_os = "windows", feature = "libc_on_windows")))]
pub use libc_convenience::*;
mod convenience
{
use super::*;
use std::fs::OpenOptions;
use std::io::{stderr, stdout};
use std::path::Path;
pub fn redirect_std_to_path(destination: &Path, append: bool) -> io::Result<()> {
let dst = OpenOptions::new().read(false).write(true).create(true).append(append).open(destination)?;
stdout().redirect(&dst)?;
stderr().redirect(&dst)?;
std::mem::forget(dst);
return Ok(());
}
}
pub use convenience::*;
pub use platform::*;
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Read, Write};
use std::mem::ManuallyDrop;
use libc::close;
#[cfg(any(all(unix, feature = "libc_on_unix"), all(target_os = "windows", feature = "libc_on_windows")))]
#[test]
fn redirects_file_to_file() {
let tempdir = tempfile::tempdir().unwrap();
let mut file1 = File::create(tempdir.path().join("file1.txt")).unwrap();
let mut file2 = File::create(tempdir.path().join("file2.txt")).unwrap();
file1.redirect(&file2).unwrap();
file1.write_all(b"Hello,").unwrap();
file1.flush().unwrap();
file2.write_all(b" World!").unwrap();
file2.flush().unwrap();
let mut dst_file = File::open(tempdir.path().join("file2.txt")).unwrap();
let mut dst_contents = String::new();
dst_file.read_to_string(&mut dst_contents).unwrap();
assert_eq!(dst_contents, "Hello, World!");
let mut old_file1_contents = String::new();
let mut old_file1 = File::open(tempdir.path().join("file1.txt")).unwrap();
old_file1.read_to_string(&mut old_file1_contents).unwrap();
assert_eq!(old_file1_contents, "");
}
#[cfg(any(all(unix, feature = "libc_on_unix"), all(target_os = "windows", feature = "libc_on_windows")))]
#[test]
fn redirects_file_to_path() {
let tempdir = tempfile::tempdir().unwrap();
let src_path = tempdir.path().join("src.txt");
let dst_path = tempdir.path().join("dst.txt");
let mut src = OpenOptions::new().create(true).read(true).write(true).open(&src_path).unwrap();
src.redirect(dst_path.as_path()).unwrap();
src.write_all(b"abc").unwrap();
src.flush().unwrap();
let mut dst_contents = String::new();
File::open(&dst_path).unwrap().read_to_string(&mut dst_contents).unwrap();
assert_eq!(dst_contents, "abc");
let mut original_contents = String::new();
File::open(&src_path).unwrap().read_to_string(&mut original_contents).unwrap();
assert_eq!(original_contents, "");
}
#[cfg(any(all(unix, feature = "libc_on_unix"), all(target_os = "windows", feature = "libc_on_windows")))]
#[test]
fn errors_on_redirect_to_directory() {
let tempdir = tempfile::tempdir().unwrap();
let dir_path = tempdir.path();
let mut src = File::create(dir_path.join("somefile.txt")).unwrap();
let err = src.redirect(dir_path).unwrap_err();
assert!(err.raw_os_error().is_some());
}
#[cfg(any(all(unix, feature = "libc_on_unix"), all(target_os = "windows", feature = "libc_on_windows")))]
#[test]
fn errors_on_redirect_with_missing_parent_directory() {
let tempdir = tempfile::tempdir().unwrap();
let mut src = File::create(tempdir.path().join("s.txt")).unwrap();
let bad_path = tempdir.path().join("no_such_dir").join("f.txt");
let err = src.redirect(bad_path.as_path()).unwrap_err();
assert!(err.raw_os_error().is_some());
}
#[cfg(any(all(unix, feature = "libc_on_unix")))]
#[test]
fn errors_on_redirect_to_closed_fd() {
use std::os::fd::AsRawFd;
let tempdir = tempfile::tempdir().unwrap();
let mut src_file = File::create(tempdir.path().join("src.txt")).unwrap();
let dst_file = File::create(tempdir.path().join("dst.txt")).unwrap();
let dst_file = ManuallyDrop::new(dst_file);
let fd = dst_file.as_raw_fd();
unsafe { close(fd) };
let err = src_file.redirect(&*dst_file).unwrap_err();
assert!(err.raw_os_error().is_some());
}
}