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