Skip to main content

redirect_stderr/
lib.rs

1// #[cfg(target_family = "unix")]
2// fn get_stdout() -> Result<RawFd, Error> {
3//     use std::os::unix::io::AsRawFd;
4//     use std::io::stdout;
5//     Ok(stdout().as_raw_fd())
6// }
7
8// #[cfg(target_family = "windows")]
9// fn get_stdout() -> Result<std::os::windows::io::RawHandle, dyn
10// std::error::Error> {     use std::io::stdout;
11//     Ok(stdout().as_raw_handle())
12// }
13
14// use std::os::fd::FromRawFd as _;
15#[cfg(target_family = "windows")]
16static mut WINAPI_STDERR_HANDLE: windows_sys::Win32::Foundation::HANDLE =
17  std::ptr::null_mut();
18#[cfg(not(target_os = "windows"))]
19static mut UNIX_STDERR_HANDLE: i32 = -1;
20
21#[cfg(target_family = "windows")]
22static mut WINAPI_STDOUT_HANDLE: windows_sys::Win32::Foundation::HANDLE =
23  std::ptr::null_mut();
24#[cfg(not(target_os = "windows"))]
25static mut UNIX_STDOUT_HANDLE: i32 = -1;
26
27pub fn redirect_stderr() -> std::io::Result<()> {
28  use std::fs::File;
29  use std::io::{self};
30
31  #[allow(unused_variables)]
32  let dev_null = if cfg!(target_os = "windows") {
33    File::create("NUL")?
34  } else {
35    File::create("/dev/null")?
36  };
37
38  #[cfg(target_os = "windows")]
39  {
40    use std::os::windows::io::AsRawHandle;
41    use windows_sys::Win32::Foundation::{
42      HANDLE_FLAG_INHERIT, SetHandleInformation,
43    };
44    use windows_sys::Win32::System::Console::{STD_ERROR_HANDLE, SetStdHandle};
45
46    unsafe {
47      // Ensure the handle is not inherited
48      let handle =
49        dev_null.as_raw_handle() as windows_sys::Win32::Foundation::HANDLE;
50      SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);
51
52      if (WINAPI_STDERR_HANDLE != handle) {
53        WINAPI_STDERR_HANDLE = std::io::stdout().as_raw_handle()
54          as windows_sys::Win32::Foundation::HANDLE;
55      }
56
57      // Redirect stderr to NUL
58      if SetStdHandle(STD_ERROR_HANDLE, handle) == 0 {
59        return Err(io::Error::last_os_error());
60      }
61    }
62  }
63
64  #[cfg(not(target_os = "windows"))]
65  {
66    use libc;
67    use std::os::unix::io::AsRawFd;
68
69    let dev_null_fd = dev_null.as_raw_fd();
70
71    unsafe {
72      // Save the real stderr fd (not stderr's *current* fd compared against
73      // dev_null) so a later `restore_stderr` can put it back. Close any
74      // previously-saved dup before overwriting.
75      if UNIX_STDERR_HANDLE != -1 {
76        libc::close(UNIX_STDERR_HANDLE);
77      }
78      UNIX_STDERR_HANDLE = libc::dup(libc::STDERR_FILENO);
79      if UNIX_STDERR_HANDLE == -1 {
80        return Err(io::Error::last_os_error());
81      }
82
83      if libc::dup2(dev_null_fd, libc::STDERR_FILENO) == -1 {
84        return Err(io::Error::last_os_error());
85      }
86    }
87  }
88
89  Ok(())
90}
91
92pub fn restore_stderr() -> std::io::Result<()> {
93  use std::io::{self};
94
95  #[cfg(target_os = "windows")]
96  {
97    use windows_sys::Win32::System::Console::{STD_ERROR_HANDLE, SetStdHandle};
98
99    unsafe {
100      if SetStdHandle(STD_ERROR_HANDLE, WINAPI_STDERR_HANDLE) == 0 {
101        return Err(io::Error::last_os_error());
102      }
103    }
104  }
105
106  #[cfg(not(target_os = "windows"))]
107  {
108    use libc;
109
110    unsafe {
111      if UNIX_STDERR_HANDLE == -1 {
112        return Ok(());
113      }
114      if libc::dup2(UNIX_STDERR_HANDLE, libc::STDERR_FILENO) == -1 {
115        return Err(io::Error::last_os_error());
116      }
117      libc::close(UNIX_STDERR_HANDLE);
118      UNIX_STDERR_HANDLE = -1;
119    }
120  }
121
122  Ok(())
123}
124
125pub fn redirect_stdout() -> std::io::Result<()> {
126  use std::fs::File;
127  use std::io::{self};
128
129  #[allow(unused_variables)]
130  let dev_null = if cfg!(target_os = "windows") {
131    File::create("NUL")?
132  } else {
133    File::create("/dev/null")?
134  };
135
136  #[cfg(target_os = "windows")]
137  {
138    use std::os::windows::io::AsRawHandle;
139    use windows_sys::Win32::Foundation::{
140      HANDLE_FLAG_INHERIT, SetHandleInformation,
141    };
142    use windows_sys::Win32::System::Console::{
143      STD_OUTPUT_HANDLE, SetStdHandle,
144    };
145
146    unsafe {
147      // Ensure the handle is not inherited
148      let handle =
149        dev_null.as_raw_handle() as windows_sys::Win32::Foundation::HANDLE;
150      SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);
151
152      if (WINAPI_STDOUT_HANDLE != handle) {
153        WINAPI_STDOUT_HANDLE = std::io::stdout().as_raw_handle()
154          as windows_sys::Win32::Foundation::HANDLE;
155      }
156
157      // Redirect stderr to NUL
158      if SetStdHandle(STD_OUTPUT_HANDLE, handle) == 0 {
159        return Err(io::Error::last_os_error());
160      }
161    }
162  }
163
164  #[cfg(not(target_os = "windows"))]
165  {
166    use libc;
167    use std::os::unix::io::AsRawFd;
168
169    // Use the `dev_null` File bound above. A previous version opened
170    // /dev/null again inline and then read `.as_raw_fd()` off the dropped
171    // temporary, leaving the local `dev_null_fd` pointing at an already-
172    // closed descriptor — the subsequent `dup2` then silently failed and
173    // stdout was never actually redirected on Unix.
174    let original_fd = io::stdout().as_raw_fd();
175    let dev_null_fd = dev_null.as_raw_fd();
176
177    unsafe {
178      // Save the original stdout fd the first time we're called so a later
179      // `restore_stdout` can put it back. Close the previously-saved dup if
180      // we're stacking redirects, to avoid leaking fds.
181      if UNIX_STDOUT_HANDLE != -1 {
182        libc::close(UNIX_STDOUT_HANDLE);
183      }
184      UNIX_STDOUT_HANDLE = libc::dup(original_fd);
185      if UNIX_STDOUT_HANDLE == -1 {
186        return Err(io::Error::last_os_error());
187      }
188
189      if libc::dup2(dev_null_fd, original_fd) == -1 {
190        return Err(io::Error::last_os_error());
191      }
192    }
193  }
194
195  Ok(())
196}
197
198pub fn restore_stdout() -> std::io::Result<()> {
199  use std::io::{self};
200
201  #[cfg(target_os = "windows")]
202  {
203    use windows_sys::Win32::System::Console::{
204      STD_OUTPUT_HANDLE, SetStdHandle,
205    };
206
207    unsafe {
208      if SetStdHandle(STD_OUTPUT_HANDLE, WINAPI_STDOUT_HANDLE) == 0 {
209        return Err(io::Error::last_os_error());
210      }
211    }
212  }
213
214  #[cfg(not(target_os = "windows"))]
215  {
216    use libc;
217    use std::os::unix::io::AsRawFd;
218
219    let original_fd = io::stdout().as_raw_fd();
220
221    unsafe {
222      if UNIX_STDOUT_HANDLE == -1 {
223        // Nothing to restore.
224        return Ok(());
225      }
226      if libc::dup2(UNIX_STDOUT_HANDLE, original_fd) == -1 {
227        return Err(io::Error::last_os_error());
228      }
229      // We're done with the saved fd; release it and reset the sentinel
230      // so a follow-up `redirect_stdout` saves the (now restored) original
231      // rather than overwriting the saved dup it still holds.
232      libc::close(UNIX_STDOUT_HANDLE);
233      UNIX_STDOUT_HANDLE = -1;
234    }
235  }
236
237  Ok(())
238}