diff --git a/src/compiletest/procsrv.rs b/src/compiletest/procsrv.rs index 1016c3cf0e6..56cdc31090e 100644 --- a/src/compiletest/procsrv.rs +++ b/src/compiletest/procsrv.rs @@ -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 { 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 { + input: Option<~str>) -> Option { 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) diff --git a/src/compiletest/runtest.rs b/src/compiletest/runtest.rs index ff978a308e6..ac0042682df 100644 --- a/src/compiletest/runtest.rs +++ b/src/compiletest/runtest.rs @@ -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(); } _=> { diff --git a/src/libextra/workcache.rs b/src/libextra/workcache.rs index 007b54adbe5..133e5bc71f8 100644 --- a/src/libextra/workcache.rs +++ b/src/libextra/workcache.rs @@ -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. diff --git a/src/libnative/io/mod.rs b/src/libnative/io/mod.rs index 0f9439b3eb5..2f4dc7817d3 100644 --- a/src/libnative/io/mod.rs +++ b/src/libnative/io/mod.rs @@ -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) } diff --git a/src/libnative/io/process.rs b/src/libnative/io/process.rs index affa3ebf544..0b833f47395 100644 --- a/src/libnative/io/process.rs +++ b/src/libnative/io/process.rs @@ -66,18 +66,16 @@ impl Process { -> Result<(Process, ~[Option]), 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], - idx: uint) -> (Option, 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]) + -> (Option, 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); + }) }) } } diff --git a/src/librustc/back/archive.rs b/src/librustc/back/archive.rs index 6dec7550fec..1df34576c3e 100644 --- a/src/librustc/back/archive.rs +++ b/src/librustc/back/archive.rs @@ -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)); diff --git a/src/librustc/back/link.rs b/src/librustc/back/link.rs index bd0748761ee..89cdd4dbe77 100644 --- a/src/librustc/back/link.rs +++ b/src/librustc/back/link.rs @@ -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) => { diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs index 354b5cb0f14..ae22d9c84dc 100644 --- a/src/librustdoc/test.rs +++ b/src/librustdoc/test.rs @@ -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, 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) => { diff --git a/src/librustuv/process.rs b/src/librustuv/process.rs index a0623059bd7..8a6b4d3150e 100644 --- a/src/librustuv/process.rs +++ b/src/librustuv/process.rs @@ -44,7 +44,10 @@ impl Process { -> Result<(~Process, ~[Option]), 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::(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, diff --git a/src/librustuv/uvio.rs b/src/librustuv/uvio.rs index 14406cb2a6a..c49a24889a1 100644 --- a/src/librustuv/uvio.rs +++ b/src/librustuv/uvio.rs @@ -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) { diff --git a/src/librustuv/uvll.rs b/src/librustuv/uvll.rs index c5ff5c60b80..039f2e8bc85 100644 --- a/src/librustuv/uvll.rs +++ b/src/librustuv/uvll.rs @@ -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; diff --git a/src/libstd/io/fs.rs b/src/libstd/io/fs.rs index 7f2af92b078..58c0caa3402 100644 --- a/src/libstd/io/fs.rs +++ b/src/libstd/io/fs.rs @@ -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"); diff --git a/src/libstd/io/mod.rs b/src/libstd/io/mod.rs index 91a8d599326..f4ff4dd7e5d 100644 --- a/src/libstd/io/mod.rs +++ b/src/libstd/io/mod.rs @@ -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, diff --git a/src/libstd/io/process.rs b/src/libstd/io/process.rs index 6540fcd85d3..b3365726465 100644 --- a/src/libstd/io/process.rs +++ b/src/libstd/io/process.rs @@ -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], + + /// 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, + + /// 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, + + /// 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, + + /// Extra I/O handles as configured by the original `ProcessConfig` when + /// this process was created. This is by default empty. + extra_io: ~[Option], } -/// 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 { + /// 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::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 { + 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 { + Process::new(prog, args).map(|mut p| p.wait()) + } + + /// Creates a new process with the specified configuration. + pub fn configure(config: ProcessConfig) -> IoResult { 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) -> Port> { + 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()); }) } diff --git a/src/libstd/io/test.rs b/src/libstd/io/test.rs index 04ecb479060..4b499aa5c12 100644 --- a/src/libstd/io/test.rs +++ b/src/libstd/io/test.rs @@ -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; diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index 2c81bcd4b0e..d7fc85bc259 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -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; diff --git a/src/libstd/rt/rtio.rs b/src/libstd/rt/rtio.rs index 5573f8ec02e..edb480fe4cb 100644 --- a/src/libstd/rt/rtio.rs +++ b/src/libstd/rt/rtio.rs @@ -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>; diff --git a/src/libstd/run.rs b/src/libstd/run.rs deleted file mode 100644 index 9ea8f6447dd..00000000000 --- a/src/libstd/run.rs +++ /dev/null @@ -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 or the MIT license -// , 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, - - /** - * 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, - - /** - * 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, - - /// The uid to assume for the child process. For more information, see the - /// documentation in `io::process::ProcessConfig` about this field. - uid: Option, - - /// The gid to assume for the child process. For more information, see the - /// documentation in `io::process::ProcessConfig` about this field. - gid: Option, - - /// 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 { - 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, 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 { - 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 { - 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")); - } -} diff --git a/src/test/run-pass/cleanup-arm-conditional.rs b/src/test/run-pass/cleanup-arm-conditional.rs index c53375c1f5b..360f94564b7 100644 --- a/src/test/run-pass/cleanup-arm-conditional.rs +++ b/src/test/run-pass/cleanup-arm-conditional.rs @@ -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 } diff --git a/src/test/run-pass/cleanup-shortcircuit.rs b/src/test/run-pass/cleanup-shortcircuit.rs index 4df457fd862..825f699601f 100644 --- a/src/test/run-pass/cleanup-shortcircuit.rs +++ b/src/test/run-pass/cleanup-shortcircuit.rs @@ -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(); diff --git a/src/test/run-pass/core-run-destroy.rs b/src/test/run-pass/core-run-destroy.rs index fcf797cf3e6..60f98b1075a 100644 --- a/src/test/run-pass/core-run-destroy.rs +++ b/src/test/run-pass/core-run-destroy.rs @@ -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] diff --git a/src/test/run-pass/issue-10626.rs b/src/test/run-pass/issue-10626.rs index 94964fbc89b..14115fa52da 100644 --- a/src/test/run-pass/issue-10626.rs +++ b/src/test/run-pass/issue-10626.rs @@ -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()); } diff --git a/src/test/run-pass/process-detach.rs b/src/test/run-pass/process-detach.rs index 91f77caf8a3..ffb446d1b33 100644 --- a/src/test/run-pass/process-detach.rs +++ b/src/test/run-pass/process-detach.rs @@ -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!() diff --git a/src/test/run-pass/signal-exit-status.rs b/src/test/run-pass/signal-exit-status.rs index a5d6b890e22..2658755cea1 100644 --- a/src/test/run-pass/signal-exit-status.rs +++ b/src/test/run-pass/signal-exit-status.rs @@ -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) } }