auto merge of #12380 : alexcrichton/rust/run-rewrite, r=brson

The std::run module is a relic from a standard library long since past, and
there's not much use to having two modules to execute processes with where one
is slightly more convenient. This commit merges the two modules, moving lots of
functionality from std::run into std::io::process and then deleting
std::run.

New things you can find in std::io::process are:

* Process::new() now only takes prog/args
* Process::configure() takes a ProcessConfig
* Process::status() is the same as run::process_status
* Process::output() is the same as run::process_output
* I/O for spawned tasks is now defaulted to captured in pipes instead of ignored
* Process::kill() was added (plus an associated green/native implementation)
* Process::wait_with_output() is the same as the old finish_with_output()
* destroy() is now signal_exit()
* force_destroy() is now signal_kill()

Closes #2625
Closes #10016
This commit is contained in:
bors 2014-02-23 22:06:50 -08:00
commit a5342d5970
24 changed files with 676 additions and 819 deletions

View File

@ -9,9 +9,8 @@
// except according to those terms.
use std::os;
use std::run;
use std::str;
use std::io::process::ProcessExit;
use std::io::process::{ProcessExit, Process, ProcessConfig, ProcessOutput};
#[cfg(target_os = "win32")]
fn target_env(lib_path: &str, prog: &str) -> ~[(~str,~str)] {
@ -49,17 +48,19 @@ pub fn run(lib_path: &str,
input: Option<~str>) -> Option<Result> {
let env = env + target_env(lib_path, prog);
let mut opt_process = run::Process::new(prog, args, run::ProcessOptions {
env: Some(env),
.. run::ProcessOptions::new()
let mut opt_process = Process::configure(ProcessConfig {
program: prog,
args: args,
env: Some(env.as_slice()),
.. ProcessConfig::new()
});
match opt_process {
Ok(ref mut process) => {
for input in input.iter() {
process.input().write(input.as_bytes()).unwrap();
process.stdin.get_mut_ref().write(input.as_bytes()).unwrap();
}
let run::ProcessOutput { status, output, error } = process.finish_with_output();
let ProcessOutput { status, output, error } = process.wait_with_output();
Some(Result {
status: status,
@ -75,18 +76,20 @@ pub fn run_background(lib_path: &str,
prog: &str,
args: &[~str],
env: ~[(~str, ~str)],
input: Option<~str>) -> Option<run::Process> {
input: Option<~str>) -> Option<Process> {
let env = env + target_env(lib_path, prog);
let opt_process = run::Process::new(prog, args, run::ProcessOptions {
env: Some(env),
.. run::ProcessOptions::new()
let opt_process = Process::configure(ProcessConfig {
program: prog,
args: args,
env: Some(env.as_slice()),
.. ProcessConfig::new()
});
match opt_process {
Ok(mut process) => {
for input in input.iter() {
process.input().write(input.as_bytes()).unwrap();
process.stdin.get_mut_ref().write(input.as_bytes()).unwrap();
}
Some(process)

View File

@ -360,7 +360,7 @@ fn run_debuginfo_test(config: &config, props: &TestProps, testfile: &Path) {
stdout: out,
stderr: err,
cmdline: cmdline};
process.force_destroy().unwrap();
process.signal_kill().unwrap();
}
_=> {

View File

@ -464,8 +464,8 @@ impl<'a, T:Send +
#[test]
#[cfg(not(target_os="android"))] // FIXME(#10455)
fn test() {
use std::{os, run};
use std::io::fs;
use std::os;
use std::io::{fs, Process};
use std::str::from_utf8_owned;
// Create a path to a new file 'filename' in the directory in which
@ -499,9 +499,9 @@ fn test() {
prep.exec(proc(_exe) {
let out = make_path(~"foo.o");
// FIXME (#9639): This needs to handle non-utf8 paths
run::process_status("gcc", [pth.as_str().unwrap().to_owned(),
~"-o",
out.as_str().unwrap().to_owned()]).unwrap();
Process::status("gcc", [pth.as_str().unwrap().to_owned(),
~"-o",
out.as_str().unwrap().to_owned()]).unwrap();
let _proof_of_concept = subcx.prep("subfn");
// Could run sub-rules inside here.

View File

@ -287,6 +287,9 @@ impl rtio::IoFactory for IoFactory {
io.move_iter().map(|p| p.map(|p| ~p as ~RtioPipe)).collect())
})
}
fn kill(&mut self, pid: libc::pid_t, signum: int) -> IoResult<()> {
process::Process::kill(pid, signum)
}
fn pipe_open(&mut self, fd: c_int) -> IoResult<~RtioPipe> {
Ok(~file::FileDesc::new(fd, true) as ~RtioPipe)
}

View File

@ -66,18 +66,16 @@ impl Process {
-> Result<(Process, ~[Option<file::FileDesc>]), io::IoError>
{
// right now we only handle stdin/stdout/stderr.
if config.io.len() > 3 {
if config.extra_io.len() > 0 {
return Err(super::unimpl());
}
fn get_io(io: &[p::StdioContainer],
ret: &mut ~[Option<file::FileDesc>],
idx: uint) -> (Option<os::Pipe>, c_int) {
if idx >= io.len() { return (None, -1); }
ret.push(None);
match io[idx] {
p::Ignored => (None, -1),
p::InheritFd(fd) => (None, fd),
fn get_io(io: p::StdioContainer, ret: &mut ~[Option<file::FileDesc>])
-> (Option<os::Pipe>, c_int)
{
match io {
p::Ignored => { ret.push(None); (None, -1) }
p::InheritFd(fd) => { ret.push(None); (None, fd) }
p::CreatePipe(readable, _writable) => {
let pipe = os::pipe();
let (theirs, ours) = if readable {
@ -85,16 +83,16 @@ impl Process {
} else {
(pipe.out, pipe.input)
};
ret[idx] = Some(file::FileDesc::new(ours, true));
ret.push(Some(file::FileDesc::new(ours, true)));
(Some(pipe), theirs)
}
}
}
let mut ret_io = ~[];
let (in_pipe, in_fd) = get_io(config.io, &mut ret_io, 0);
let (out_pipe, out_fd) = get_io(config.io, &mut ret_io, 1);
let (err_pipe, err_fd) = get_io(config.io, &mut ret_io, 2);
let (in_pipe, in_fd) = get_io(config.stdin, &mut ret_io);
let (out_pipe, out_fd) = get_io(config.stdout, &mut ret_io);
let (err_pipe, err_fd) = get_io(config.stderr, &mut ret_io);
let env = config.env.map(|a| a.to_owned());
let cwd = config.cwd.map(|a| Path::new(a));
@ -115,6 +113,10 @@ impl Process {
Err(e) => Err(e)
}
}
pub fn kill(pid: libc::pid_t, signum: int) -> IoResult<()> {
unsafe { killpid(pid, signum) }
}
}
impl rtio::RtioProcess for Process {
@ -144,27 +146,6 @@ impl rtio::RtioProcess for Process {
None => {}
}
return unsafe { killpid(self.pid, signum) };
#[cfg(windows)]
unsafe fn killpid(pid: pid_t, signal: int) -> Result<(), io::IoError> {
match signal {
io::process::PleaseExitSignal | io::process::MustDieSignal => {
let ret = libc::TerminateProcess(pid as libc::HANDLE, 1);
super::mkerr_winbool(ret)
}
_ => Err(io::IoError {
kind: io::OtherIoError,
desc: "unsupported signal on windows",
detail: None,
})
}
}
#[cfg(not(windows))]
unsafe fn killpid(pid: pid_t, signal: int) -> Result<(), io::IoError> {
let r = libc::funcs::posix88::signal::kill(pid, signal as c_int);
super::mkerr_libc(r)
}
}
}
@ -174,6 +155,51 @@ impl Drop for Process {
}
}
#[cfg(windows)]
unsafe fn killpid(pid: pid_t, signal: int) -> Result<(), io::IoError> {
let handle = libc::OpenProcess(libc::PROCESS_TERMINATE |
libc::PROCESS_QUERY_INFORMATION,
libc::FALSE, pid as libc::DWORD);
if handle.is_null() {
return Err(super::last_error())
}
let ret = match signal {
// test for existence on signal 0
0 => {
let mut status = 0;
let ret = libc::GetExitCodeProcess(handle, &mut status);
if ret == 0 {
Err(super::last_error())
} else if status != libc::STILL_ACTIVE {
Err(io::IoError {
kind: io::OtherIoError,
desc: "process no longer alive",
detail: None,
})
} else {
Ok(())
}
}
io::process::PleaseExitSignal | io::process::MustDieSignal => {
let ret = libc::TerminateProcess(handle, 1);
super::mkerr_winbool(ret)
}
_ => Err(io::IoError {
kind: io::OtherIoError,
desc: "unsupported signal on windows",
detail: None,
})
};
let _ = libc::CloseHandle(handle);
return ret;
}
#[cfg(not(windows))]
unsafe fn killpid(pid: pid_t, signal: int) -> Result<(), io::IoError> {
let r = libc::funcs::posix88::signal::kill(pid, signal as c_int);
super::mkerr_libc(r)
}
struct SpawnProcessResult {
pid: pid_t,
handle: *(),
@ -536,10 +562,10 @@ fn spawn_process_os(config: p::ProcessConfig,
if !envp.is_null() {
set_environ(envp);
}
});
with_argv(config.program, config.args, |argv| {
let _ = execvp(*argv, argv);
fail(&mut output);
with_argv(config.program, config.args, |argv| {
let _ = execvp(*argv, argv);
fail(&mut output);
})
})
}
}

View File

@ -20,7 +20,7 @@ use std::io::fs;
use std::io;
use std::libc;
use std::os;
use std::run::{ProcessOptions, Process, ProcessOutput};
use std::io::process::{ProcessConfig, Process, ProcessOutput};
use std::str;
use std::raw;
use extra::tempfile::TempDir;
@ -44,16 +44,19 @@ fn run_ar(sess: Session, args: &str, cwd: Option<&Path>,
let mut args = ~[args.to_owned()];
let mut paths = paths.iter().map(|p| p.as_str().unwrap().to_owned());
args.extend(&mut paths);
let mut opts = ProcessOptions::new();
opts.dir = cwd;
debug!("{} {}", ar, args.connect(" "));
match cwd {
Some(p) => { debug!("inside {}", p.display()); }
None => {}
}
match Process::new(ar, args.as_slice(), opts) {
match Process::configure(ProcessConfig {
program: ar.as_slice(),
args: args.as_slice(),
cwd: cwd.map(|a| &*a),
.. ProcessConfig::new()
}) {
Ok(mut prog) => {
let o = prog.finish_with_output();
let o = prog.wait_with_output();
if !o.status.success() {
sess.err(format!("{} {} failed with: {}", ar, args.connect(" "),
o.status));

View File

@ -30,9 +30,9 @@ use std::c_str::ToCStr;
use std::char;
use std::os::consts::{macos, freebsd, linux, android, win32};
use std::ptr;
use std::run;
use std::str;
use std::io;
use std::io::Process;
use std::io::fs;
use flate;
use serialize::hex::ToHex;
@ -101,8 +101,8 @@ pub mod write {
use syntax::abi;
use std::c_str::ToCStr;
use std::io::Process;
use std::libc::{c_uint, c_int};
use std::run;
use std::str;
// On android, we by default compile for armv7 processors. This enables
@ -333,7 +333,7 @@ pub mod write {
assembly.as_str().unwrap().to_owned()];
debug!("{} '{}'", cc, args.connect("' '"));
match run::process_output(cc, args) {
match Process::output(cc, args) {
Ok(prog) => {
if !prog.status.success() {
sess.err(format!("linking with `{}` failed: {}", cc, prog.status));
@ -1033,7 +1033,7 @@ fn link_natively(sess: Session, dylib: bool, obj_filename: &Path,
// Invoke the system linker
debug!("{} {}", cc_prog, cc_args.connect(" "));
let prog = time(sess.time_passes(), "running linker", (), |()|
run::process_output(cc_prog, cc_args));
Process::output(cc_prog, cc_args));
match prog {
Ok(prog) => {
if !prog.status.success() {
@ -1054,7 +1054,7 @@ fn link_natively(sess: Session, dylib: bool, obj_filename: &Path,
// the symbols
if sess.targ_cfg.os == abi::OsMacos && sess.opts.debuginfo {
// FIXME (#9639): This needs to handle non-utf8 paths
match run::process_status("dsymutil",
match Process::status("dsymutil",
[out_filename.as_str().unwrap().to_owned()]) {
Ok(..) => {}
Err(e) => {

View File

@ -9,12 +9,12 @@
// except according to those terms.
use std::cell::RefCell;
use collections::HashSet;
use std::io::Process;
use std::local_data;
use std::os;
use std::run;
use std::str;
use collections::HashSet;
use testing;
use extra::tempfile::TempDir;
use rustc::back::link;
@ -126,7 +126,7 @@ fn runtest(test: &str, cratename: &str, libs: HashSet<Path>, should_fail: bool)
driver::compile_input(sess, cfg, &input, &out, &None);
let exe = outdir.path().join("rust_out");
let out = run::process_output(exe.as_str().unwrap(), []);
let out = Process::output(exe.as_str().unwrap(), []);
match out {
Err(e) => fail!("couldn't run the test: {}", e),
Ok(out) => {

View File

@ -44,7 +44,10 @@ impl Process {
-> Result<(~Process, ~[Option<PipeWatcher>]), UvError>
{
let cwd = config.cwd.map(|s| s.to_c_str());
let io = config.io;
let mut io = ~[config.stdin, config.stdout, config.stderr];
for slot in config.extra_io.iter() {
io.push(*slot);
}
let mut stdio = vec::with_capacity::<uvll::uv_stdio_container_t>(io.len());
let mut ret_io = vec::with_capacity(io.len());
unsafe {
@ -105,6 +108,15 @@ impl Process {
Err(e) => Err(e),
}
}
pub fn kill(pid: libc::pid_t, signum: int) -> Result<(), UvError> {
match unsafe {
uvll::uv_kill(pid as libc::c_int, signum as libc::c_int)
} {
0 => Ok(()),
n => Err(UvError(n))
}
}
}
extern fn on_exit(handle: *uvll::uv_process_t,

View File

@ -277,6 +277,10 @@ impl IoFactory for UvIoFactory {
}
}
fn kill(&mut self, pid: libc::pid_t, signum: int) -> Result<(), IoError> {
Process::kill(pid, signum).map_err(uv_error_to_io_error)
}
fn unix_bind(&mut self, path: &CString) -> Result<~rtio::RtioUnixListener, IoError>
{
match PipeListener::bind(self, path) {

View File

@ -645,6 +645,7 @@ extern {
pub fn uv_spawn(loop_ptr: *uv_loop_t, outptr: *uv_process_t,
options: *uv_process_options_t) -> c_int;
pub fn uv_process_kill(p: *uv_process_t, signum: c_int) -> c_int;
pub fn uv_kill(pid: c_int, signum: c_int) -> c_int;
// pipes
pub fn uv_pipe_init(l: *uv_loop_t, p: *uv_pipe_t, ipc: c_int) -> c_int;

View File

@ -1108,6 +1108,7 @@ mod test {
drop(file);
})
#[ignore(cfg(windows))] // FIXME(#11638)
iotest!(fn truncate_works() {
let tmpdir = tmpdir();
let path = tmpdir.join("in.txt");

View File

@ -208,7 +208,7 @@ 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::process::Process;
pub use self::process::{Process, ProcessConfig};
pub use self::mem::{MemReader, BufReader, MemWriter, BufWriter};
pub use self::buffered::{BufferedReader, BufferedWriter, BufferedStream,

View File

@ -10,29 +10,82 @@
//! Bindings for executing child processes
#[deny(missing_doc)];
use prelude::*;
use libc;
use io;
use fmt;
use io::IoResult;
use io;
use libc;
use rt::rtio::{RtioProcess, IoFactory, LocalIo};
use fmt;
// windows values don't matter as long as they're at least one of unix's
// TERM/KILL/INT signals
/// Signal a process to exit, without forcibly killing it. Corresponds to
/// SIGTERM on unix platforms.
#[cfg(windows)] pub static PleaseExitSignal: int = 15;
/// Signal a process to exit immediately, forcibly killing it. Corresponds to
/// SIGKILL on unix platforms.
#[cfg(windows)] pub static MustDieSignal: int = 9;
/// Signal a process to exit, without forcibly killing it. Corresponds to
/// SIGTERM on unix platforms.
#[cfg(not(windows))] pub static PleaseExitSignal: int = libc::SIGTERM as int;
/// Signal a process to exit immediately, forcibly killing it. Corresponds to
/// SIGKILL on unix platforms.
#[cfg(not(windows))] pub static MustDieSignal: int = libc::SIGKILL as int;
/// Representation of a running or exited child process.
///
/// This structure is used to create, run, and manage child processes. A process
/// is configured with the `ProcessConfig` struct which contains specific
/// options for dictating how the child is spawned.
///
/// # Example
///
/// ```should_fail
/// use std::io::Process;
///
/// let mut child = match Process::new("/bin/cat", [~"file.txt"]) {
/// Ok(child) => child,
/// Err(e) => fail!("failed to execute child: {}", e),
/// };
///
/// let contents = child.stdout.get_mut_ref().read_to_end();
/// assert!(child.wait().success());
/// ```
pub struct Process {
priv handle: ~RtioProcess,
io: ~[Option<io::PipeStream>],
/// Handle to the child's stdin, if the `stdin` field of this process's
/// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`.
stdin: Option<io::PipeStream>,
/// Handle to the child's stdout, if the `stdout` field of this process's
/// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`.
stdout: Option<io::PipeStream>,
/// Handle to the child's stderr, if the `stderr` field of this process's
/// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`.
stderr: Option<io::PipeStream>,
/// Extra I/O handles as configured by the original `ProcessConfig` when
/// this process was created. This is by default empty.
extra_io: ~[Option<io::PipeStream>],
}
/// This configuration describes how a new process should be spawned. This is
/// translated to libuv's own configuration
/// This configuration describes how a new process should be spawned. A blank
/// configuration can be created with `ProcessConfig::new()`. It is also
/// recommented to use a functional struct update pattern when creating process
/// configuration:
///
/// ```
/// use std::io::ProcessConfig;
///
/// let config = ProcessConfig {
/// program: "/bin/sh",
/// args: &[~"-c", ~"true"],
/// .. ProcessConfig::new()
/// };
/// ```
pub struct ProcessConfig<'a> {
/// Path to the program to run
program: &'a str,
@ -46,19 +99,30 @@ pub struct ProcessConfig<'a> {
/// Optional working directory for the new process. If this is None, then
/// the current directory of the running process is inherited.
cwd: Option<&'a str>,
cwd: Option<&'a Path>,
/// Configuration for the child process's stdin handle (file descriptor 0).
/// This field defaults to `CreatePipe(true, false)` so the input can be
/// written to.
stdin: StdioContainer,
/// Configuration for the child process's stdout handle (file descriptor 1).
/// This field defaults to `CreatePipe(false, true)` so the output can be
/// collected.
stdout: StdioContainer,
/// Configuration for the child process's stdout handle (file descriptor 2).
/// This field defaults to `CreatePipe(false, true)` so the output can be
/// collected.
stderr: StdioContainer,
/// Any number of streams/file descriptors/pipes may be attached to this
/// process. This list enumerates the file descriptors and such for the
/// process to be spawned, and the file descriptors inherited will start at
/// 0 and go to the length of this array.
///
/// Standard file descriptors are:
///
/// 0 - stdin
/// 1 - stdout
/// 2 - stderr
io: &'a [StdioContainer],
/// 3 and go to the length of this array. The first three file descriptors
/// (stdin/stdout/stderr) are configured with the `stdin`, `stdout`, and
/// `stderr` fields.
extra_io: &'a [StdioContainer],
/// 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
@ -75,6 +139,16 @@ pub struct ProcessConfig<'a> {
detach: bool,
}
/// The output of a finished process.
pub struct ProcessOutput {
/// The status (exit code) of the process.
status: ProcessExit,
/// The data that the process wrote to stdout.
output: ~[u8],
/// The data that the process wrote to stderr.
error: ~[u8],
}
/// Describes what to do with a standard io stream for a child process.
pub enum StdioContainer {
/// This stream will be ignored. This is the equivalent of attaching the
@ -138,20 +212,23 @@ impl<'a> ProcessConfig<'a> {
///
/// let config = ProcessConfig {
/// program: "/bin/sh",
/// args: &'static [~"-c", ~"echo hello"],
/// args: &[~"-c", ~"echo hello"],
/// .. ProcessConfig::new()
/// };
///
/// let p = Process::new(config);
/// let p = Process::configure(config);
/// ```
///
pub fn new() -> ProcessConfig<'static> {
pub fn new<'a>() -> ProcessConfig<'a> {
ProcessConfig {
program: "",
args: &'static [],
args: &[],
env: None,
cwd: None,
io: &'static [],
stdin: CreatePipe(true, false),
stdout: CreatePipe(false, true),
stderr: CreatePipe(false, true),
extra_io: &[],
uid: None,
gid: None,
detach: false,
@ -160,22 +237,105 @@ impl<'a> ProcessConfig<'a> {
}
impl Process {
/// Creates a new pipe initialized, but not bound to any particular
/// source/destination
pub fn new(config: ProcessConfig) -> IoResult<Process> {
/// Creates a new process for the specified program/arguments, using
/// otherwise default configuration.
///
/// By default, new processes have their stdin/stdout/stderr handles created
/// as pipes the can be manipulated through the respective fields of the
/// returned `Process`.
///
/// # Example
///
/// ```
/// use std::io::Process;
///
/// let mut process = match Process::new("sh", &[~"c", ~"echo hello"]) {
/// Ok(p) => p,
/// Err(e) => fail!("failed to execute process: {}", e),
/// };
///
/// let output = process.stdout.get_mut_ref().read_to_end();
/// ```
pub fn new(prog: &str, args: &[~str]) -> IoResult<Process> {
Process::configure(ProcessConfig {
program: prog,
args: args,
.. ProcessConfig::new()
})
}
/// Executes the specified program with arguments, waiting for it to finish
/// and collecting all of its output.
///
/// # Example
///
/// ```
/// use std::io::Process;
/// use std::str;
///
/// let output = match Process::output("cat", [~"foo.txt"]) {
/// Ok(output) => output,
/// Err(e) => fail!("failed to execute process: {}", e),
/// };
///
/// println!("status: {}", output.status);
/// println!("stdout: {}", str::from_utf8_lossy(output.output));
/// println!("stderr: {}", str::from_utf8_lossy(output.error));
/// ```
pub fn output(prog: &str, args: &[~str]) -> IoResult<ProcessOutput> {
Process::new(prog, args).map(|mut p| p.wait_with_output())
}
/// Executes a child process and collects its exit status. This will block
/// waiting for the child to exit.
///
/// # Example
///
/// ```
/// use std::io::Process;
///
/// let status = match Process::status("ls", []) {
/// Ok(status) => status,
/// Err(e) => fail!("failed to execute process: {}", e),
/// };
///
/// println!("process exited with: {}", status);
/// ```
pub fn status(prog: &str, args: &[~str]) -> IoResult<ProcessExit> {
Process::new(prog, args).map(|mut p| p.wait())
}
/// Creates a new process with the specified configuration.
pub fn configure(config: ProcessConfig) -> IoResult<Process> {
let mut config = Some(config);
LocalIo::maybe_raise(|io| {
io.spawn(config.take_unwrap()).map(|(p, io)| {
let mut io = io.move_iter().map(|p| {
p.map(|p| io::PipeStream::new(p))
});
Process {
handle: p,
io: io.move_iter().map(|p| {
p.map(|p| io::PipeStream::new(p))
}).collect()
stdin: io.next().unwrap(),
stdout: io.next().unwrap(),
stderr: io.next().unwrap(),
extra_io: io.collect(),
}
})
})
}
/// Sends `signal` to another process in the system identified by `id`.
///
/// Note that windows doesn't quite have the same model as unix, so some
/// unix signals are mapped to windows signals. Notably, unix termination
/// signals (SIGTERM/SIGKILL/SIGINT) are translated to `TerminateProcess`.
///
/// Additionally, a signal number of 0 can check for existence of the target
/// process.
pub fn kill(id: libc::pid_t, signal: int) -> IoResult<()> {
LocalIo::maybe_raise(|io| io.kill(id, signal))
}
/// Returns the process id of this child process
pub fn id(&self) -> libc::pid_t { self.handle.id() }
@ -190,18 +350,66 @@ impl Process {
self.handle.kill(signal)
}
/// Sends a signal to this child requesting that it exits. This is
/// equivalent to sending a SIGTERM on unix platforms.
pub fn signal_exit(&mut self) -> IoResult<()> {
self.signal(PleaseExitSignal)
}
/// Sends a signal to this child forcing it to exit. This is equivalent to
/// sending a SIGKILL on unix platforms.
pub fn signal_kill(&mut self) -> IoResult<()> {
self.signal(MustDieSignal)
}
/// Wait for the child to exit completely, returning the status that it
/// exited with. This function will continue to have the same return value
/// after it has been called at least once.
pub fn wait(&mut self) -> ProcessExit { self.handle.wait() }
///
/// The stdin handle to the child process will be closed before waiting.
pub fn wait(&mut self) -> ProcessExit {
drop(self.stdin.take());
self.handle.wait()
}
/// Simultaneously wait for the child to exit and collect all remaining
/// output on the stdout/stderr handles, returning a `ProcessOutput`
/// instance.
///
/// The stdin handle to the child is closed before waiting.
pub fn wait_with_output(&mut self) -> ProcessOutput {
drop(self.stdin.take());
fn read(stream: Option<io::PipeStream>) -> Port<IoResult<~[u8]>> {
let (p, c) = Chan::new();
match stream {
Some(stream) => spawn(proc() {
let mut stream = stream;
c.send(stream.read_to_end())
}),
None => c.send(Ok(~[]))
}
p
}
let stdout = read(self.stdout.take());
let stderr = read(self.stderr.take());
let status = self.wait();
ProcessOutput { status: status,
output: stdout.recv().ok().unwrap_or(~[]),
error: stderr.recv().ok().unwrap_or(~[]) }
}
}
impl Drop for Process {
fn drop(&mut self) {
// Close all I/O before exiting to ensure that the child doesn't wait
// forever to print some text or something similar.
drop(self.stdin.take());
drop(self.stdout.take());
drop(self.stderr.take());
loop {
match self.io.pop() {
match self.extra_io.pop() {
Some(_) => (),
None => break,
}
@ -216,42 +424,39 @@ mod tests {
use io::process::{ProcessConfig, Process};
use prelude::*;
// FIXME(#10380)
#[cfg(unix, not(target_os="android"))]
// FIXME(#10380) these tests should not all be ignored on android.
#[cfg(not(target_os="android"))]
iotest!(fn smoke() {
let args = ProcessConfig {
program: "/bin/sh",
args: &[~"-c", ~"true"],
program: "true",
.. ProcessConfig::new()
};
let p = Process::new(args);
let p = Process::configure(args);
assert!(p.is_ok());
let mut p = p.unwrap();
assert!(p.wait().success());
})
// FIXME(#10380)
#[cfg(unix, not(target_os="android"))]
#[cfg(not(target_os="android"))]
iotest!(fn smoke_failure() {
let args = ProcessConfig {
program: "if-this-is-a-binary-then-the-world-has-ended",
.. ProcessConfig::new()
};
match Process::new(args) {
match Process::configure(args) {
Ok(..) => fail!(),
Err(..) => {}
}
})
// FIXME(#10380)
#[cfg(unix, not(target_os="android"))]
#[cfg(not(target_os="android"))]
iotest!(fn exit_reported_right() {
let args = ProcessConfig {
program: "/bin/sh",
args: &[~"-c", ~"exit 1"],
program: "false",
.. ProcessConfig::new()
};
let p = Process::new(args);
let p = Process::configure(args);
assert!(p.is_ok());
let mut p = p.unwrap();
assert!(p.wait().matches_exit_status(1));
@ -264,7 +469,7 @@ mod tests {
args: &[~"-c", ~"kill -1 $$"],
.. ProcessConfig::new()
};
let p = Process::new(args);
let p = Process::configure(args);
assert!(p.is_ok());
let mut p = p.unwrap();
match p.wait() {
@ -278,73 +483,64 @@ mod tests {
}
pub fn run_output(args: ProcessConfig) -> ~str {
let p = Process::new(args);
let p = Process::configure(args);
assert!(p.is_ok());
let mut p = p.unwrap();
assert!(p.io[0].is_none());
assert!(p.io[1].is_some());
let ret = read_all(p.io[1].get_mut_ref() as &mut Reader);
assert!(p.stdout.is_some());
let ret = read_all(p.stdout.get_mut_ref() as &mut Reader);
assert!(p.wait().success());
return ret;
}
// FIXME(#10380)
#[cfg(unix, not(target_os="android"))]
#[cfg(not(target_os="android"))]
iotest!(fn stdout_works() {
let io = ~[Ignored, CreatePipe(false, true)];
let args = ProcessConfig {
program: "/bin/sh",
args: &[~"-c", ~"echo foobar"],
io: io,
program: "echo",
args: &[~"foobar"],
stdout: CreatePipe(false, true),
.. ProcessConfig::new()
};
assert_eq!(run_output(args), ~"foobar\n");
})
// FIXME(#10380)
#[cfg(unix, not(target_os="android"))]
iotest!(fn set_cwd_works() {
let io = ~[Ignored, CreatePipe(false, true)];
let cwd = Some("/");
let cwd = Path::new("/");
let args = ProcessConfig {
program: "/bin/sh",
args: &[~"-c", ~"pwd"],
cwd: cwd,
io: io,
cwd: Some(&cwd),
stdout: CreatePipe(false, true),
.. ProcessConfig::new()
};
assert_eq!(run_output(args), ~"/\n");
})
// FIXME(#10380)
#[cfg(unix, not(target_os="android"))]
iotest!(fn stdin_works() {
let io = ~[CreatePipe(true, false),
CreatePipe(false, true)];
let args = ProcessConfig {
program: "/bin/sh",
args: &[~"-c", ~"read line; echo $line"],
io: io,
stdin: CreatePipe(true, false),
stdout: CreatePipe(false, true),
.. ProcessConfig::new()
};
let mut p = Process::new(args).unwrap();
p.io[0].get_mut_ref().write("foobar".as_bytes()).unwrap();
p.io[0] = None; // close stdin;
let out = read_all(p.io[1].get_mut_ref() as &mut Reader);
let mut p = Process::configure(args).unwrap();
p.stdin.get_mut_ref().write("foobar".as_bytes()).unwrap();
drop(p.stdin.take());
let out = read_all(p.stdout.get_mut_ref() as &mut Reader);
assert!(p.wait().success());
assert_eq!(out, ~"foobar\n");
})
// FIXME(#10380)
#[cfg(unix, not(target_os="android"))]
#[cfg(not(target_os="android"))]
iotest!(fn detach_works() {
let args = ProcessConfig {
program: "/bin/sh",
args: &[~"-c", ~"true"],
program: "true",
detach: true,
.. ProcessConfig::new()
};
let mut p = Process::new(args).unwrap();
let mut p = Process::configure(args).unwrap();
assert!(p.wait().success());
})
@ -355,10 +551,9 @@ mod tests {
uid: Some(10),
.. ProcessConfig::new()
};
assert!(Process::new(args).is_err());
assert!(Process::configure(args).is_err());
})
// FIXME(#10380)
#[cfg(unix, not(target_os="android"))]
iotest!(fn uid_works() {
use libc;
@ -369,11 +564,10 @@ mod tests {
gid: Some(unsafe { libc::getgid() as uint }),
.. ProcessConfig::new()
};
let mut p = Process::new(args).unwrap();
let mut p = Process::configure(args).unwrap();
assert!(p.wait().success());
})
// FIXME(#10380)
#[cfg(unix, not(target_os="android"))]
iotest!(fn uid_to_root_fails() {
use libc;
@ -387,6 +581,253 @@ mod tests {
gid: Some(0),
.. ProcessConfig::new()
};
assert!(Process::new(args).is_err());
assert!(Process::configure(args).is_err());
})
#[cfg(not(target_os="android"))]
iotest!(fn test_process_status() {
let mut status = Process::status("false", []).unwrap();
assert!(status.matches_exit_status(1));
status = Process::status("true", []).unwrap();
assert!(status.success());
})
iotest!(fn test_process_output_fail_to_start() {
match Process::output("/no-binary-by-this-name-should-exist", []) {
Err(e) => assert_eq!(e.kind, FileNotFound),
Ok(..) => fail!()
}
})
#[cfg(not(target_os="android"))]
iotest!(fn test_process_output_output() {
let ProcessOutput {status, output, error}
= Process::output("echo", [~"hello"]).unwrap();
let output_str = str::from_utf8_owned(output).unwrap();
assert!(status.success());
assert_eq!(output_str.trim().to_owned(), ~"hello");
// FIXME #7224
if !running_on_valgrind() {
assert_eq!(error, ~[]);
}
})
#[cfg(not(target_os="android"))]
iotest!(fn test_process_output_error() {
let ProcessOutput {status, output, error}
= Process::output("mkdir", [~"."]).unwrap();
assert!(status.matches_exit_status(1));
assert_eq!(output, ~[]);
assert!(!error.is_empty());
})
#[cfg(not(target_os="android"))]
iotest!(fn test_finish_once() {
let mut prog = Process::new("false", []).unwrap();
assert!(prog.wait().matches_exit_status(1));
})
#[cfg(not(target_os="android"))]
iotest!(fn test_finish_twice() {
let mut prog = Process::new("false", []).unwrap();
assert!(prog.wait().matches_exit_status(1));
assert!(prog.wait().matches_exit_status(1));
})
#[cfg(not(target_os="android"))]
iotest!(fn test_wait_with_output_once() {
let mut prog = Process::new("echo", [~"hello"]).unwrap();
let ProcessOutput {status, output, error} = prog.wait_with_output();
let output_str = str::from_utf8_owned(output).unwrap();
assert!(status.success());
assert_eq!(output_str.trim().to_owned(), ~"hello");
// FIXME #7224
if !running_on_valgrind() {
assert_eq!(error, ~[]);
}
})
#[cfg(not(target_os="android"))]
iotest!(fn test_wait_with_output_twice() {
let mut prog = Process::new("echo", [~"hello"]).unwrap();
let ProcessOutput {status, output, error} = prog.wait_with_output();
let output_str = str::from_utf8_owned(output).unwrap();
assert!(status.success());
assert_eq!(output_str.trim().to_owned(), ~"hello");
// FIXME #7224
if !running_on_valgrind() {
assert_eq!(error, ~[]);
}
let ProcessOutput {status, output, error} = prog.wait_with_output();
assert!(status.success());
assert_eq!(output, ~[]);
// FIXME #7224
if !running_on_valgrind() {
assert_eq!(error, ~[]);
}
})
#[cfg(unix,not(target_os="android"))]
pub fn run_pwd(dir: Option<&Path>) -> Process {
Process::configure(ProcessConfig {
program: "pwd",
cwd: dir,
.. ProcessConfig::new()
}).unwrap()
}
#[cfg(target_os="android")]
pub fn run_pwd(dir: Option<&Path>) -> Process {
Process::configure(ProcessConfig {
program: "/system/bin/sh",
args: &[~"-c",~"pwd"],
cwd: dir.map(|a| &*a),
.. ProcessConfig::new()
}).unwrap()
}
#[cfg(windows)]
pub fn run_pwd(dir: Option<&Path>) -> Process {
Process::configure(ProcessConfig {
program: "cmd",
args: &[~"/c", ~"cd"],
cwd: dir.map(|a| &*a),
.. ProcessConfig::new()
}).unwrap()
}
iotest!(fn test_keep_current_working_dir() {
use os;
let mut prog = run_pwd(None);
let output = str::from_utf8_owned(prog.wait_with_output().output).unwrap();
let parent_dir = os::getcwd();
let child_dir = Path::new(output.trim());
let parent_stat = parent_dir.stat().unwrap();
let child_stat = child_dir.stat().unwrap();
assert_eq!(parent_stat.unstable.device, child_stat.unstable.device);
assert_eq!(parent_stat.unstable.inode, child_stat.unstable.inode);
})
iotest!(fn test_change_working_directory() {
use os;
// test changing to the parent of os::getcwd() because we know
// the path exists (and os::getcwd() is not expected to be root)
let parent_dir = os::getcwd().dir_path();
let mut prog = run_pwd(Some(&parent_dir));
let output = str::from_utf8_owned(prog.wait_with_output().output).unwrap();
let child_dir = Path::new(output.trim());
let parent_stat = parent_dir.stat().unwrap();
let child_stat = child_dir.stat().unwrap();
assert_eq!(parent_stat.unstable.device, child_stat.unstable.device);
assert_eq!(parent_stat.unstable.inode, child_stat.unstable.inode);
})
#[cfg(unix,not(target_os="android"))]
pub fn run_env(env: Option<~[(~str, ~str)]>) -> Process {
Process::configure(ProcessConfig {
program: "env",
env: env.as_ref().map(|e| e.as_slice()),
.. ProcessConfig::new()
}).unwrap()
}
#[cfg(target_os="android")]
pub fn run_env(env: Option<~[(~str, ~str)]>) -> Process {
Process::configure(ProcessConfig {
program: "/system/bin/sh",
args: &[~"-c",~"set"],
env: env.as_ref().map(|e| e.as_slice()),
.. ProcessConfig::new()
}).unwrap()
}
#[cfg(windows)]
pub fn run_env(env: Option<~[(~str, ~str)]>) -> Process {
Process::configure(ProcessConfig {
program: "cmd",
args: &[~"/c", ~"set"],
env: env.as_ref().map(|e| e.as_slice()),
.. ProcessConfig::new()
}).unwrap()
}
#[cfg(not(target_os="android"))]
iotest!(fn test_inherit_env() {
use os;
if running_on_valgrind() { return; }
let mut prog = run_env(None);
let output = str::from_utf8_owned(prog.wait_with_output().output).unwrap();
let r = os::env();
for &(ref k, ref v) in r.iter() {
// don't check windows magical empty-named variables
assert!(k.is_empty() || output.contains(format!("{}={}", *k, *v)));
}
})
#[cfg(target_os="android")]
iotest!(fn test_inherit_env() {
use os;
if running_on_valgrind() { return; }
let mut prog = run_env(None);
let output = str::from_utf8_owned(prog.wait_with_output().output).unwrap();
let r = os::env();
for &(ref k, ref v) in r.iter() {
// don't check android RANDOM variables
if *k != ~"RANDOM" {
assert!(output.contains(format!("{}={}", *k, *v)) ||
output.contains(format!("{}=\'{}\'", *k, *v)));
}
}
})
iotest!(fn test_add_to_env() {
let new_env = ~[(~"RUN_TEST_NEW_ENV", ~"123")];
let mut prog = run_env(Some(new_env));
let result = prog.wait_with_output();
let output = str::from_utf8_lossy(result.output).into_owned();
assert!(output.contains("RUN_TEST_NEW_ENV=123"),
"didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output);
})
#[cfg(unix)]
pub fn sleeper() -> Process {
Process::new("sleep", [~"1000"]).unwrap()
}
#[cfg(windows)]
pub fn sleeper() -> Process {
Process::new("timeout", [~"1000"]).unwrap()
}
iotest!(fn test_kill() {
let mut p = sleeper();
Process::kill(p.id(), PleaseExitSignal).unwrap();
assert!(!p.wait().success());
})
#[ignore(cfg(windows))]
iotest!(fn test_exists() {
let mut p = sleeper();
assert!(Process::kill(p.id(), 0).is_ok());
p.signal_kill().unwrap();
assert!(!p.wait().success());
})
}

View File

@ -36,6 +36,7 @@ macro_rules! iotest (
use io::net::unix::*;
use io::timer::*;
use io::process::*;
use unstable::running_on_valgrind;
use str;
use util;

View File

@ -174,7 +174,6 @@ pub mod os;
pub mod io;
pub mod path;
pub mod rand;
pub mod run;
pub mod cast;
pub mod fmt;
pub mod cleanup;

View File

@ -179,6 +179,7 @@ pub trait IoFactory {
fn timer_init(&mut self) -> Result<~RtioTimer, IoError>;
fn spawn(&mut self, config: ProcessConfig)
-> Result<(~RtioProcess, ~[Option<~RtioPipe>]), IoError>;
fn kill(&mut self, pid: libc::pid_t, signal: int) -> Result<(), IoError>;
fn pipe_open(&mut self, fd: c_int) -> Result<~RtioPipe, IoError>;
fn tty_open(&mut self, fd: c_int, readable: bool)
-> Result<~RtioTTY, IoError>;

View File

@ -1,634 +0,0 @@
// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Utilities for spawning and managing processes
#[allow(missing_doc)];
#[deny(unused_must_use)];
use comm::Chan;
use io::Reader;
use io::process::ProcessExit;
use io::process;
use io;
use libc::{pid_t, c_int};
use libc;
use option::{None, Option, Some};
use task::spawn;
use path::{Path, GenericPath};
use result::Ok;
use str::Str;
use vec::Vector;
use clone::Clone;
/**
* A value representing a child process.
*
* The lifetime of this value is linked to the lifetime of the actual
* process - the Process destructor calls self.finish() which waits
* for the process to terminate.
*/
pub struct Process {
priv inner: process::Process,
}
/// Options that can be given when starting a Process.
pub struct ProcessOptions<'a> {
/**
* If this is None then the new process will have the same initial
* environment as the parent process.
*
* If this is Some(vec-of-names-and-values) then the new process will
* have an environment containing the given named values only.
*/
env: Option<~[(~str, ~str)]>,
/**
* If this is None then the new process will use the same initial working
* directory as the parent process.
*
* If this is Some(path) then the new process will use the given path
* for its initial working directory.
*/
dir: Option<&'a Path>,
/**
* If this is None then a new pipe will be created for the new process's
* input and Process.input() will provide a Writer to write to this pipe.
*
* If this is Some(file-descriptor) then the new process will read its input
* from the given file descriptor, Process.input_redirected() will return
* true, and Process.input() will fail.
*/
in_fd: Option<c_int>,
/**
* If this is None then a new pipe will be created for the new program's
* output and Process.output() will provide a Reader to read from this pipe.
*
* If this is Some(file-descriptor) then the new process will write its output
* to the given file descriptor, Process.output_redirected() will return
* true, and Process.output() will fail.
*/
out_fd: Option<c_int>,
/**
* If this is None then a new pipe will be created for the new program's
* error stream and Process.error() will provide a Reader to read from this pipe.
*
* If this is Some(file-descriptor) then the new process will write its error output
* to the given file descriptor, Process.error_redirected() will return true, and
* and Process.error() will fail.
*/
err_fd: Option<c_int>,
/// The uid to assume for the child process. For more information, see the
/// documentation in `io::process::ProcessConfig` about this field.
uid: Option<uint>,
/// The gid to assume for the child process. For more information, see the
/// documentation in `io::process::ProcessConfig` about this field.
gid: Option<uint>,
/// Flag as to whether the child process will be the leader of a new process
/// group or not. This allows the parent process to exit while the child is
/// still running. For more information, see the documentation in
/// `io::process::ProcessConfig` about this field.
detach: bool,
}
impl <'a> ProcessOptions<'a> {
/// Return a ProcessOptions that has None in every field.
pub fn new<'a>() -> ProcessOptions<'a> {
ProcessOptions {
env: None,
dir: None,
in_fd: None,
out_fd: None,
err_fd: None,
uid: None,
gid: None,
detach: false,
}
}
}
/// The output of a finished process.
pub struct ProcessOutput {
/// The status (exit code) of the process.
status: ProcessExit,
/// The data that the process wrote to stdout.
output: ~[u8],
/// The data that the process wrote to stderr.
error: ~[u8],
}
impl Process {
/**
* Spawns a new Process.
*
* # Arguments
*
* * prog - The path to an executable.
* * args - Vector of arguments to pass to the child process.
* * options - Options to configure the environment of the process,
* the working directory and the standard IO streams.
*/
pub fn new(prog: &str, args: &[~str],
options: ProcessOptions) -> io::IoResult<Process> {
let ProcessOptions {
env, dir, in_fd, out_fd, err_fd, uid, gid, detach
} = options;
let env = env.as_ref().map(|a| a.as_slice());
let cwd = dir.as_ref().map(|a| a.as_str().unwrap());
fn rtify(fd: Option<c_int>, input: bool) -> process::StdioContainer {
match fd {
Some(fd) => process::InheritFd(fd),
None => process::CreatePipe(input, !input),
}
}
let rtio = [rtify(in_fd, true), rtify(out_fd, false),
rtify(err_fd, false)];
let rtconfig = process::ProcessConfig {
program: prog,
args: args,
env: env,
cwd: cwd,
io: rtio,
uid: uid,
gid: gid,
detach: detach,
};
process::Process::new(rtconfig).map(|p| Process { inner: p })
}
/// Returns the unique id of the process
pub fn get_id(&self) -> pid_t { self.inner.id() }
/**
* Returns an io::Writer that can be used to write to this Process's stdin.
*
* Fails if there is no stdin available (it's already been removed by
* take_input)
*/
pub fn input<'a>(&'a mut self) -> &'a mut io::Writer {
self.inner.io[0].get_mut_ref() as &mut io::Writer
}
/**
* Returns an io::Reader that can be used to read from this Process's stdout.
*
* Fails if there is no stdout available (it's already been removed by
* take_output)
*/
pub fn output<'a>(&'a mut self) -> &'a mut io::Reader {
self.inner.io[1].get_mut_ref() as &mut io::Reader
}
/**
* Returns an io::Reader that can be used to read from this Process's stderr.
*
* Fails if there is no stderr available (it's already been removed by
* take_error)
*/
pub fn error<'a>(&'a mut self) -> &'a mut io::Reader {
self.inner.io[2].get_mut_ref() as &mut io::Reader
}
/**
* Closes the handle to the child process's stdin.
*/
pub fn close_input(&mut self) {
self.inner.io[0].take();
}
/**
* Closes the handle to stdout and stderr.
*/
pub fn close_outputs(&mut self) {
self.inner.io[1].take();
self.inner.io[2].take();
}
/**
* Closes the handle to stdin, waits for the child process to terminate,
* and returns the exit code.
*
* If the child has already been finished then the exit code is returned.
*/
pub fn finish(&mut self) -> ProcessExit { self.inner.wait() }
/**
* Closes the handle to stdin, waits for the child process to terminate, and
* reads and returns all remaining output of stdout and stderr, along with
* the exit code.
*
* If the child has already been finished then the exit code and any
* remaining unread output of stdout and stderr will be returned.
*
* This method will fail if the child process's stdout or stderr streams
* were redirected to existing file descriptors.
*/
pub fn finish_with_output(&mut self) -> ProcessOutput {
self.close_input();
let output = self.inner.io[1].take();
let error = self.inner.io[2].take();
// Spawn two entire schedulers to read both stdout and sterr
// in parallel so we don't deadlock while blocking on one
// or the other. FIXME (#2625): Surely there's a much more
// clever way to do this.
let (p, ch) = Chan::new();
let ch_clone = ch.clone();
spawn(proc() {
let mut error = error;
match error {
Some(ref mut e) => ch.send((2, e.read_to_end())),
None => ch.send((2, Ok(~[])))
}
});
spawn(proc() {
let mut output = output;
match output {
Some(ref mut e) => ch_clone.send((1, e.read_to_end())),
None => ch_clone.send((1, Ok(~[])))
}
});
let status = self.finish();
let (errs, outs) = match (p.recv(), p.recv()) {
((1, o), (2, e)) => (e, o),
((2, e), (1, o)) => (e, o),
((x, _), (y, _)) => {
fail!("unexpected file numbers: {}, {}", x, y);
}
};
return ProcessOutput {status: status,
output: outs.ok().unwrap_or(~[]),
error: errs.ok().unwrap_or(~[]) };
}
/**
* Terminates the process, giving it a chance to clean itself up if
* this is supported by the operating system.
*
* On Posix OSs SIGTERM will be sent to the process. On Win32
* TerminateProcess(..) will be called.
*/
pub fn destroy(&mut self) -> io::IoResult<()> {
let ret = self.inner.signal(io::process::PleaseExitSignal);
self.finish();
return ret;
}
/**
* Terminates the process as soon as possible without giving it a
* chance to clean itself up.
*
* On Posix OSs SIGKILL will be sent to the process. On Win32
* TerminateProcess(..) will be called.
*/
pub fn force_destroy(&mut self) -> io::IoResult<()> {
// This should never fail because we own the process
let ret = self.inner.signal(io::process::MustDieSignal);
self.finish();
return ret;
}
}
/**
* Spawns a process and waits for it to terminate. The process will
* inherit the current stdin/stdout/stderr file descriptors.
*
* # Arguments
*
* * prog - The path to an executable
* * args - Vector of arguments to pass to the child process
*
* # Return value
*
* The process's exit code, or None if the child process could not be started
*/
pub fn process_status(prog: &str, args: &[~str]) -> io::IoResult<ProcessExit> {
Process::new(prog, args, ProcessOptions {
in_fd: Some(unsafe { libc::dup(libc::STDIN_FILENO) }),
out_fd: Some(unsafe { libc::dup(libc::STDOUT_FILENO) }),
err_fd: Some(unsafe { libc::dup(libc::STDERR_FILENO) }),
.. ProcessOptions::new()
}).map(|mut p| p.finish())
}
/**
* Spawns a process, records all its output, and waits for it to terminate.
*
* # Arguments
*
* * prog - The path to an executable
* * args - Vector of arguments to pass to the child process
*
* # Return value
*
* The process's stdout/stderr output and exit code, or None if the child process could not be
* started.
*/
pub fn process_output(prog: &str, args: &[~str]) -> io::IoResult<ProcessOutput> {
Process::new(prog, args, ProcessOptions::new()).map(|mut p| {
p.finish_with_output()
})
}
#[cfg(test)]
mod tests {
use prelude::*;
use os;
use run;
use str;
use task::spawn;
use unstable::running_on_valgrind;
use io::pipe::PipeStream;
use io::{FileNotFound};
use libc::c_int;
#[test]
#[cfg(not(target_os="android"))] // FIXME(#10380)
fn test_process_status() {
let mut status = run::process_status("false", []).unwrap();
assert!(status.matches_exit_status(1));
status = run::process_status("true", []).unwrap();
assert!(status.success());
}
#[test]
fn test_process_output_fail_to_start() {
match run::process_output("/no-binary-by-this-name-should-exist", []) {
Err(e) => assert_eq!(e.kind, FileNotFound),
Ok(..) => fail!()
}
}
#[test]
#[cfg(not(target_os="android"))] // FIXME(#10380)
fn test_process_output_output() {
let run::ProcessOutput {status, output, error}
= run::process_output("echo", [~"hello"]).unwrap();
let output_str = str::from_utf8_owned(output).unwrap();
assert!(status.success());
assert_eq!(output_str.trim().to_owned(), ~"hello");
// FIXME #7224
if !running_on_valgrind() {
assert_eq!(error, ~[]);
}
}
#[test]
#[cfg(not(target_os="android"))] // FIXME(#10380)
fn test_process_output_error() {
let run::ProcessOutput {status, output, error}
= run::process_output("mkdir", [~"."]).unwrap();
assert!(status.matches_exit_status(1));
assert_eq!(output, ~[]);
assert!(!error.is_empty());
}
#[test]
#[ignore] // FIXME(#10016) cat never sees stdin close
fn test_pipes() {
let pipe_in = os::pipe();
let pipe_out = os::pipe();
let pipe_err = os::pipe();
let mut process = run::Process::new("cat", [], run::ProcessOptions {
in_fd: Some(pipe_in.input),
out_fd: Some(pipe_out.out),
err_fd: Some(pipe_err.out),
.. run::ProcessOptions::new()
}).unwrap();
os::close(pipe_in.input as int);
os::close(pipe_out.out as int);
os::close(pipe_err.out as int);
spawn(proc() {
writeclose(pipe_in.out, "test");
});
let actual = readclose(pipe_out.input);
readclose(pipe_err.input);
process.finish();
assert_eq!(~"test", actual);
}
fn writeclose(fd: c_int, s: &str) {
let mut writer = PipeStream::open(fd);
writer.write(s.as_bytes()).unwrap();
}
fn readclose(fd: c_int) -> ~str {
PipeStream::open(fd).read_to_str().unwrap()
}
#[test]
#[cfg(not(target_os="android"))] // FIXME(#10380)
fn test_finish_once() {
let mut prog = run::Process::new("false", [], run::ProcessOptions::new())
.unwrap();
assert!(prog.finish().matches_exit_status(1));
}
#[test]
#[cfg(not(target_os="android"))] // FIXME(#10380)
fn test_finish_twice() {
let mut prog = run::Process::new("false", [], run::ProcessOptions::new())
.unwrap();
assert!(prog.finish().matches_exit_status(1));
assert!(prog.finish().matches_exit_status(1));
}
#[test]
#[cfg(not(target_os="android"))] // FIXME(#10380)
fn test_finish_with_output_once() {
let mut prog = run::Process::new("echo", [~"hello"], run::ProcessOptions::new())
.unwrap();
let run::ProcessOutput {status, output, error}
= prog.finish_with_output();
let output_str = str::from_utf8_owned(output).unwrap();
assert!(status.success());
assert_eq!(output_str.trim().to_owned(), ~"hello");
// FIXME #7224
if !running_on_valgrind() {
assert_eq!(error, ~[]);
}
}
#[test]
#[cfg(not(target_os="android"))] // FIXME(#10380)
fn test_finish_with_output_twice() {
let mut prog = run::Process::new("echo", [~"hello"], run::ProcessOptions::new())
.unwrap();
let run::ProcessOutput {status, output, error}
= prog.finish_with_output();
let output_str = str::from_utf8_owned(output).unwrap();
assert!(status.success());
assert_eq!(output_str.trim().to_owned(), ~"hello");
// FIXME #7224
if !running_on_valgrind() {
assert_eq!(error, ~[]);
}
let run::ProcessOutput {status, output, error}
= prog.finish_with_output();
assert!(status.success());
assert_eq!(output, ~[]);
// FIXME #7224
if !running_on_valgrind() {
assert_eq!(error, ~[]);
}
}
#[cfg(unix,not(target_os="android"))]
fn run_pwd(dir: Option<&Path>) -> run::Process {
run::Process::new("pwd", [], run::ProcessOptions {
dir: dir,
.. run::ProcessOptions::new()
}).unwrap()
}
#[cfg(unix,target_os="android")]
fn run_pwd(dir: Option<&Path>) -> run::Process {
run::Process::new("/system/bin/sh", [~"-c",~"pwd"], run::ProcessOptions {
dir: dir,
.. run::ProcessOptions::new()
}).unwrap()
}
#[cfg(windows)]
fn run_pwd(dir: Option<&Path>) -> run::Process {
run::Process::new("cmd", [~"/c", ~"cd"], run::ProcessOptions {
dir: dir,
.. run::ProcessOptions::new()
}).unwrap()
}
#[test]
fn test_keep_current_working_dir() {
let mut prog = run_pwd(None);
let output = str::from_utf8_owned(prog.finish_with_output().output).unwrap();
let parent_dir = os::getcwd();
let child_dir = Path::new(output.trim());
let parent_stat = parent_dir.stat().unwrap();
let child_stat = child_dir.stat().unwrap();
assert_eq!(parent_stat.unstable.device, child_stat.unstable.device);
assert_eq!(parent_stat.unstable.inode, child_stat.unstable.inode);
}
#[test]
fn test_change_working_directory() {
// test changing to the parent of os::getcwd() because we know
// the path exists (and os::getcwd() is not expected to be root)
let parent_dir = os::getcwd().dir_path();
let mut prog = run_pwd(Some(&parent_dir));
let output = str::from_utf8_owned(prog.finish_with_output().output).unwrap();
let child_dir = Path::new(output.trim());
let parent_stat = parent_dir.stat().unwrap();
let child_stat = child_dir.stat().unwrap();
assert_eq!(parent_stat.unstable.device, child_stat.unstable.device);
assert_eq!(parent_stat.unstable.inode, child_stat.unstable.inode);
}
#[cfg(unix,not(target_os="android"))]
fn run_env(env: Option<~[(~str, ~str)]>) -> run::Process {
run::Process::new("env", [], run::ProcessOptions {
env: env,
.. run::ProcessOptions::new()
}).unwrap()
}
#[cfg(unix,target_os="android")]
fn run_env(env: Option<~[(~str, ~str)]>) -> run::Process {
run::Process::new("/system/bin/sh", [~"-c",~"set"], run::ProcessOptions {
env: env,
.. run::ProcessOptions::new()
}).unwrap()
}
#[cfg(windows)]
fn run_env(env: Option<~[(~str, ~str)]>) -> run::Process {
run::Process::new("cmd", [~"/c", ~"set"], run::ProcessOptions {
env: env,
.. run::ProcessOptions::new()
}).unwrap()
}
#[test]
#[cfg(not(target_os="android"))]
fn test_inherit_env() {
if running_on_valgrind() { return; }
let mut prog = run_env(None);
let output = str::from_utf8_owned(prog.finish_with_output().output).unwrap();
let r = os::env();
for &(ref k, ref v) in r.iter() {
// don't check windows magical empty-named variables
assert!(k.is_empty() || output.contains(format!("{}={}", *k, *v)));
}
}
#[test]
#[cfg(target_os="android")]
fn test_inherit_env() {
if running_on_valgrind() { return; }
let mut prog = run_env(None);
let output = str::from_utf8_owned(prog.finish_with_output().output).unwrap();
let r = os::env();
for &(ref k, ref v) in r.iter() {
// don't check android RANDOM variables
if *k != ~"RANDOM" {
assert!(output.contains(format!("{}={}", *k, *v)) ||
output.contains(format!("{}=\'{}\'", *k, *v)));
}
}
}
#[test]
fn test_add_to_env() {
let mut new_env = os::env();
new_env.push((~"RUN_TEST_NEW_ENV", ~"123"));
let mut prog = run_env(Some(new_env));
let output = str::from_utf8_owned(prog.finish_with_output().output).unwrap();
assert!(output.contains("RUN_TEST_NEW_ENV=123"));
}
}

View File

@ -21,8 +21,7 @@
// Test that cleanup scope for temporaries created in a match
// arm is confined to the match arm itself.
use std::{os, run};
use std::io::process;
use std::os;
struct Test { x: int }

View File

@ -20,8 +20,7 @@
// Test that cleanups for the RHS of shorcircuiting operators work.
use std::{os, run};
use std::io::process;
use std::os;
pub fn main() {
let args = os::args();

View File

@ -15,10 +15,10 @@
// memory, which makes for some *confusing* logs. That's why these are here
// instead of in std.
use std::io::timer;
use std::libc;
use std::run;
use std::str;
use std::io;
use std::io::process::{Process, ProcessOutput};
#[test]
fn test_destroy_once() {
@ -27,8 +27,8 @@ fn test_destroy_once() {
#[cfg(target_os="android")]
static PROG: &'static str = "ls"; // android don't have echo binary
let mut p = run::Process::new(PROG, [], run::ProcessOptions::new()).unwrap();
p.destroy(); // this shouldn't crash (and nor should the destructor)
let mut p = Process::new(PROG, []).unwrap();
p.signal_exit().unwrap(); // this shouldn't crash (and nor should the destructor)
}
#[test]
@ -38,12 +38,12 @@ fn test_destroy_twice() {
#[cfg(target_os="android")]
static PROG: &'static str = "ls"; // android don't have echo binary
let mut p = match run::Process::new(PROG, [], run::ProcessOptions::new()) {
let mut p = match Process::new(PROG, []) {
Ok(p) => p,
Err(e) => fail!("wut: {}", e),
};
p.destroy(); // this shouldnt crash...
p.destroy(); // ...and nor should this (and nor should the destructor)
p.signal_exit().unwrap(); // this shouldnt crash...
p.signal_exit().unwrap(); // ...and nor should this (and nor should the destructor)
}
fn test_destroy_actually_kills(force: bool) {
@ -59,14 +59,14 @@ fn test_destroy_actually_kills(force: bool) {
#[cfg(unix,not(target_os="android"))]
fn process_exists(pid: libc::pid_t) -> bool {
let run::ProcessOutput {output, ..} = run::process_output("ps", [~"-p", pid.to_str()])
let ProcessOutput {output, ..} = Process::output("ps", [~"-p", pid.to_str()])
.unwrap();
str::from_utf8_owned(output).unwrap().contains(pid.to_str())
}
#[cfg(unix,target_os="android")]
fn process_exists(pid: libc::pid_t) -> bool {
let run::ProcessOutput {output, ..} = run::process_output("/system/bin/ps", [pid.to_str()])
let ProcessOutput {output, ..} = Process::output("/system/bin/ps", [pid.to_str()])
.unwrap();
str::from_utf8_owned(output).unwrap().contains(~"root")
}
@ -91,18 +91,20 @@ fn test_destroy_actually_kills(force: bool) {
}
// this process will stay alive indefinitely trying to read from stdin
let mut p = run::Process::new(BLOCK_COMMAND, [], run::ProcessOptions::new())
.unwrap();
let mut p = Process::new(BLOCK_COMMAND, []).unwrap();
assert!(process_exists(p.get_id()));
assert!(process_exists(p.id()));
if force {
p.force_destroy();
p.signal_kill().unwrap();
} else {
p.destroy();
p.signal_exit().unwrap();
}
assert!(!process_exists(p.get_id()));
if process_exists(p.id()) {
timer::sleep(500);
assert!(!process_exists(p.id()));
}
}
#[test]

View File

@ -31,14 +31,11 @@ pub fn main () {
let config = process::ProcessConfig {
program : args[0].as_slice(),
args : &[~"child"],
env : None,
cwd : None,
io : &[],
uid: None,
gid: None,
detach: false,
stdout: process::Ignored,
stderr: process::Ignored,
.. process::ProcessConfig::new()
};
let mut p = process::Process::new(config).unwrap();
let mut p = process::Process::configure(config).unwrap();
println!("{}", p.wait());
}

View File

@ -29,8 +29,7 @@ fn main() {
let config = process::ProcessConfig {
program : "/bin/sh",
args : &[~"-c", ~"read a"],
io : &[process::CreatePipe(true, false)],
args: &[~"-c", ~"read a"],
detach: true,
.. process::ProcessConfig::new()
};
@ -40,14 +39,14 @@ fn main() {
l.register(Interrupt).unwrap();
// spawn the child
let mut p = process::Process::new(config).unwrap();
let mut p = process::Process::configure(config).unwrap();
// send an interrupt to everyone in our process group
unsafe { libc::funcs::posix88::signal::kill(0, libc::SIGINT); }
// Wait for the child process to die (terminate it's stdin and the read
// should fail).
drop(p.io[0].take());
drop(p.stdin.take());
match p.wait() {
process::ExitStatus(..) => {}
process::ExitSignal(..) => fail!()

View File

@ -20,8 +20,8 @@
// ignore-fast calling itself doesn't work on check-fast
use std::{os, run};
use std::io::process;
use std::os;
use std::io::process::{Process, ExitSignal, ExitStatus};
pub fn main() {
let args = os::args();
@ -29,11 +29,11 @@ pub fn main() {
// Raise a segfault.
unsafe { *(0 as *mut int) = 0; }
} else {
let status = run::process_status(args[0], [~"signal"]).unwrap();
let status = Process::status(args[0], [~"signal"]).unwrap();
// Windows does not have signal, so we get exit status 0xC0000028 (STATUS_BAD_STACK).
match status {
process::ExitSignal(_) if cfg!(unix) => {},
process::ExitStatus(0xC0000028) if cfg!(windows) => {},
ExitSignal(_) if cfg!(unix) => {},
ExitStatus(0xC0000028) if cfg!(windows) => {},
_ => fail!("invalid termination (was not signalled): {:?}", status)
}
}