stdio_utils/lib.rs
1#[cfg_attr(unix, path = "sys/unix.rs")]
2#[cfg_attr(windows, path = "sys/windows.rs")]
3mod sys;
4
5use std::fs::File;
6use std::io::Result;
7
8use crate::sys::{borrow_fd, override_stdio, AsFd, BorrowedFd, OwnedFd, DEV_NULL};
9
10#[derive(Clone, Copy)]
11enum Stdio {
12 Stdin,
13 Stdout,
14 Stderr,
15}
16
17pub trait StdioOverride: AsFd {
18 /// Replace the process standard output with a duplicate of the file descriptor.
19 ///
20 /// ```rust
21 /// # use stdio_utils::StdioOverride as _;
22 /// # use std::fs::{File, read_to_string};
23 /// # use std::io::stdout;
24 /// # let mut lock = stdout().lock();
25 /// let _guard = File::create("./output.txt")?.override_stdout()?;
26 /// println!("hello world!");
27 /// let output = read_to_string("./output.txt")?;
28 /// assert_eq!(output, "hello world!\n");
29 /// # std::io::Result::Ok(())
30 /// ```
31 fn override_stdout(&self) -> Result<Guard> {
32 let stdio = Stdio::Stdout;
33 let backup = unsafe { override_stdio(self, stdio) }?;
34 let backup = Some(backup);
35 Ok(Guard { backup, stdio })
36 }
37
38 /// Replace the process standard error with a duplicate of the file descriptor.
39 ///
40 /// See [duplicate_to_stdout](StdioOverride::override_stdout).
41 fn override_stderr(&self) -> Result<Guard> {
42 let stdio = Stdio::Stderr;
43 let backup = unsafe { override_stdio(self, stdio) }?;
44 let backup = Some(backup);
45 Ok(Guard { backup, stdio })
46 }
47
48 /// Replace the process standard input with a duplicate of the file descriptor.
49 ///
50 /// ```rust
51 /// # use stdio_utils::StdioOverride as _;
52 /// # use std::fs::{File, write};
53 /// # use std::io::{stdin, stdout, read_to_string};
54 /// # let mut lock = stdout().lock();
55 /// write("./input.txt", "hello world!")?;
56 /// let _guard = File::open("./input.txt")?.override_stdin()?;
57 /// let input = read_to_string(stdin())?;
58 /// assert_eq!(input, "hello world!");
59 /// # std::io::Result::Ok(())
60 /// ```
61 fn override_stdin(&self) -> Result<Guard> {
62 let stdio = Stdio::Stdin;
63 let backup = unsafe { override_stdio(self, stdio) }?;
64 let backup = Some(backup);
65 Ok(Guard { backup, stdio })
66 }
67}
68
69impl<T: AsFd> StdioOverride for T {}
70
71pub trait AsFdExt: AsFd {
72 /// Borrow the current file descriptor as a BorrowFd.
73 ///
74 /// This method provides a cross-platform way of calling
75 /// `as_fd()` on Unix and `as_handle()` on Windows.
76 fn borrow_file(&self) -> BorrowedFd<'_> {
77 borrow_fd(self)
78 }
79
80 /// Duplicates the current file descriptor as an OwnedFd.
81 ///
82 /// This is a shorthand for to `file.borrow_fd().try_clone_to_owned()`.
83 fn duplicate_file(&self) -> Result<OwnedFd> {
84 self.borrow_file().try_clone_to_owned()
85 }
86}
87
88impl<T: AsFd> AsFdExt for T {}
89
90#[must_use]
91/// A type that restores a replaced file descriptor when it's dropped
92pub struct Guard {
93 stdio: Stdio,
94 backup: Option<OwnedFd>,
95}
96
97impl Guard {
98 /// Consume the guard without restoring the file descriptor.
99 pub fn forget(self) {
100 self.into_inner();
101 }
102
103 /// Consume the guard returning an OwnedFd with the original file descriptor
104 pub fn into_inner(mut self) -> OwnedFd {
105 self.backup.take().unwrap()
106 }
107}
108
109impl Drop for Guard {
110 fn drop(&mut self) {
111 if let Some(backup) = self.backup.take() {
112 let _ = unsafe { override_stdio(backup, self.stdio) };
113 }
114 }
115}
116
117/// Returns a file that can be written to.
118/// The file discards all bytes written to it.
119///
120/// ```rust
121/// # use stdio_utils::null;
122/// # use std::io::Write;
123/// let mut f = null()?;
124/// f.write_all(b"hello world")?;
125/// # std::io::Result::Ok(())
126/// ```
127///
128/// It can be used to dicard all the data written
129/// to stdout
130///
131/// ```rust
132/// # use stdio_utils::{null, StdioOverride as _};
133/// # use std::io::Write;
134/// # use std::io::stdout;
135/// # let mut lock = stdout().lock();
136/// let _guard = null()?.override_stdout();
137/// println!("hello world!"); // this will never print
138/// # std::io::Result::Ok(())
139/// ```
140pub fn null() -> Result<File> {
141 File::options()
142 .create(false)
143 .read(false)
144 .append(true)
145 .open(DEV_NULL)
146}