diff --git a/src/libstd/io/mod.rs b/src/libstd/io/mod.rs index 5ed10eab15b80..35ab9035f2edd 100644 --- a/src/libstd/io/mod.rs +++ b/src/libstd/io/mod.rs @@ -259,7 +259,7 @@ pub use self::net::ip::IpAddr; pub use self::net::tcp::TcpListener; pub use self::net::tcp::TcpStream; pub use self::net::udp::UdpStream; -pub use self::pipe::PipeStream; +pub use self::pipe::{PipeReader,PipeWriter}; pub use self::process::{Process, Command}; pub use self::tempfile::TempDir; diff --git a/src/libstd/io/pipe.rs b/src/libstd/io/pipe.rs index 41676cdf6e9cc..a3c6037c9002a 100644 --- a/src/libstd/io/pipe.rs +++ b/src/libstd/io/pipe.rs @@ -25,19 +25,74 @@ use sys_common; use sys; use sys::fs::FileDesc as FileDesc; -/// A synchronous, in-memory pipe. -pub struct PipeStream { +#[deriving(Clone)] +struct PipeImpl { inner: Arc } +/// The reading end of a pipe +#[deriving(Clone)] +pub struct PipeReader { + inner: PipeImpl +} + +/// The writing end of a pipe +#[deriving(Clone)] +pub struct PipeWriter { + inner: PipeImpl +} + pub struct PipePair { - pub reader: PipeStream, - pub writer: PipeStream, + pub reader: PipeReader, + pub writer: PipeWriter, +} + +impl PipePair { + /// Creates a pair of in-memory OS pipes for a unidirectional communication + /// stream. + /// + /// The structure returned contains a reader and writer I/O object. Data + /// written to the writer can be read from the reader. + /// + /// # Errors + /// + /// This function can fail to succeed if the underlying OS has run out of + /// available resources to allocate a new pipe. + pub fn new() -> IoResult { + let (reader, writer) = try!(unsafe { sys::os::pipe() }); + Ok(PipePair { + reader: PipeReader::from_filedesc(reader), + writer: PipeWriter::from_filedesc(writer), + }) + } +} + +impl PipeImpl { + fn open(fd: libc::c_int) -> IoResult { + Ok(PipeImpl::from_filedesc(FileDesc::new(fd, true))) + } + + #[doc(hidden)] + fn from_filedesc(fd: FileDesc) -> PipeImpl { + PipeImpl { inner: Arc::new(fd) } + } +} + +impl sys_common::AsInner for PipeReader { + fn as_inner(&self) -> &sys::fs::FileDesc { + &*self.inner.inner + } +} + +impl sys_common::AsInner for PipeWriter { + fn as_inner(&self) -> &sys::fs::FileDesc { + &*self.inner.inner + } } -impl PipeStream { - /// Consumes a file descriptor to return a pipe stream that will have - /// synchronous, but non-blocking reads/writes. This is useful if the file +impl PipeReader { + /// Consumes a file descriptor to return a pipe reader that will have + /// synchronous, but non-blocking reads. This is useful if the file /// descriptor is acquired via means other than the standard methods. /// /// This operation consumes ownership of the file descriptor and it will be @@ -49,64 +104,69 @@ impl PipeStream { /// # #![allow(unused_must_use)] /// extern crate libc; /// - /// use std::io::pipe::PipeStream; + /// use std::io::pipe::PipeReader; /// /// fn main() { - /// let mut pipe = PipeStream::open(libc::STDERR_FILENO); - /// pipe.write(b"Hello, stderr!"); + /// let mut pipe = PipeReader::open(libc::STDIN_FILENO); + /// let mut buf = [0, ..10]; + /// pipe.read(&mut buf).unwrap(); /// } /// ``` - pub fn open(fd: libc::c_int) -> IoResult { - Ok(PipeStream::from_filedesc(FileDesc::new(fd, true))) + pub fn open(fd: libc::c_int) -> IoResult { + PipeImpl::open(fd).map(|x| PipeReader { inner: x }) } // FIXME: expose this some other way /// Wrap a FileDesc directly, taking ownership. #[doc(hidden)] - pub fn from_filedesc(fd: FileDesc) -> PipeStream { - PipeStream { inner: Arc::new(fd) } + pub fn from_filedesc(fd: FileDesc) -> PipeReader { + PipeReader { inner: PipeImpl::from_filedesc(fd) } } - /// Creates a pair of in-memory OS pipes for a unidirectional communication - /// stream. +} + +impl PipeWriter { + /// Consumes a file descriptor to return a pipe writer that will have + /// synchronous, but non-blocking writes. This is useful if the file + /// descriptor is acquired via means other than the standard methods. /// - /// The structure returned contains a reader and writer I/O object. Data - /// written to the writer can be read from the reader. + /// This operation consumes ownership of the file descriptor and it will be + /// closed once the object is deallocated. /// - /// # Errors + /// # Example /// - /// This function can fail to succeed if the underlying OS has run out of - /// available resources to allocate a new pipe. - pub fn pair() -> IoResult { - let (reader, writer) = try!(unsafe { sys::os::pipe() }); - Ok(PipePair { - reader: PipeStream::from_filedesc(reader), - writer: PipeStream::from_filedesc(writer), - }) - } -} - -impl sys_common::AsInner for PipeStream { - fn as_inner(&self) -> &sys::fs::FileDesc { - &*self.inner + /// ```{rust,no_run} + /// # #![allow(unused_must_use)] + /// extern crate libc; + /// + /// use std::io::pipe::PipeWriter; + /// + /// fn main() { + /// let mut pipe = PipeWriter::open(libc::STDERR_FILENO); + /// pipe.write(b"Hello, World!"); + /// } + /// ``` + pub fn open(fd: libc::c_int) -> IoResult { + PipeImpl::open(fd).map(|x| PipeWriter { inner: x }) } -} -impl Clone for PipeStream { - fn clone(&self) -> PipeStream { - PipeStream { inner: self.inner.clone() } + // FIXME: expose this some other way + /// Wrap a FileDesc directly, taking ownership. + #[doc(hidden)] + pub fn from_filedesc(fd: FileDesc) -> PipeWriter { + PipeWriter { inner: PipeImpl::from_filedesc(fd) } } } -impl Reader for PipeStream { +impl Reader for PipeReader { fn read(&mut self, buf: &mut [u8]) -> IoResult { - self.inner.read(buf) + self.inner.inner.read(buf) } } -impl Writer for PipeStream { +impl Writer for PipeWriter { fn write(&mut self, buf: &[u8]) -> IoResult<()> { - self.inner.write(buf) + self.inner.inner.write(buf) } } @@ -117,11 +177,11 @@ mod test { #[test] fn partial_read() { use os; - use io::pipe::PipeStream; + use io::pipe::{PipeReader,PipeWriter}; let os::Pipe { reader, writer } = unsafe { os::pipe().unwrap() }; - let out = PipeStream::open(writer); - let mut input = PipeStream::open(reader); + let out = PipeWriter::open(writer); + let mut input = PipeReader::open(reader); let (tx, rx) = channel(); spawn(proc() { let mut out = out; diff --git a/src/libstd/io/process.rs b/src/libstd/io/process.rs index d4d24c1e12fc8..01707951ce73d 100644 --- a/src/libstd/io/process.rs +++ b/src/libstd/io/process.rs @@ -28,7 +28,7 @@ use collections::HashMap; use hash::Hash; #[cfg(windows)] use std::hash::sip::SipState; -use io::pipe::{PipeStream, PipePair}; +use io::pipe::{PipePair,PipeReader,PipeWriter}; use path::BytesContainer; use sys; @@ -82,15 +82,15 @@ pub struct Process { /// Handle to the child's stdin, if the `stdin` field of this process's /// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`. - pub stdin: Option, + pub stdin: Option, /// Handle to the child's stdout, if the `stdout` field of this process's /// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`. - pub stdout: Option, + pub stdout: Option, /// Handle to the child's stderr, if the `stderr` field of this process's /// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`. - pub stderr: Option, + pub stderr: Option, } /// A representation of environment variable name @@ -208,9 +208,9 @@ impl Command { args: Vec::new(), env: None, cwd: None, - stdin: CreatePipe(true, false), - stdout: CreatePipe(false, true), - stderr: CreatePipe(false, true), + stdin: CreatePipe, + stdout: CreatePipe, + stderr: CreatePipe, uid: None, gid: None, detach: false, @@ -278,21 +278,21 @@ impl Command { } /// Configuration for the child process's stdin handle (file descriptor 0). - /// Defaults to `CreatePipe(true, false)` so the input can be written to. + /// Defaults to `CreatePipe` so the input can be written to. pub fn stdin<'a>(&'a mut self, cfg: StdioContainer) -> &'a mut Command { self.stdin = cfg; self } /// Configuration for the child process's stdout handle (file descriptor 1). - /// Defaults to `CreatePipe(false, true)` so the output can be collected. + /// Defaults to `CreatePipe` so the output can be collected. pub fn stdout<'a>(&'a mut self, cfg: StdioContainer) -> &'a mut Command { self.stdout = cfg; self } /// Configuration for the child process's stderr handle (file descriptor 2). - /// Defaults to `CreatePipe(false, true)` so the output can be collected. + /// Defaults to `CreatePipe` so the output can be collected. pub fn stderr<'a>(&'a mut self, cfg: StdioContainer) -> &'a mut Command { self.stderr = cfg; self @@ -323,9 +323,9 @@ impl Command { /// Executes the command as a child process, which is returned. pub fn spawn(&self) -> IoResult { - let (their_stdin, our_stdin) = try!(setup_io(self.stdin)); - let (their_stdout, our_stdout) = try!(setup_io(self.stdout)); - let (their_stderr, our_stderr) = try!(setup_io(self.stderr)); + let (their_stdin, our_stdin) = try!(setup_io(Direction::Write, self.stdin)); + let (our_stdout, their_stdout) = try!(setup_io(Direction::Read, self.stdout)); + let (our_stderr, their_stderr) = try!(setup_io(Direction::Read, self.stderr)); match ProcessImp::spawn(self, their_stdin, their_stdout, their_stderr) { Err(e) => Err(e), @@ -396,30 +396,36 @@ impl fmt::Show for Command { } } -fn setup_io(io: StdioContainer) -> IoResult<(Option, Option)> { - let ours; - let theirs; +enum Direction{Read,Write} + +fn setup_io(way:Direction, io: StdioContainer) + -> IoResult<(Option, Option)> { + let reader; + let writer; match io { Ignored => { - theirs = None; - ours = None; + reader = None; + writer = None; } InheritFd(fd) => { - theirs = Some(PipeStream::from_filedesc(FileDesc::new(fd, false))); - ours = None; - } - CreatePipe(readable, _writable) => { - let PipePair { reader, writer } = try!(PipeStream::pair()); - if readable { - theirs = Some(reader); - ours = Some(writer); - } else { - theirs = Some(writer); - ours = Some(reader); + match way { + Direction::Read => { + reader = Some(PipeReader::from_filedesc(FileDesc::new(fd, false))); + writer = None; + } + Direction::Write => { + reader = None; + writer = Some(PipeWriter::from_filedesc(FileDesc::new(fd, false))); + } } } + CreatePipe => { + let PipePair { reader:read, writer:write } = try!(PipePair::new()); + reader = Some(read); + writer = Some(write); + } } - Ok((theirs, ours)) + Ok((reader, writer)) } // Allow the sys module to get access to the Command state @@ -473,11 +479,7 @@ pub enum StdioContainer { /// Creates a pipe for the specified file descriptor which will be created /// when the process is spawned. - /// - /// The first boolean argument is whether the pipe is readable, and the - /// second is whether it is writable. These properties are from the view of - /// the *child* process, not the parent process. - CreatePipe(bool /* readable */, bool /* writable */), + CreatePipe, } /// Describes the result of a process after it has terminated. @@ -686,7 +688,7 @@ impl Process { /// fail. pub fn wait_with_output(mut self) -> IoResult { drop(self.stdin.take()); - fn read(stream: Option) -> Receiver>> { + fn read(stream: Option) -> Receiver>> { let (tx, rx) = channel(); match stream { Some(stream) => spawn(proc() { @@ -809,7 +811,7 @@ mod tests { #[test] fn stdout_works() { let mut cmd = Command::new("echo"); - cmd.arg("foobar").stdout(CreatePipe(false, true)); + cmd.arg("foobar").stdout(CreatePipe); assert_eq!(run_output(cmd), "foobar\n".to_string()); } @@ -819,7 +821,7 @@ mod tests { let mut cmd = Command::new("/bin/sh"); cmd.arg("-c").arg("pwd") .cwd(&Path::new("/")) - .stdout(CreatePipe(false, true)); + .stdout(CreatePipe); assert_eq!(run_output(cmd), "/\n".to_string()); } @@ -828,8 +830,8 @@ mod tests { fn stdin_works() { let mut p = Command::new("/bin/sh") .arg("-c").arg("read line; echo $line") - .stdin(CreatePipe(true, false)) - .stdout(CreatePipe(false, true)) + .stdin(CreatePipe) + .stdout(CreatePipe) .spawn().unwrap(); p.stdin.as_mut().unwrap().write("foobar".as_bytes()).unwrap(); drop(p.stdin.take()); diff --git a/src/libstd/sys/unix/ext.rs b/src/libstd/sys/unix/ext.rs index ae3c939bf78bd..9872966450c8a 100644 --- a/src/libstd/sys/unix/ext.rs +++ b/src/libstd/sys/unix/ext.rs @@ -51,7 +51,13 @@ impl AsRawFd for io::fs::File { } } -impl AsRawFd for io::pipe::PipeStream { +impl AsRawFd for io::pipe::PipeReader { + fn as_raw_fd(&self) -> Fd { + self.as_inner().fd() + } +} + +impl AsRawFd for io::pipe::PipeWriter { fn as_raw_fd(&self) -> Fd { self.as_inner().fd() } diff --git a/src/libstd/sys/unix/process.rs b/src/libstd/sys/unix/process.rs index 76c316076f93e..02695fa74ab34 100644 --- a/src/libstd/sys/unix/process.rs +++ b/src/libstd/sys/unix/process.rs @@ -10,6 +10,7 @@ use self::Req::*; use libc::{mod, pid_t, c_void, c_int}; +use libc::funcs::posix88::unistd::dup2; use c_str::CString; use io::{mod, IoResult, IoError}; use mem; @@ -39,6 +40,34 @@ enum Req { NewChild(libc::pid_t, Sender, u64), } +// If a stdio file descriptor is set to be ignored (via a -1 file +// descriptor), then we don't actually close it, but rather open +// up /dev/null into that file descriptor. Otherwise, the first file +// descriptor opened up in the child would be numbered as one of the +// stdio file descriptors, which is likely to wreak havoc. +unsafe fn setup(src: Option, dst: c_int, devnull:&CString, ) -> bool + where T : AsInner { + let src = match src { + None => { + let flags = if dst == libc::STDIN_FILENO { + libc::O_RDONLY + } else { + libc::O_RDWR + }; + libc::open(devnull.as_ptr(), flags, 0) + } + Some(obj) => { + let fd = obj.as_inner().fd(); + // Leak the memory and the file descriptor. We're in the + // child now an all our resources are going to be + // cleaned up very soon + mem::forget(obj); + fd + } + }; + src != -1 && retry(|| dup2(src, dst)) != -1 +} + impl Process { pub fn id(&self) -> pid_t { self.pid @@ -53,10 +82,10 @@ impl Process { mkerr_libc(r) } - pub fn spawn(cfg: &C, in_fd: Option

, - out_fd: Option

, err_fd: Option

) - -> IoResult - where C: ProcessConfig, P: AsInner, + pub fn spawn(cfg: &C, in_fd: Option

, + out_fd: Option, err_fd: Option) + -> IoResult + where C: ProcessConfig, P: AsInner, Q: AsInner, K: BytesContainer + Eq + Hash, V: BytesContainer { use libc::funcs::posix88::unistd::{fork, dup2, close, chdir, execvp}; @@ -167,36 +196,10 @@ impl Process { rustrt::rust_unset_sigprocmask(); - // If a stdio file descriptor is set to be ignored (via a -1 file - // descriptor), then we don't actually close it, but rather open - // up /dev/null into that file descriptor. Otherwise, the first file - // descriptor opened up in the child would be numbered as one of the - // stdio file descriptors, which is likely to wreak havoc. - let setup = |src: Option

, dst: c_int| { - let src = match src { - None => { - let flags = if dst == libc::STDIN_FILENO { - libc::O_RDONLY - } else { - libc::O_RDWR - }; - libc::open(devnull.as_ptr(), flags, 0) - } - Some(obj) => { - let fd = obj.as_inner().fd(); - // Leak the memory and the file descriptor. We're in the - // child now an all our resources are going to be - // cleaned up very soon - mem::forget(obj); - fd - } - }; - src != -1 && retry(|| dup2(src, dst)) != -1 - }; - if !setup(in_fd, libc::STDIN_FILENO) { fail(&mut output) } - if !setup(out_fd, libc::STDOUT_FILENO) { fail(&mut output) } - if !setup(err_fd, libc::STDERR_FILENO) { fail(&mut output) } + if !setup(in_fd, libc::STDIN_FILENO, &devnull) { fail(&mut output) } + if !setup(out_fd, libc::STDOUT_FILENO, &devnull) { fail(&mut output) } + if !setup(err_fd, libc::STDERR_FILENO, &devnull) { fail(&mut output) } // close all other fds for fd in range(3, getdtablesize()).rev() { diff --git a/src/libstd/sys/windows/ext.rs b/src/libstd/sys/windows/ext.rs index 2c58ee69e8b7c..a75cc6d1d355b 100644 --- a/src/libstd/sys/windows/ext.rs +++ b/src/libstd/sys/windows/ext.rs @@ -39,7 +39,13 @@ impl AsRawHandle for io::fs::File { } } -impl AsRawHandle for io::pipe::PipeStream { +impl AsRawHandle for io::pipe::PipeReader { + fn as_raw_handle(&self) -> Handle { + self.as_inner().handle() + } +} + +impl AsRawHandle for io::pipe::PipeWriter { fn as_raw_handle(&self) -> Handle { self.as_inner().handle() } diff --git a/src/libstd/sys/windows/process.rs b/src/libstd/sys/windows/process.rs index 78a8e09dac1a0..a259f5caa6bd2 100644 --- a/src/libstd/sys/windows/process.rs +++ b/src/libstd/sys/windows/process.rs @@ -53,6 +53,51 @@ impl Drop for Process { } } +// Similarly to unix, we don't actually leave holes for the stdio file +// descriptors, but rather open up /dev/null equivalents. These +// equivalents are drawn from libuv's windows process spawning. +fn set_fd(fd: &Option

, slot: &mut HANDLE, is_stdin: bool, cur_proc: &HANDLE) -> IoResult<()> { + match *fd { + None => { + let access = if is_stdin { + libc::FILE_GENERIC_READ + } else { + libc::FILE_GENERIC_WRITE | libc::FILE_READ_ATTRIBUTES + }; + let size = mem::size_of::(); + let mut sa = libc::SECURITY_ATTRIBUTES { + nLength: size as libc::DWORD, + lpSecurityDescriptor: ptr::null_mut(), + bInheritHandle: 1, + }; + let mut filename: Vec = "NUL".utf16_units().collect(); + filename.push(0); + *slot = libc::CreateFileW(filename.as_ptr(), + access, + libc::FILE_SHARE_READ | + libc::FILE_SHARE_WRITE, + &mut sa, + libc::OPEN_EXISTING, + 0, + ptr::null_mut()); + if *slot == INVALID_HANDLE_VALUE { + return Err(super::last_error()) + } + } + Some(ref fd) => { + let orig = get_osfhandle(fd.as_inner().fd()) as HANDLE; + if orig == INVALID_HANDLE_VALUE { + return Err(super::last_error()) + } + if DuplicateHandle(cur_proc, orig, cur_proc, slot, + 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE { + return Err(super::last_error()) + } + } + } + Ok(()) +} + impl Process { pub fn id(&self) -> pid_t { self.pid @@ -100,10 +145,10 @@ impl Process { return ret; } - pub fn spawn(cfg: &C, in_fd: Option

, - out_fd: Option

, err_fd: Option

) - -> IoResult - where C: ProcessConfig, P: AsInner, + pub fn spawn(cfg: &C, in_fd: Option

, + out_fd: Option

, err_fd: Option

) + -> IoResult + where C: ProcessConfig, P: AsInner, Q: AsInner, K: BytesContainer + Eq + Hash, V: BytesContainer { use libc::types::os::arch::extra::{DWORD, HANDLE, STARTUPINFO}; @@ -160,55 +205,9 @@ impl Process { let cur_proc = GetCurrentProcess(); - // Similarly to unix, we don't actually leave holes for the stdio file - // descriptors, but rather open up /dev/null equivalents. These - // equivalents are drawn from libuv's windows process spawning. - let set_fd = |fd: &Option

, slot: &mut HANDLE, - is_stdin: bool| { - match *fd { - None => { - let access = if is_stdin { - libc::FILE_GENERIC_READ - } else { - libc::FILE_GENERIC_WRITE | libc::FILE_READ_ATTRIBUTES - }; - let size = mem::size_of::(); - let mut sa = libc::SECURITY_ATTRIBUTES { - nLength: size as libc::DWORD, - lpSecurityDescriptor: ptr::null_mut(), - bInheritHandle: 1, - }; - let mut filename: Vec = "NUL".utf16_units().collect(); - filename.push(0); - *slot = libc::CreateFileW(filename.as_ptr(), - access, - libc::FILE_SHARE_READ | - libc::FILE_SHARE_WRITE, - &mut sa, - libc::OPEN_EXISTING, - 0, - ptr::null_mut()); - if *slot == INVALID_HANDLE_VALUE { - return Err(super::last_error()) - } - } - Some(ref fd) => { - let orig = get_osfhandle(fd.as_inner().fd()) as HANDLE; - if orig == INVALID_HANDLE_VALUE { - return Err(super::last_error()) - } - if DuplicateHandle(cur_proc, orig, cur_proc, slot, - 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE { - return Err(super::last_error()) - } - } - } - Ok(()) - }; - - try!(set_fd(&in_fd, &mut si.hStdInput, true)); - try!(set_fd(&out_fd, &mut si.hStdOutput, false)); - try!(set_fd(&err_fd, &mut si.hStdError, false)); + try!(set_fd(&in_fd, &mut si.hStdInput, true, &cur_proc)); + try!(set_fd(&out_fd, &mut si.hStdOutput, false, &cur_proc)); + try!(set_fd(&err_fd, &mut si.hStdError, false, &cur_proc)); let cmd_str = make_command_line(program.as_ref().unwrap_or(cfg.program()), cfg.args()); diff --git a/src/test/run-pass/sigpipe-should-be-ignored.rs b/src/test/run-pass/sigpipe-should-be-ignored.rs index 1804dd2e1358a..bfd6f067d022c 100644 --- a/src/test/run-pass/sigpipe-should-be-ignored.rs +++ b/src/test/run-pass/sigpipe-should-be-ignored.rs @@ -12,13 +12,13 @@ // doesn't die in a ball of fire, but rather it's gracefully handled. use std::os; -use std::io::PipeStream; +use std::io::{Pipe,PipeReader,IoResult}; use std::io::Command; fn test() { let os::Pipe { reader, writer } = unsafe { os::pipe().unwrap() }; - let reader = PipeStream::open(reader); - let mut writer = PipeStream::open(writer); + let reader : IoResult = Pipe::open(reader); + let mut writer = Pipe::open(writer); drop(reader); let _ = writer.write(&[1]);