1use std::io;
50use std::io::{Stdout, Stderr};
51use std::fs::{File, OpenOptions};
52
53pub trait Redirectable<T: ?Sized>
63{
64 fn redirect(&self, destination: &T) -> io::Result<()>;
87}
88
89#[cfg(any(unix))]
90mod platform
91{
92 use super::*;
93 use std::os::fd::{AsRawFd, RawFd};
94
95 pub type Descriptor = RawFd;
96
97 pub trait Descriptable: AsRawFd {}
98 impl<T: AsRawFd> Descriptable for T {}
99
100 impl<T1: Descriptable, T2: Descriptable> Redirectable<T2> for T1 {
101 fn redirect(&self, destination: &T2) -> io::Result<()> {
102 let src_fd = self.as_raw_fd();
103 let dst_fd = destination.as_raw_fd();
104 return crate::libc_common::redirect_fd_to_fd(src_fd, dst_fd);
105 }
106 }
107}
108
109#[cfg(any(target_os = "windows"))]
110mod platform
111{
112 use super::*;
113 use std::os::windows::io::AsRawHandle;
114
115 pub trait Descriptable: AsRawHandle {}
116 impl<T: AsRawHandle> Descriptable for T {}
117
118 #[cfg(feature = "libc_on_windows")]
119 mod libc_backend
120 {
121 use super::*;
122 use crate::{Descriptable, Redirectable};
123 use libc::{c_int, open_osfhandle};
124
125 pub type Descriptor = c_int;
126
127 impl<T1: Descriptable, T2: Descriptable> Redirectable<T2> for T1 {
128 fn redirect(&self, destination: &T2) -> io::Result<()> {
129 let src_handle = self.as_raw_handle() as isize;
130 let dst_handle = destination.as_raw_handle() as isize;
131
132 let src_fd = unsafe { open_osfhandle(src_handle, 0) };
133 if src_fd < 0 {
134 return Err(io::Error::last_os_error());
135 }
136
137 let dst_fd = unsafe { open_osfhandle(dst_handle, 0) };
138 if dst_fd < 0 {
139 return Err(io::Error::last_os_error());
140 }
141
142 return crate::libc_common::redirect_fd_to_fd(src_fd, dst_fd);
143 }
144 }
145 }
146
147 #[cfg(feature = "libc_on_windows")]
148 pub use libc_backend::*;
149
150 #[cfg(feature = "windows-sys")]
151 mod windows_sys_backend
152 {
153 use super::*;
154 use windows_sys::Win32::Foundation::HANDLE;
155 use windows_sys::Win32::System::Console::{SetStdHandle, STD_ERROR_HANDLE, STD_HANDLE, STD_OUTPUT_HANDLE};
156
157 impl<T: Descriptable> Redirectable<T> for Stdout {
158 fn redirect(&self, destination: &T) -> io::Result<()> {
159 redirect_using_setstdhandle(STD_OUTPUT_HANDLE, destination)
160 }
161 }
162
163 impl<T: Descriptable> Redirectable<T> for Stderr {
164 fn redirect(&self, destination: &T) -> io::Result<()> {
165 redirect_using_setstdhandle(STD_ERROR_HANDLE, destination)
166 }
167 }
168
169 fn redirect_using_setstdhandle<T: Descriptable>(std_handle: STD_HANDLE, destination: &T) -> io::Result<()> {
170 let dst_handle = destination.as_raw_handle() as HANDLE;
171 let result = unsafe { SetStdHandle(std_handle, dst_handle) };
172 if result == 0 {
173 return Err(io::Error::last_os_error());
174 }
175 return Ok(());
176 }
177 }
178
179 #[cfg(feature = "windows-sys")]
180 pub use windows_sys_backend::*;
181}
182
183#[cfg(any(all(unix, feature = "libc_on_unix"), all(target_os = "windows", feature = "libc_on_windows")))]
184mod libc_common
185{
186 use super::*;
187 use crate::platform::Descriptor;
188 use libc::dup2;
189
190 pub fn redirect_fd_to_fd(src: Descriptor, dst: Descriptor) -> io::Result<()> {
191 let result = unsafe {
192 dup2(dst, src)
193 };
196 if result < 0 {
197 return Err(io::Error::last_os_error());
198 }
199
200 return Ok(());
201 }
202}
203
204#[cfg(any(all(unix, feature = "libc_on_unix"), all(target_os = "windows", feature = "libc_on_windows")))]
205mod libc_convenience
206{
207 use super::*;
208 use std::fs::OpenOptions;
209 use std::path::Path;
210
211 impl<T: Descriptable> Redirectable<Path> for T {
212 fn redirect(&self, destination: &Path) -> io::Result<()> {
213 let dst = OpenOptions::new().read(false).write(true).create(true).append(true).open(destination)?;
214 let result = self.redirect(&dst);
215 if result.is_ok() {
216 std::mem::forget(dst);
217 }
218 return result;
219 }
220 }
221}
222
223#[cfg(any(all(unix, feature = "libc_on_unix"), all(target_os = "windows", feature = "libc_on_windows")))]
224pub use libc_convenience::*;
225
226mod convenience
227{
228 use super::*;
229 use std::fs::OpenOptions;
230 use std::io::{stderr, stdout};
231 use std::path::Path;
232 pub fn redirect_std_to_path(destination: &Path, append: bool) -> io::Result<()> {
233 let dst = OpenOptions::new().read(false).write(true).create(true).append(append).open(destination)?;
234 stdout().redirect(&dst)?;
235 stderr().redirect(&dst)?;
236 std::mem::forget(dst);
237 return Ok(());
238 }
239}
240
241pub use convenience::*;
242pub use platform::*;
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use std::io::{Read, Write};
248 use std::mem::ManuallyDrop;
249 use libc::close;
250
251 #[cfg(any(all(unix, feature = "libc_on_unix"), all(target_os = "windows", feature = "libc_on_windows")))]
252 #[test]
253 fn redirects_file_to_file() {
254 let tempdir = tempfile::tempdir().unwrap();
256 let mut file1 = File::create(tempdir.path().join("file1.txt")).unwrap();
257 let mut file2 = File::create(tempdir.path().join("file2.txt")).unwrap();
258
259 file1.redirect(&file2).unwrap();
261 file1.write_all(b"Hello,").unwrap();
262 file1.flush().unwrap();
263 file2.write_all(b" World!").unwrap();
264 file2.flush().unwrap();
265
266 let mut dst_file = File::open(tempdir.path().join("file2.txt")).unwrap();
268 let mut dst_contents = String::new();
269 dst_file.read_to_string(&mut dst_contents).unwrap();
270 assert_eq!(dst_contents, "Hello, World!");
271
272 let mut old_file1_contents = String::new();
273 let mut old_file1 = File::open(tempdir.path().join("file1.txt")).unwrap();
274 old_file1.read_to_string(&mut old_file1_contents).unwrap();
275 assert_eq!(old_file1_contents, "");
276 }
277
278 #[cfg(any(all(unix, feature = "libc_on_unix"), all(target_os = "windows", feature = "libc_on_windows")))]
279 #[test]
280 fn redirects_file_to_path() {
281 let tempdir = tempfile::tempdir().unwrap();
283 let src_path = tempdir.path().join("src.txt");
284 let dst_path = tempdir.path().join("dst.txt");
285 let mut src = OpenOptions::new().create(true).read(true).write(true).open(&src_path).unwrap();
286
287 src.redirect(dst_path.as_path()).unwrap();
289 src.write_all(b"abc").unwrap();
290 src.flush().unwrap();
291
292 let mut dst_contents = String::new();
294 File::open(&dst_path).unwrap().read_to_string(&mut dst_contents).unwrap();
295 assert_eq!(dst_contents, "abc");
296
297 let mut original_contents = String::new();
298 File::open(&src_path).unwrap().read_to_string(&mut original_contents).unwrap();
299 assert_eq!(original_contents, "");
300 }
301
302 #[cfg(any(all(unix, feature = "libc_on_unix"), all(target_os = "windows", feature = "libc_on_windows")))]
303 #[test]
304 fn errors_on_redirect_to_directory() {
305 let tempdir = tempfile::tempdir().unwrap();
307 let dir_path = tempdir.path();
308 let src = File::create(dir_path.join("somefile.txt")).unwrap();
309
310 let err = src.redirect(dir_path).unwrap_err();
312
313 assert!(err.raw_os_error().is_some());
315 }
316
317 #[cfg(any(all(unix, feature = "libc_on_unix"), all(target_os = "windows", feature = "libc_on_windows")))]
318 #[test]
319 fn errors_on_redirect_with_missing_parent_directory() {
320 let tempdir = tempfile::tempdir().unwrap();
322 let src = File::create(tempdir.path().join("s.txt")).unwrap();
323 let bad_path = tempdir.path().join("no_such_dir").join("f.txt");
324
325 let err = src.redirect(bad_path.as_path()).unwrap_err();
327
328 assert!(err.raw_os_error().is_some());
330 }
331
332 #[cfg(any(all(unix, feature = "libc_on_unix")))]
333 #[test]
334 fn errors_on_redirect_to_closed_fd() {
335 use std::os::fd::AsRawFd;
336 let tempdir = tempfile::tempdir().unwrap();
338 let src_file = File::create(tempdir.path().join("src.txt")).unwrap();
339 let dst_file = File::create(tempdir.path().join("dst.txt")).unwrap();
340
341 let dst_file = ManuallyDrop::new(dst_file);
342 let fd = dst_file.as_raw_fd();
343 unsafe { close(fd) };
344
345 let err = src_file.redirect(&*dst_file).unwrap_err();
347
348 assert!(err.raw_os_error().is_some());
350 }
351}