diff options
Diffstat (limited to 'src/libstd/io/process.rs')
| -rw-r--r-- | src/libstd/io/process.rs | 228 |
1 files changed, 150 insertions, 78 deletions
diff --git a/src/libstd/io/process.rs b/src/libstd/io/process.rs index 698e0a3460f..d71bab0b48f 100644 --- a/src/libstd/io/process.rs +++ b/src/libstd/io/process.rs @@ -20,14 +20,17 @@ use os; use io::{IoResult, IoError}; use io; use libc; -use mem; -use rt::rtio::{RtioProcess, ProcessConfig, IoFactory, LocalIo}; -use rt::rtio; use c_str::CString; use collections::HashMap; use hash::Hash; #[cfg(windows)] use std::hash::sip::SipState; +use io::pipe::{PipeStream, PipePair}; +use path::BytesContainer; + +use sys; +use sys::fs::FileDesc; +use sys::process::Process as ProcessImp; /// Signal a process to exit, without forcibly killing it. Corresponds to /// SIGTERM on unix platforms. @@ -62,24 +65,29 @@ use std::hash::sip::SipState; /// assert!(child.wait().unwrap().success()); /// ``` pub struct Process { - handle: Box<RtioProcess + Send>, + handle: ProcessImp, forget: bool, + /// None until wait() is called. + exit_code: Option<ProcessExit>, + + /// Manually delivered signal + exit_signal: Option<int>, + + /// Deadline after which wait() will return + deadline: u64, + /// 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<io::PipeStream>, + pub stdin: Option<PipeStream>, /// 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<io::PipeStream>, + pub stdout: Option<PipeStream>, /// 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<io::PipeStream>, - - /// Extra I/O handles as configured by the original `ProcessConfig` when - /// this process was created. This is by default empty. - pub extra_io: Vec<Option<io::PipeStream>>, + pub stderr: Option<PipeStream>, } /// A representation of environment variable name @@ -130,6 +138,13 @@ impl PartialEq for EnvKey { } } +impl BytesContainer for EnvKey { + fn container_as_bytes<'a>(&'a self) -> &'a [u8] { + let &EnvKey(ref k) = self; + k.container_as_bytes() + } +} + /// A HashMap representation of environment variables. pub type EnvMap = HashMap<EnvKey, CString>; @@ -160,7 +175,6 @@ pub struct Command { stdin: StdioContainer, stdout: StdioContainer, stderr: StdioContainer, - extra_io: Vec<StdioContainer>, uid: Option<uint>, gid: Option<uint>, detach: bool, @@ -194,7 +208,6 @@ impl Command { stdin: CreatePipe(true, false), stdout: CreatePipe(false, true), stderr: CreatePipe(false, true), - extra_io: Vec::new(), uid: None, gid: None, detach: false, @@ -281,14 +294,6 @@ impl Command { self.stderr = cfg; self } - /// Attaches a stream/file descriptor/pipe to the child process. Inherited - /// file descriptors are numbered consecutively, starting at 3; the first - /// three file descriptors (stdin/stdout/stderr) are configured with the - /// `stdin`, `stdout`, and `stderr` methods. - pub fn extra_io<'a>(&'a mut self, cfg: StdioContainer) -> &'a mut Command { - self.extra_io.push(cfg); - self - } /// Sets the child process's user id. This translates to a `setuid` call in /// the child process. Setting this value on windows will cause the spawn to @@ -315,50 +320,23 @@ impl Command { /// Executes the command as a child process, which is returned. pub fn spawn(&self) -> IoResult<Process> { - fn to_rtio(p: StdioContainer) -> rtio::StdioContainer { - match p { - Ignored => rtio::Ignored, - InheritFd(fd) => rtio::InheritFd(fd), - CreatePipe(a, b) => rtio::CreatePipe(a, b), - } - } - let extra_io: Vec<rtio::StdioContainer> = - self.extra_io.iter().map(|x| to_rtio(*x)).collect(); - LocalIo::maybe_raise(|io| { - let env = match self.env { - None => None, - Some(ref env_map) => - Some(env_map.iter() - .map(|(&EnvKey(ref key), val)| (key, val)) - .collect::<Vec<_>>()) - }; - let cfg = ProcessConfig { - program: &self.program, - args: self.args.as_slice(), - env: env.as_ref().map(|e| e.as_slice()), - cwd: self.cwd.as_ref(), - stdin: to_rtio(self.stdin), - stdout: to_rtio(self.stdout), - stderr: to_rtio(self.stderr), - extra_io: extra_io.as_slice(), - uid: self.uid, - gid: self.gid, - detach: self.detach, - }; - io.spawn(cfg).map(|(p, io)| { - let mut io = io.into_iter().map(|p| { - p.map(|p| io::PipeStream::new(p)) - }); - Process { - handle: p, - forget: false, - stdin: io.next().unwrap(), - stdout: io.next().unwrap(), - stderr: io.next().unwrap(), - extra_io: io.collect(), - } + 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)); + + match ProcessImp::spawn(self, their_stdin, their_stdout, their_stderr) { + Err(e) => Err(e), + Ok(handle) => Ok(Process { + handle: handle, + forget: false, + exit_code: None, + exit_signal: None, + deadline: 0, + stdin: our_stdin, + stdout: our_stdout, + stderr: our_stderr, }) - }).map_err(IoError::from_rtio_error) + } } /// Executes the command as a child process, waiting for it to finish and @@ -415,6 +393,58 @@ impl fmt::Show for Command { } } +fn setup_io(io: StdioContainer) -> IoResult<(Option<PipeStream>, Option<PipeStream>)> { + let ours; + let theirs; + match io { + Ignored => { + theirs = None; + ours = 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); + } + } + } + Ok((theirs, ours)) +} + +// Allow the sys module to get access to the Command state +impl sys::process::ProcessConfig<EnvKey, CString> for Command { + fn program(&self) -> &CString { + &self.program + } + fn args(&self) -> &[CString] { + self.args.as_slice() + } + fn env(&self) -> Option<&EnvMap> { + self.env.as_ref() + } + fn cwd(&self) -> Option<&CString> { + self.cwd.as_ref() + } + fn uid(&self) -> Option<uint> { + self.uid.clone() + } + fn gid(&self) -> Option<uint> { + self.gid.clone() + } + fn detach(&self) -> bool { + self.detach + } + +} + /// The output of a finished process. #[deriving(PartialEq, Eq, Clone)] pub struct ProcessOutput { @@ -494,9 +524,7 @@ impl Process { /// be successfully delivered if the child has exited, but not yet been /// reaped. pub fn kill(id: libc::pid_t, signal: int) -> IoResult<()> { - LocalIo::maybe_raise(|io| { - io.kill(id, signal) - }).map_err(IoError::from_rtio_error) + unsafe { ProcessImp::killpid(id, signal) } } /// Returns the process id of this child process @@ -518,7 +546,42 @@ impl Process { /// /// If the signal delivery fails, the corresponding error is returned. pub fn signal(&mut self, signal: int) -> IoResult<()> { - self.handle.kill(signal).map_err(IoError::from_rtio_error) + #[cfg(unix)] fn collect_status(p: &mut Process) { + // On Linux (and possibly other unices), a process that has exited will + // continue to accept signals because it is "defunct". The delivery of + // signals will only fail once the child has been reaped. For this + // reason, if the process hasn't exited yet, then we attempt to collect + // their status with WNOHANG. + if p.exit_code.is_none() { + match p.handle.try_wait() { + Some(code) => { p.exit_code = Some(code); } + None => {} + } + } + } + #[cfg(windows)] fn collect_status(_p: &mut Process) {} + + collect_status(self); + + // if the process has finished, and therefore had waitpid called, + // and we kill it, then on unix we might ending up killing a + // newer process that happens to have the same (re-used) id + if self.exit_code.is_some() { + return Err(IoError { + kind: io::InvalidInput, + desc: "invalid argument: can't kill an exited process", + detail: None, + }) + } + + // A successfully delivered signal that isn't 0 (just a poll for being + // alive) is recorded for windows (see wait()) + match unsafe { self.handle.kill(signal) } { + Ok(()) if signal == 0 => Ok(()), + Ok(()) => { self.exit_signal = Some(signal); Ok(()) } + Err(e) => Err(e), + } + } /// Sends a signal to this child requesting that it exits. This is @@ -545,10 +608,21 @@ impl Process { /// `set_timeout` and the timeout expires before the child exits. pub fn wait(&mut self) -> IoResult<ProcessExit> { drop(self.stdin.take()); - match self.handle.wait() { - Ok(rtio::ExitSignal(s)) => Ok(ExitSignal(s)), - Ok(rtio::ExitStatus(s)) => Ok(ExitStatus(s)), - Err(e) => Err(IoError::from_rtio_error(e)), + match self.exit_code { + Some(code) => Ok(code), + None => { + let code = try!(self.handle.wait(self.deadline)); + // On windows, waitpid will never return a signal. If a signal + // was successfully delivered to the process, however, we can + // consider it as having died via a signal. + let code = match self.exit_signal { + None => code, + Some(signal) if cfg!(windows) => ExitSignal(signal), + Some(..) => code, + }; + self.exit_code = Some(code); + Ok(code) + } } } @@ -594,7 +668,7 @@ impl Process { /// ``` #[experimental = "the type of the timeout is likely to change"] pub fn set_timeout(&mut self, timeout_ms: Option<u64>) { - self.handle.set_timeout(timeout_ms) + self.deadline = timeout_ms.map(|i| i + sys::timer::now()).unwrap_or(0); } /// Simultaneously wait for the child to exit and collect all remaining @@ -653,7 +727,6 @@ impl Drop for Process { drop(self.stdin.take()); drop(self.stdout.take()); drop(self.stderr.take()); - drop(mem::replace(&mut self.extra_io, Vec::new())); self.set_timeout(None); let _ = self.wait().unwrap(); @@ -1109,8 +1182,7 @@ mod tests { #[test] fn dont_close_fd_on_command_spawn() { - use std::rt::rtio::{Truncate, Write}; - use self::native::io::file; + use sys::fs; let path = if cfg!(windows) { Path::new("NUL") @@ -1118,7 +1190,7 @@ mod tests { Path::new("/dev/null") }; - let mut fdes = match file::open(&path.to_c_str(), Truncate, Write) { + let mut fdes = match fs::open(&path, Truncate, Write) { Ok(f) => f, Err(_) => panic!("failed to open file descriptor"), }; @@ -1126,7 +1198,7 @@ mod tests { let mut cmd = pwd_cmd(); let _ = cmd.stdout(InheritFd(fdes.fd())); assert!(cmd.status().unwrap().success()); - assert!(fdes.inner_write("extra write\n".as_bytes()).is_ok()); + assert!(fdes.write("extra write\n".as_bytes()).is_ok()); } #[test] |
