From 214650e0dd473976075ea08fa6295719768f4d11 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Thu, 2 Apr 2015 09:07:14 -0700 Subject: [PATCH 1/2] std: Feature `fs_pipe`: create OS pipes safely This allows for safely creating OS in-memory pipes and representing the reader and writer ends simply as an `std::fs::File` --- src/libstd/fs.rs | 26 +++++++++++++++++++ src/libstd/sys/unix/fs2.rs | 12 +++++++++ src/libstd/sys/windows/fs2.rs | 49 ++++++++++++++++++++++++++++++++++- 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/libstd/fs.rs b/src/libstd/fs.rs index 4e2dade9a3ca8..ef5896feccdf4 100644 --- a/src/libstd/fs.rs +++ b/src/libstd/fs.rs @@ -145,6 +145,17 @@ pub struct OpenOptions(fs_imp::OpenOptions); #[stable(feature = "rust1", since = "1.0.0")] pub struct Permissions(fs_imp::FilePermissions); +/// A low-level OS in-memory pipe. +#[unstable(feature = "fs_pipe", reason = "feature was recently added")] +pub struct Pipe { + /// A file representing the read end of the pipe. Data written + /// on the `writer` file can be read from this file. + pub reader: File, + /// A file representing the write end of the pipe. Data written + /// to this file can be read from the `reader` file. + pub writer: File, +} + impl File { /// Attempts to open a file in read-only mode. /// @@ -627,6 +638,21 @@ impl AsInner for Permissions { fn as_inner(&self) -> &fs_imp::FilePermissions { &self.0 } } +impl Pipe { + /// Creates a new low-level OS in-memory pipe. + /// + /// This function will return an error if there are no more resources + /// available to allocate a pipe. + #[unstable(feature = "fs_pipe", reason = "feature was recently added")] + pub fn new() -> io::Result { + let (reader, writer) = try!(fs_imp::pipe()); + Ok(Pipe { + reader: File::from_inner(reader), + writer: File::from_inner(writer), + }) + } +} + #[stable(feature = "rust1", since = "1.0.0")] impl Iterator for ReadDir { type Item = io::Result; diff --git a/src/libstd/sys/unix/fs2.rs b/src/libstd/sys/unix/fs2.rs index c0426af051be3..ad8bda71d011c 100644 --- a/src/libstd/sys/unix/fs2.rs +++ b/src/libstd/sys/unix/fs2.rs @@ -381,3 +381,15 @@ pub fn utimes(p: &Path, atime: u64, mtime: u64) -> io::Result<()> { try!(cvt(unsafe { c::utimes(p.as_ptr(), buf.as_ptr()) })); Ok(()) } + +pub fn pipe() -> io::Result<(File, File)> { + unsafe { + let mut fds = [0; 2]; + if let Ok(0) = cvt_r(|| libc::pipe(fds.as_mut_ptr())) { + Ok((File::from_inner(fds[0]), + File::from_inner(fds[1]))) + } else { + Err(io::Error::last_os_error()) + } + } +} diff --git a/src/libstd/sys/windows/fs2.rs b/src/libstd/sys/windows/fs2.rs index d03e45649ed05..ab395891c493e 100644 --- a/src/libstd/sys/windows/fs2.rs +++ b/src/libstd/sys/windows/fs2.rs @@ -21,7 +21,7 @@ use path::{Path, PathBuf}; use ptr; use sync::Arc; use sys::handle::Handle; -use sys::{c, cvt}; +use sys::{c, cvt, retry}; use sys_common::FromInner; use vec::Vec; @@ -432,3 +432,50 @@ pub fn utimes(p: &Path, atime: u64, mtime: u64) -> io::Result<()> { })); Ok(()) } + +pub fn pipe() -> io::Result<(File, File)> { + unsafe { + let mut fds = [0; 2]; + if retry(|| libc::pipe(fds.as_mut_ptr(), 4096, libc::O_TEXT)) == 0 { + let (read_fd, write_fd) = (fds[0], fds[1]); + let (read_handle, write_handle) = + (libc::get_osfhandle(read_fd) as libc::HANDLE, + libc::get_osfhandle(write_fd) as libc::HANDLE); + + if read_handle == libc::INVALID_HANDLE_VALUE || + write_handle == libc::INVALID_HANDLE_VALUE + { + // Capture the error which caused us to fail + let err = io::Error::last_os_error(); + + // Cleanup file descriptors in unlikely event that + // they are created but we couldn't get the handles + if read_fd != -1 { + retry(|| libc::close(read_fd)); + } + + if write_fd != -1 { + retry(|| libc::close(write_fd)); + } + + return Err(err); + } + + let read_file = File { + handle: Handle::new(read_handle), + read_fd: Some(read_fd), + write_fd: None, + }; + + let write_file = File { + handle: Handle::new(write_handle), + read_fd: None, + write_fd: Some(write_fd), + }; + + Ok((read_file, write_file)) + } else { + Err(io::Error::last_os_error()) + } + } +} From 0222f54cd9e529b82ea72ffea7f69b0e7e1affe5 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 28 Mar 2015 11:06:57 -0700 Subject: [PATCH 2/2] std: Feature `process_redirect`: Redirect command stdio to file handles This allows redirection of stdio for `std::process::Command` to already open file handles, or to OS pipes created via `std::fs::Pipe` --- src/libstd/fs.rs | 3 + src/libstd/process.rs | 298 ++++++++++++++++++++++++++++++++ src/libstd/sys/unix/fs2.rs | 7 + src/libstd/sys/unix/pipe2.rs | 19 +- src/libstd/sys/windows/fs2.rs | 55 +++++- src/libstd/sys/windows/pipe2.rs | 27 ++- 6 files changed, 396 insertions(+), 13 deletions(-) diff --git a/src/libstd/fs.rs b/src/libstd/fs.rs index ef5896feccdf4..bf88c0fa4b138 100644 --- a/src/libstd/fs.rs +++ b/src/libstd/fs.rs @@ -310,6 +310,9 @@ impl File { impl AsInner for File { fn as_inner(&self) -> &fs_imp::File { &self.inner } } +impl AsInnerMut for File { + fn as_inner_mut(&mut self) -> &mut fs_imp::File { &mut self.inner } +} impl FromInner for File { fn from_inner(f: fs_imp::File) -> File { File { inner: f, path: None } diff --git a/src/libstd/process.rs b/src/libstd/process.rs index 52f5965db809a..114f2b0d22225 100644 --- a/src/libstd/process.rs +++ b/src/libstd/process.rs @@ -18,6 +18,7 @@ use io::prelude::*; use ffi::OsStr; use fmt; +use fs::File; use io::{self, Error, ErrorKind}; use libc; use path; @@ -347,6 +348,9 @@ fn setup_io(io: &StdioImp, fd: libc::c_int, readable: bool) (Some(writer), Some(reader)) } } + Redirect(ref pipe) => { + (Some(pipe.clone()), None) + } }) } @@ -375,6 +379,7 @@ enum StdioImp { Piped, Inherit, Null, + Redirect(AnonPipe), } impl Stdio { @@ -390,6 +395,58 @@ impl Stdio { /// stream to `/dev/null` #[stable(feature = "process", since = "1.0.0")] pub fn null() -> Stdio { Stdio(StdioImp::Null) } + + /// This stream will use a currently opened file. The file must have been + /// opened with the appropriate read/write permissions. + /// + /// Note: this call will make an internal duplicate of the file handle, so + /// when using a file handle created as part of a pipe (e.g. from `std::fs::Pipe`) + /// keep in mind that an extra copy of it will remain open until it is dropped. + /// Thus it is possible to deadlock when reading from the pipe if writers to that + /// pipe remain open, e.g. the original argument to this method and its returned value. + /// + /// ``` + /// #![feature(fs_pipe)] + /// #![feature(process_redirect)] + /// use std::process::{Command, Stdio}; + /// use std::fs::Pipe; + /// use std::io::Write; + /// + /// let pipe = Pipe::new().unwrap_or_else(|e| { + /// panic!("unable to create a pipe: {}", e) + /// }); + /// + /// let mut write_to_cmd = pipe.writer; + /// let mut cmd_stdin = pipe.reader; + /// + /// let mut cmd = Command::new("cat"); + /// cmd.stdout(Stdio::piped()); + /// cmd.stdin(Stdio::redirect(&mut cmd_stdin, false).unwrap_or_else(|e| { + /// panic!("unable to redirect stdin: {}", e); + /// })); + /// + /// let child = cmd.spawn().unwrap_or_else(|e| { + /// panic!("failed to spawn process: {}", e); + /// }); + /// + /// // This indirectly drops the extra writer copy of + /// // the pipe, only write_to_cmd remains. + /// drop(cmd); + /// + /// write_to_cmd.write_all("hello world!".as_bytes()).unwrap(); + /// write_to_cmd.flush().unwrap(); + /// drop(write_to_cmd); // Nothing else to write, signal that we are done! + /// + /// let output = child.wait_with_output().unwrap_or_else(|e| { + /// panic!("failed to get child's output: {}", e); + /// }); + /// + /// assert_eq!("hello world!", String::from_utf8(output.stdout).unwrap()); + /// ``` + #[unstable(feature = "process_redirect", reason = "feature was recently added")] + pub fn redirect(f: &mut File, writable: bool) -> io::Result { + Ok(Stdio(StdioImp::Redirect(try!(f.as_inner_mut().dup_as_anon_pipe(writable))))) + } } /// Describes the result of a process after it has terminated. @@ -542,6 +599,7 @@ mod tests { use prelude::v1::*; use io::prelude::*; + use fs::{File, OpenOptions, Pipe, remove_file}; use io::ErrorKind; use old_path::{self, GenericPath}; use old_io::fs::PathExtensions; @@ -549,6 +607,20 @@ mod tests { use str; use super::{Command, Output, Stdio}; + // Poor man's mktemp + macro_rules! unique_test_path { + () => { + { + use env; + use path::Path; + let name = Path::new(file!()).file_name().unwrap().to_str().unwrap(); + let mut path = env::temp_dir(); + path.set_file_name(format!("rust-test-{}:{}", name, line!())); + path + } + }; + } + // FIXME(#10380) these tests should not all be ignored on android. #[cfg(not(target_os="android"))] @@ -879,4 +951,230 @@ mod tests { assert!(output.contains("RUN_TEST_NEW_ENV=123"), "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output); } + + #[cfg(not(target_os="android"))] + fn shell_cmd(c_flag: bool) -> Command { + let mut cmd = Command::new("sh"); + if c_flag { cmd.arg("-c"); } + cmd + } + + #[cfg(target_os="android")] + fn shell_cmd(c_flag: bool) -> Command { + let mut cmd = Command::new("/system/bin/sh"); + if c_flag { cmd.arg("-c"); } + cmd + } + + #[test] + fn test_redirect_stdio_with_file() { + let in_path = unique_test_path!(); + let out_path = unique_test_path!(); + let err_path = unique_test_path!(); + + { + let in_path = in_path.clone(); + let mut in_file = File::create(in_path).ok().expect("failed to open stdin file"); + in_file.write_all("echo stdout\necho stderr 1>&2\n".as_bytes()).unwrap(); + in_file.flush().unwrap(); + } + + { + let in_path = in_path.clone(); + let out_path = out_path.clone(); + let err_path = err_path.clone(); + + let mut in_file = File::open(in_path).ok().expect("failed to open stdout file"); + let mut out_file = File::create(out_path).ok().expect("failed to open stdout file"); + let mut err_file = File::create(err_path).ok().expect("failed to open stderr file"); + + let mut cmd = shell_cmd(false); + cmd.stdin(Stdio::redirect(&mut in_file, false).unwrap()); + cmd.stdout(Stdio::redirect(&mut out_file, true).unwrap()); + cmd.stderr(Stdio::redirect(&mut err_file, true).unwrap()); + + assert!(cmd.status().ok().expect("unabled to spawn child").success()); + } + + let mut out_file = File::open(out_path.clone()).ok().expect("missing stdout file"); + let mut err_file = File::open(err_path.clone()).ok().expect("missing stderr file"); + + let mut stdout = String::new(); + let mut stderr = String::new(); + + out_file.read_to_string(&mut stdout).ok().expect("failed to read from stdout file"); + err_file.read_to_string(&mut stderr).ok().expect("failed to read from stderr file"); + + assert_eq!(stdout, "stdout\n"); + assert_eq!(stderr, "stderr\n"); + + let _ = remove_file(in_path); + let _ = remove_file(out_path); + let _ = remove_file(err_path); + } + + #[test] + fn test_redirect_reuse() { + let out_path = unique_test_path!(); + + { + let out_path = out_path.clone(); + let mut out_file = File::create(out_path).ok().expect("failed to open stdout file"); + + let mut cmd = shell_cmd(true); + cmd.arg("echo stdout\necho stderr 1>&2\n"); + cmd.stdout(Stdio::redirect(&mut out_file, true).unwrap()); + cmd.stderr(Stdio::redirect(&mut out_file, true).unwrap()); + + assert!(cmd.status().ok().expect("unabled to spawn child").success()); + } + + let mut out_file = File::open(out_path.clone()).ok().expect("missing stdout file"); + + let mut stdout = String::new(); + out_file.read_to_string(&mut stdout).ok().expect("failed to read from stdout file"); + assert_eq!(stdout, "stdout\nstderr\n"); + + let _ = remove_file(out_path); + } + + #[test] + fn test_redirect_reuse_as_stdin_and_stdout() { + use io::SeekFrom; + + let path = unique_test_path!(); + let mut file = OpenOptions::new().create(true).truncate(true) + .read(true).write(true).open(path.clone()).ok().expect("failed to open file"); + + let mut cmd_writer = shell_cmd(true); + cmd_writer.arg("echo 'echo stdout\necho stderr 1>&2\n'"); + cmd_writer.stdout(Stdio::redirect(&mut file, true).unwrap()); + + let mut cmd_reader = shell_cmd(false); + cmd_reader.stdin(Stdio::redirect(&mut file, false).unwrap()); + + assert!(cmd_writer.status().ok().expect("unabled to spawn writer").success()); + + file.seek(SeekFrom::Start(0)).unwrap(); + + let output = cmd_reader.output().ok().expect("unable to spawn reader"); + assert_eq!(output.stdout, "stdout\n".as_bytes()); + assert_eq!(output.stderr, "stderr\n".as_bytes()); + + let _ = remove_file(path); + } + + #[test] + fn test_redirect_file_remains_valid() { + let out_path = unique_test_path!(); + let mut out_file = File::create(out_path.clone()) + .ok().expect("failed to open stdout file for writing"); + out_file.write_all("pre-cmd write\n".as_bytes()).unwrap(); + out_file.flush().unwrap(); + + { + let mut cmd = shell_cmd(true); + cmd.arg("echo stdout\necho stderr 1>&2\n"); + cmd.stdout(Stdio::redirect(&mut out_file, true).unwrap()); + cmd.stderr(Stdio::redirect(&mut out_file, true).unwrap()); + + assert!(cmd.status().ok().expect("unabled to spawn child").success()); + } + + out_file.write_all("post-cmd write\n".as_bytes()).unwrap(); + out_file.flush().unwrap(); + + let mut out_read = File::open(out_path.clone()) + .ok().expect("failed to open stdout for reading"); + + let mut stdout = String::new(); + out_read.read_to_string(&mut stdout).ok().expect("failed to read from stdout file"); + assert_eq!(stdout, "pre-cmd write\nstdout\nstderr\npost-cmd write\n"); + + let _ = remove_file(out_path); + } + + #[test] + fn test_redirect_stdio_with_pipe() { + let (mut cmd_stdin, mut send_to_cmd) = { + let pipe = Pipe::new().ok().expect("unable to create pipe"); + (pipe.reader, pipe.writer) + }; + + let (mut read_from_cmd, mut cmd_stdout) = { + let pipe = Pipe::new().ok().expect("unable to create pipe"); + (pipe.reader, pipe.writer) + }; + + { + let mut cmd = shell_cmd(false); + cmd.stdin(Stdio::redirect(&mut cmd_stdin, false).unwrap()); + cmd.stdout(Stdio::redirect(&mut cmd_stdout, true).unwrap()); + cmd.stderr(Stdio::redirect(&mut cmd_stdout, true).unwrap()); + + // cmd has a copy of cmd_stdout (the pipe writer), since we don't intend + // to write anything in this end, we had better close it to avoid hanging + // while trying to read from the other end. + drop(cmd_stdout); + drop(cmd_stdin); // We also don't care about reading from the child's pipe + + let mut child = cmd.spawn().ok().expect("unsable to spawn child"); + + send_to_cmd.write("echo stdout\necho stderr 1>&2\nexit 0\n".as_bytes()).unwrap(); + drop(send_to_cmd); + + assert!(child.wait().ok().expect("unable to wait on child").success()); + + // cmd goes out of scope here and closes its (duplicated) end of the pipe + } + + let mut output = String::new(); + read_from_cmd.read_to_string(&mut output).ok().expect("failed to read from stdout file"); + assert_eq!(output, "stdout\nstderr\n"); + } + + #[test] + fn test_redirect_pipe_remains_valid() { + let (mut cmd_stdin, mut send_to_cmd) = { + let pipe = Pipe::new().ok().expect("unable to create pipe"); + (pipe.reader, pipe.writer) + }; + + let (mut read_from_cmd, mut cmd_stdout) = { + let pipe = Pipe::new().ok().expect("unable to create pipe"); + (pipe.reader, pipe.writer) + }; + + { + let mut cmd = shell_cmd(false); + cmd.stdin(Stdio::redirect(&mut cmd_stdin, false).unwrap()); + cmd.stdout(Stdio::redirect(&mut cmd_stdout, true).unwrap()); + cmd.stderr(Stdio::redirect(&mut cmd_stdout, true).unwrap()); + + // cmd has a copy of cmd_stdout (the pipe writer), since we don't intend + // to write anything in this end, we had better close it to avoid hanging + // while trying to read from the other end. + drop(cmd_stdout); + + let mut child = cmd.spawn().ok().expect("unsable to spawn child"); + + send_to_cmd.write("echo stdout\necho stderr 1>&2\nexit 0\n".as_bytes()).unwrap(); + assert!(child.wait().ok().expect("unable to wait on child").success()); + + // cmd goes out of scope here and closes its (duplicated) end of the pipe + } + + send_to_cmd.write("post cmd\n".as_bytes()).unwrap(); + + // Make sure there are no writers left before we read from the pipe + drop(send_to_cmd); + + let mut output = String::new(); + read_from_cmd.read_to_string(&mut output).unwrap(); + assert_eq!(output, "stdout\nstderr\n"); + + let mut output = String::new(); + cmd_stdin.read_to_string(&mut output).unwrap(); + assert_eq!(output, "post cmd\n"); + } } diff --git a/src/libstd/sys/unix/fs2.rs b/src/libstd/sys/unix/fs2.rs index ad8bda71d011c..8b7b75eef077b 100644 --- a/src/libstd/sys/unix/fs2.rs +++ b/src/libstd/sys/unix/fs2.rs @@ -20,6 +20,7 @@ use path::{Path, PathBuf}; use ptr; use sync::Arc; use sys::fd::FileDesc; +use sys::pipe2::AnonPipe; use sys::{c, cvt, cvt_r}; use sys_common::FromInner; use vec::Vec; @@ -56,6 +57,12 @@ pub struct OpenOptions { #[derive(Clone, PartialEq, Eq, Debug)] pub struct FilePermissions { mode: mode_t } +impl File { + pub fn dup_as_anon_pipe(&self, _writable: bool) -> io::Result { + AnonPipe::clone_fd(self.0.raw()) + } +} + impl FileAttr { pub fn is_dir(&self) -> bool { (self.stat.st_mode as mode_t) & libc::S_IFMT == libc::S_IFDIR diff --git a/src/libstd/sys/unix/pipe2.rs b/src/libstd/sys/unix/pipe2.rs index 7af2c0f0b2a8e..a638fb19c393d 100644 --- a/src/libstd/sys/unix/pipe2.rs +++ b/src/libstd/sys/unix/pipe2.rs @@ -10,6 +10,8 @@ use prelude::v1::*; +use sync::Arc; +use sys::cvt_r; use sys::fd::FileDesc; use io; use libc; @@ -18,7 +20,10 @@ use libc; // Anonymous pipes //////////////////////////////////////////////////////////////////////////////// -pub struct AnonPipe(FileDesc); +#[derive(Clone)] +pub struct AnonPipe { + inner: Arc +} pub unsafe fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> { let mut fds = [0; 2]; @@ -32,18 +37,22 @@ pub unsafe fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> { impl AnonPipe { pub fn from_fd(fd: libc::c_int) -> AnonPipe { - AnonPipe(FileDesc::new(fd)) + AnonPipe { inner: Arc::new(FileDesc::new(fd)) } + } + + pub fn clone_fd(fd: libc::c_int) -> io::Result { + unsafe { Ok(AnonPipe::from_fd(try!(cvt_r(|| libc::dup(fd))))) } } pub fn read(&self, buf: &mut [u8]) -> io::Result { - self.0.read(buf) + self.inner.read(buf) } pub fn write(&self, buf: &[u8]) -> io::Result { - self.0.write(buf) + self.inner.write(buf) } pub fn raw(&self) -> libc::c_int { - self.0.raw() + self.inner.raw() } } diff --git a/src/libstd/sys/windows/fs2.rs b/src/libstd/sys/windows/fs2.rs index ab395891c493e..02bd922a2ca44 100644 --- a/src/libstd/sys/windows/fs2.rs +++ b/src/libstd/sys/windows/fs2.rs @@ -21,11 +21,20 @@ use path::{Path, PathBuf}; use ptr; use sync::Arc; use sys::handle::Handle; +use sys::pipe2::AnonPipe; use sys::{c, cvt, retry}; use sys_common::FromInner; use vec::Vec; -pub struct File { handle: Handle } +pub struct File { + /// OS level file handle + handle: Handle, + /// File descriptor opened with open_osfhandle + read_fd: Option, + /// File descriptor opened with open_osfhandle + write_fd: Option, +} + pub struct FileAttr { data: c::WIN32_FILE_ATTRIBUTE_DATA } pub struct ReadDir { @@ -193,7 +202,11 @@ impl File { if handle == libc::INVALID_HANDLE_VALUE { Err(Error::last_os_error()) } else { - Ok(File { handle: Handle::new(handle) }) + Ok(File { + handle: Handle::new(handle), + read_fd: None, + write_fd: None, + }) } } @@ -262,11 +275,47 @@ impl File { } pub fn handle(&self) -> &Handle { &self.handle } + + pub fn dup_as_anon_pipe(&mut self, writable: bool) -> io::Result { + // Calling close on the handle's fd will also close the + // handle itself. Thus when the AnonPipe cleans itself up, we + // must make sure it closes a *clone* of the fd and not the + // original itself. + AnonPipe::clone_fd(unsafe { try!(self.raw_fd(writable)) }) + } + + unsafe fn raw_fd(&mut self, writable: bool) -> io::Result { + if writable { + if let Some(fd) = self.write_fd { return Ok(fd); } + } else { + if let Some(fd) = self.read_fd { return Ok(fd); } + } + + // Calling open_osfhandle repeatedly will create new + // file descriptors, so we should cache previous calls + let flag = if writable { libc:: O_APPEND } else { libc:: O_RDONLY }; + let fd = retry(|| libc::open_osfhandle(self.handle.raw() as libc::intptr_t, flag)); + if fd == -1 { + return Err(Error::last_os_error()); + } + + if writable { + self.write_fd = Some(fd); + } else { + self.read_fd = Some(fd); + } + + Ok(fd) + } } impl FromInner for File { fn from_inner(handle: libc::HANDLE) -> File { - File { handle: Handle::new(handle) } + File { + handle: Handle::new(handle), + read_fd: None, + write_fd: None, + } } } diff --git a/src/libstd/sys/windows/pipe2.rs b/src/libstd/sys/windows/pipe2.rs index 229481e3d57e6..209724251b654 100644 --- a/src/libstd/sys/windows/pipe2.rs +++ b/src/libstd/sys/windows/pipe2.rs @@ -10,7 +10,8 @@ use prelude::v1::*; -use sys::handle; +use sync::Arc; +use sys::{handle, retry}; use io; use libc::{self, c_int, HANDLE}; @@ -18,10 +19,15 @@ use libc::{self, c_int, HANDLE}; // Anonymous pipes //////////////////////////////////////////////////////////////////////////////// -pub struct AnonPipe { +struct InnerFd { fd: c_int } +#[derive(Clone)] +pub struct AnonPipe { + inner: Arc +} + pub unsafe fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> { // Windows pipes work subtly differently than unix pipes, and their // inheritance has to be handled in a different way that I do not @@ -43,11 +49,22 @@ pub unsafe fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> { impl AnonPipe { pub fn from_fd(fd: libc::c_int) -> AnonPipe { - AnonPipe { fd: fd } + AnonPipe { inner: Arc::new(InnerFd { fd: fd }) } + } + + pub fn clone_fd(fd: libc::c_int) -> io::Result { + unsafe { + let fd = retry(|| libc::dup(fd)); + if fd != -1 { + Ok(AnonPipe::from_fd(fd)) + } else { + Err(io::Error::last_os_error()) + } + } } pub fn raw(&self) -> HANDLE { - unsafe { libc::get_osfhandle(self.fd) as libc::HANDLE } + unsafe { libc::get_osfhandle(self.inner.fd) as libc::HANDLE } } pub fn read(&self, buf: &mut [u8]) -> io::Result { @@ -59,7 +76,7 @@ impl AnonPipe { } } -impl Drop for AnonPipe { +impl Drop for InnerFd { fn drop(&mut self) { // closing stdio file handles makes no sense, so never do it. Also, note // that errors are ignored when closing a file descriptor. The reason