From d0386ea236cab5e3be018007a56985a28bed6dfd Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Mon, 5 May 2014 14:33:55 -0700 Subject: [PATCH] Process::new etc should support non-utf8 commands/args The existing APIs for spawning processes took strings for the command and arguments, but the underlying system may not impose utf8 encoding, so this is overly limiting. The assumption we actually want to make is just that the command and arguments are viewable as [u8] slices with no interior NULLs, i.e., as CStrings. The ToCStr trait is a handy bound for types that meet this requirement (such as &str and Path). However, since the commands and arguments are often a mixture of strings and paths, it would be inconvenient to take a slice with a single T: ToCStr bound. So this patch revamps the process creation API to instead use a builder-style interface, called `Command`, allowing arguments to be added one at a time with differing ToCStr implementations for each. The initial cut of the builder API has some drawbacks that can be addressed once issue #13851 (libstd as a facade) is closed. These are detailed as FIXMEs. Closes #11650. [breaking-change] --- src/compiletest/procsrv.rs | 20 +- src/compiletest/runtest.rs | 30 +- src/libnative/io/mod.rs | 7 +- src/libnative/io/process.rs | 217 +++---- src/librustc/back/archive.rs | 28 +- src/librustc/back/link.rs | 211 +++---- src/librustdoc/test.rs | 6 +- src/librustuv/process.rs | 104 +-- src/librustuv/uvio.rs | 7 +- src/libstd/io/mod.rs | 2 +- src/libstd/io/process.rs | 592 +++++++++--------- src/libstd/rt/rtio.rs | 59 +- src/libstd/unstable/dynamic_lib.rs | 2 - src/libworkcache/lib.rs | 7 +- src/test/auxiliary/linkage-visibility.rs | 3 +- .../run-make/unicode-input/multiple_files.rs | 7 +- .../run-make/unicode-input/span_length.rs | 8 +- src/test/run-pass/backtrace.rs | 28 +- src/test/run-pass/core-run-destroy.rs | 12 +- src/test/run-pass/issue-10626.rs | 15 +- src/test/run-pass/issue-13304.rs | 13 +- src/test/run-pass/logging-separate-lines.rs | 13 +- src/test/run-pass/out-of-stack.rs | 6 +- src/test/run-pass/process-detach.rs | 11 +- src/test/run-pass/signal-exit-status.rs | 5 +- .../run-pass/sigpipe-should-be-ignored.rs | 6 +- 26 files changed, 687 insertions(+), 732 deletions(-) diff --git a/src/compiletest/procsrv.rs b/src/compiletest/procsrv.rs index 7d3aa33aae853..d45db5827402b 100644 --- a/src/compiletest/procsrv.rs +++ b/src/compiletest/procsrv.rs @@ -10,7 +10,7 @@ use std::os; use std::str; -use std::io::process::{ProcessExit, Process, ProcessConfig, ProcessOutput}; +use std::io::process::{ProcessExit, Command, Process, ProcessOutput}; #[cfg(target_os = "win32")] fn target_env(lib_path: &str, prog: &str) -> Vec<(~str, ~str)> { @@ -68,14 +68,7 @@ pub fn run(lib_path: &str, input: Option<~str>) -> Option { let env = env.clone().append(target_env(lib_path, prog).as_slice()); - let mut opt_process = Process::configure(ProcessConfig { - program: prog, - args: args, - env: Some(env.as_slice()), - .. ProcessConfig::new() - }); - - match opt_process { + match Command::new(prog).args(args).env(env.as_slice()).spawn() { Ok(ref mut process) => { for input in input.iter() { process.stdin.get_mut_ref().write(input.as_bytes()).unwrap(); @@ -99,14 +92,7 @@ pub fn run_background(lib_path: &str, input: Option<~str>) -> Option { let env = env.clone().append(target_env(lib_path, prog).as_slice()); - let opt_process = Process::configure(ProcessConfig { - program: prog, - args: args, - env: Some(env.as_slice()), - .. ProcessConfig::new() - }); - - match opt_process { + match Command::new(prog).args(args).env(env.as_slice()).spawn() { Ok(mut process) => { for input in input.iter() { process.stdin.get_mut_ref().write(input.as_bytes()).unwrap(); diff --git a/src/compiletest/runtest.rs b/src/compiletest/runtest.rs index e47e7dc33d8e4..19439008d42dd 100644 --- a/src/compiletest/runtest.rs +++ b/src/compiletest/runtest.rs @@ -404,7 +404,7 @@ fn run_debuginfo_gdb_test(config: &config, props: &TestProps, testfile: &Path) { } fn run_debuginfo_lldb_test(config: &config, props: &TestProps, testfile: &Path) { - use std::io::process::{Process, ProcessConfig, ProcessOutput}; + use std::io::process::{Command, ProcessOutput}; if config.lldb_python_dir.is_none() { fatal("Can't run LLDB test because LLDB's python path is not set.".to_owned()); @@ -467,25 +467,13 @@ fn run_debuginfo_lldb_test(config: &config, props: &TestProps, testfile: &Path) fn run_lldb(config: &config, test_executable: &Path, debugger_script: &Path) -> ProcRes { // Prepare the lldb_batchmode which executes the debugger script - let lldb_batchmode_script = "./src/etc/lldb_batchmode.py".to_owned(); - let test_executable_str = test_executable.as_str().unwrap().to_owned(); - let debugger_script_str = debugger_script.as_str().unwrap().to_owned(); - let commandline = format!("python {} {} {}", - lldb_batchmode_script.as_slice(), - test_executable_str.as_slice(), - debugger_script_str.as_slice()); - - let args = &[lldb_batchmode_script, test_executable_str, debugger_script_str]; - let env = &[("PYTHONPATH".to_owned(), config.lldb_python_dir.clone().unwrap())]; - - let mut opt_process = Process::configure(ProcessConfig { - program: "python", - args: args, - env: Some(env), - .. ProcessConfig::new() - }); - - let (status, out, err) = match opt_process { + let mut cmd = Command::new("python"); + cmd.arg("./src/etc/lldb_batchmode.py") + .arg(test_executable) + .arg(debugger_script) + .env([("PYTHONPATH", config.lldb_python_dir.clone().unwrap().as_slice())]); + + let (status, out, err) = match cmd.spawn() { Ok(ref mut process) => { let ProcessOutput { status, output, error } = process.wait_with_output(); @@ -503,7 +491,7 @@ fn run_debuginfo_lldb_test(config: &config, props: &TestProps, testfile: &Path) status: status, stdout: out, stderr: err, - cmdline: commandline + cmdline: format!("{}", cmd) }; } } diff --git a/src/libnative/io/mod.rs b/src/libnative/io/mod.rs index f2c2c66e1425f..7588dbf06df91 100644 --- a/src/libnative/io/mod.rs +++ b/src/libnative/io/mod.rs @@ -27,13 +27,12 @@ use std::c_str::CString; use std::io; use std::io::IoError; use std::io::net::ip::SocketAddr; -use std::io::process::ProcessConfig; use std::io::signal::Signum; use std::os; use std::rt::rtio; use std::rt::rtio::{RtioTcpStream, RtioTcpListener, RtioUdpSocket}; use std::rt::rtio::{RtioUnixListener, RtioPipe, RtioFileStream, RtioProcess}; -use std::rt::rtio::{RtioSignal, RtioTTY, CloseBehavior, RtioTimer}; +use std::rt::rtio::{RtioSignal, RtioTTY, CloseBehavior, RtioTimer, ProcessConfig}; use ai = std::io::net::addrinfo; // Local re-exports @@ -258,10 +257,10 @@ impl rtio::IoFactory for IoFactory { fn timer_init(&mut self) -> IoResult> { timer::Timer::new().map(|t| box t as Box) } - fn spawn(&mut self, config: ProcessConfig) + fn spawn(&mut self, cfg: ProcessConfig) -> IoResult<(Box, Vec>>)> { - process::Process::spawn(config).map(|(p, io)| { + process::Process::spawn(cfg).map(|(p, io)| { (box p as Box, io.move_iter().map(|p| p.map(|p| { box p as Box diff --git a/src/libnative/io/process.rs b/src/libnative/io/process.rs index 81c76bba7a0eb..c8317d4390e76 100644 --- a/src/libnative/io/process.rs +++ b/src/libnative/io/process.rs @@ -14,13 +14,14 @@ use libc; use std::os; use std::ptr; use std::rt::rtio; +use std::rt::rtio::ProcessConfig; +use std::c_str::CString; use p = std::io::process; use super::IoResult; use super::file; #[cfg(windows)] use std::mem; -#[cfg(windows)] use std::strbuf::StrBuf; #[cfg(not(windows))] use super::retry; /** @@ -50,27 +51,11 @@ impl Process { /// Creates a new process using native process-spawning abilities provided /// by the OS. Operations on this process will be blocking instead of using /// the runtime for sleeping just this current task. - /// - /// # Arguments - /// - /// * prog - the program to run - /// * args - the arguments to pass to the program, not including the program - /// itself - /// * env - an optional environment to specify for the child process. If - /// this value is `None`, then the child will inherit the parent's - /// environment - /// * cwd - an optionally specified current working directory of the child, - /// defaulting to the parent's current working directory - /// * stdin, stdout, stderr - These optionally specified file descriptors - /// dictate where the stdin/out/err of the child process will go. If - /// these are `None`, then this module will bind the input/output to an - /// os pipe instead. This process takes ownership of these file - /// descriptors, closing them upon destruction of the process. - pub fn spawn(config: p::ProcessConfig) + pub fn spawn(cfg: ProcessConfig) -> Result<(Process, Vec>), io::IoError> { // right now we only handle stdin/stdout/stderr. - if config.extra_io.len() > 0 { + if cfg.extra_io.len() > 0 { return Err(super::unimpl()); } @@ -94,14 +79,11 @@ impl Process { } let mut ret_io = Vec::new(); - 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 (in_pipe, in_fd) = get_io(cfg.stdin, &mut ret_io); + let (out_pipe, out_fd) = get_io(cfg.stdout, &mut ret_io); + let (err_pipe, err_fd) = get_io(cfg.stderr, &mut ret_io); - let env = config.env.map(|a| a.to_owned()); - let cwd = config.cwd.map(|a| Path::new(a)); - let res = spawn_process_os(config, env, cwd.as_ref(), in_fd, out_fd, - err_fd); + let res = spawn_process_os(cfg, in_fd, out_fd, err_fd); unsafe { for pipe in in_pipe.iter() { let _ = libc::close(pipe.input); } @@ -242,11 +224,8 @@ struct SpawnProcessResult { } #[cfg(windows)] -fn spawn_process_os(config: p::ProcessConfig, - env: Option<~[(~str, ~str)]>, - dir: Option<&Path>, - in_fd: c_int, out_fd: c_int, - err_fd: c_int) -> IoResult { +fn spawn_process_os(cfg: ProcessConfig, in_fd: c_int, out_fd: c_int, err_fd: c_int) + -> IoResult { use libc::types::os::arch::extra::{DWORD, HANDLE, STARTUPINFO}; use libc::consts::os::extra::{ TRUE, FALSE, @@ -264,7 +243,7 @@ fn spawn_process_os(config: p::ProcessConfig, use std::mem; - if config.gid.is_some() || config.uid.is_some() { + if cfg.gid.is_some() || cfg.uid.is_some() { return Err(io::IoError { kind: io::OtherIoError, desc: "unsupported gid/uid requested on windows", @@ -273,7 +252,6 @@ fn spawn_process_os(config: p::ProcessConfig, } unsafe { - let mut si = zeroed_startupinfo(); si.cb = mem::size_of::() as DWORD; si.dwFlags = STARTF_USESTDHANDLES; @@ -313,23 +291,26 @@ fn spawn_process_os(config: p::ProcessConfig, } } - let cmd = make_command_line(config.program, config.args); + let mut cmd_str = make_command_line(cfg.program, cfg.args); let mut pi = zeroed_process_information(); let mut create_err = None; // stolen from the libuv code. let mut flags = 0; - if config.detach { + if cfg.detach { flags |= libc::DETACHED_PROCESS | libc::CREATE_NEW_PROCESS_GROUP; } - with_envp(env, |envp| { - with_dirp(dir, |dirp| { - cmd.with_c_str(|cmdp| { - let created = CreateProcessA(ptr::null(), mem::transmute(cmdp), - ptr::mut_null(), ptr::mut_null(), TRUE, - flags, envp, dirp, &mut si, - &mut pi); + with_envp(cfg.env, |envp| { + with_dirp(cfg.cwd, |cwdp| { + cmd_str.with_mut_ref(|cmdp| { + let created = CreateProcessA(ptr::null(), + mem::transmute(cmdp), + ptr::mut_null(), + ptr::mut_null(), + TRUE, + flags, envp, cwdp, + &mut si, &mut pi); if created == FALSE { create_err = Some(super::last_error()); } @@ -395,63 +376,61 @@ fn zeroed_process_information() -> libc::types::os::arch::extra::PROCESS_INFORMA } #[cfg(windows)] -fn make_command_line(prog: &str, args: &[~str]) -> ~str { - let mut cmd = StrBuf::new(); +fn make_command_line(prog: &CString, args: &[CString]) -> CString { + let mut cmd: Vec = Vec::new(); append_arg(&mut cmd, prog); for arg in args.iter() { - cmd.push_char(' '); - append_arg(&mut cmd, *arg); + cmd.push(' ' as u8); + append_arg(&mut cmd, arg); } - return cmd.into_owned(); + return cmd.as_slice().to_c_str(); - fn append_arg(cmd: &mut StrBuf, arg: &str) { - let quote = arg.chars().any(|c| c == ' ' || c == '\t'); + fn append_arg(cmd: &mut Vec, arg: &CString) { + let arg = arg.as_bytes_no_nul(); + let quote = arg.iter().any(|c| *c as char == ' ' || *c as char == '\t'); if quote { - cmd.push_char('"'); + cmd.push('\"' as u8); } for i in range(0u, arg.len()) { - append_char_at(cmd, arg, i); + append_at(cmd, arg, i); } if quote { - cmd.push_char('"'); + cmd.push('\"' as u8); } } - fn append_char_at(cmd: &mut StrBuf, arg: &str, i: uint) { + fn append_at(cmd: &mut Vec, arg: &[u8], i: uint) { match arg[i] as char { - '"' => { + '\"' => { // Escape quotes. - cmd.push_str("\\\""); + cmd.push('\\' as u8); + cmd.push('\"' as u8); } '\\' => { + cmd.push('\\' as u8); if backslash_run_ends_in_quote(arg, i) { // Double all backslashes that are in runs before quotes. - cmd.push_str("\\\\"); - } else { - // Pass other backslashes through unescaped. - cmd.push_char('\\'); + cmd.push('\\' as u8); } } - c => { - cmd.push_char(c); + _ => { + cmd.push(arg[i]); } } } - fn backslash_run_ends_in_quote(s: &str, mut i: uint) -> bool { + fn backslash_run_ends_in_quote(s: &[u8], mut i: uint) -> bool { while i < s.len() && s[i] as char == '\\' { i += 1; } - return i < s.len() && s[i] as char == '"'; + return i < s.len() && s[i] as char == '\"'; } } #[cfg(unix)] -fn spawn_process_os(config: p::ProcessConfig, - env: Option<~[(~str, ~str)]>, - dir: Option<&Path>, - in_fd: c_int, out_fd: c_int, - err_fd: c_int) -> IoResult { +fn spawn_process_os(cfg: ProcessConfig, in_fd: c_int, out_fd: c_int, err_fd: c_int) + -> IoResult +{ use libc::funcs::posix88::unistd::{fork, dup2, close, chdir, execvp}; use libc::funcs::bsd44::getdtablesize; use io::c; @@ -479,11 +458,10 @@ fn spawn_process_os(config: p::ProcessConfig, assert_eq!(ret, 0); } - let dirp = dir.map(|p| p.to_c_str()); - let dirp = dirp.as_ref().map(|c| c.with_ref(|p| p)).unwrap_or(ptr::null()); + let dirp = cfg.cwd.map(|c| c.with_ref(|p| p)).unwrap_or(ptr::null()); - with_envp(env, proc(envp) { - with_argv(config.program, config.args, proc(argv) unsafe { + with_envp(cfg.env, proc(envp) { + with_argv(cfg.program, cfg.args, proc(argv) unsafe { let pipe = os::pipe(); let mut input = file::FileDesc::new(pipe.input, true); let mut output = file::FileDesc::new(pipe.out, true); @@ -584,7 +562,7 @@ fn spawn_process_os(config: p::ProcessConfig, } } - match config.gid { + match cfg.gid { Some(u) => { if libc::setgid(u as libc::gid_t) != 0 { fail(&mut output); @@ -592,7 +570,7 @@ fn spawn_process_os(config: p::ProcessConfig, } None => {} } - match config.uid { + match cfg.uid { Some(u) => { // When dropping privileges from root, the `setgroups` call will // remove any extraneous groups. If we don't call this, then @@ -612,7 +590,7 @@ fn spawn_process_os(config: p::ProcessConfig, } None => {} } - if config.detach { + if cfg.detach { // Don't check the error of setsid because it fails if we're the // process leader already. We just forked so it shouldn't return // error, but ignore it anyway. @@ -631,47 +609,47 @@ fn spawn_process_os(config: p::ProcessConfig, } #[cfg(unix)] -fn with_argv(prog: &str, args: &[~str], cb: proc(**libc::c_char) -> T) -> T { - // We can't directly convert `str`s into `*char`s, as someone needs to hold - // a reference to the intermediary byte buffers. So first build an array to - // hold all the ~[u8] byte strings. - let mut tmps = Vec::with_capacity(args.len() + 1); - - tmps.push(prog.to_c_str()); - - for arg in args.iter() { - tmps.push(arg.to_c_str()); - } - - // Next, convert each of the byte strings into a pointer. This is - // technically unsafe as the caller could leak these pointers out of our - // scope. - let mut ptrs: Vec<_> = tmps.iter().map(|tmp| tmp.with_ref(|buf| buf)).collect(); - - // Finally, make sure we add a null pointer. +fn with_argv(prog: &CString, args: &[CString], cb: proc(**libc::c_char) -> T) -> T { + let mut ptrs: Vec<*libc::c_char> = Vec::with_capacity(args.len()+1); + + // Convert the CStrings into an array of pointers. Note: the + // lifetime of the various CStrings involved is guaranteed to be + // larger than the lifetime of our invocation of cb, but this is + // technically unsafe as the callback could leak these pointers + // out of our scope. + ptrs.push(prog.with_ref(|buf| buf)); + ptrs.extend(args.iter().map(|tmp| tmp.with_ref(|buf| buf))); + + // Add a terminating null pointer (required by libc). ptrs.push(ptr::null()); cb(ptrs.as_ptr()) } #[cfg(unix)] -fn with_envp(env: Option<~[(~str, ~str)]>, cb: proc(*c_void) -> T) -> T { +fn with_envp(env: Option<&[(CString, CString)]>, cb: proc(*c_void) -> T) -> T { // On posixy systems we can pass a char** for envp, which is a - // null-terminated array of "k=v\n" strings. Like `with_argv`, we have to - // have a temporary buffer to hold the intermediary `~[u8]` byte strings. + // null-terminated array of "k=v\0" strings. Since we must create + // these strings locally, yet expose a raw pointer to them, we + // create a temporary vector to own the CStrings that outlives the + // call to cb. match env { Some(env) => { let mut tmps = Vec::with_capacity(env.len()); for pair in env.iter() { - let kv = format!("{}={}", *pair.ref0(), *pair.ref1()); - tmps.push(kv.to_c_str()); + let mut kv = Vec::new(); + kv.push_all(pair.ref0().as_bytes_no_nul()); + kv.push('=' as u8); + kv.push_all(pair.ref1().as_bytes()); // includes terminal \0 + tmps.push(kv); } - // Once again, this is unsafe. - let mut ptrs: Vec<*libc::c_char> = tmps.iter() - .map(|tmp| tmp.with_ref(|buf| buf)) - .collect(); + // As with `with_argv`, this is unsafe, since cb could leak the pointers. + let mut ptrs: Vec<*libc::c_char> = + tmps.iter() + .map(|tmp| tmp.as_ptr() as *libc::c_char) + .collect(); ptrs.push(ptr::null()); cb(ptrs.as_ptr() as *c_void) @@ -681,7 +659,7 @@ fn with_envp(env: Option<~[(~str, ~str)]>, cb: proc(*c_void) -> T) -> T { } #[cfg(windows)] -fn with_envp(env: Option<~[(~str, ~str)]>, cb: |*mut c_void| -> T) -> T { +fn with_envp(env: Option<&[(CString, CString)]>, cb: |*mut c_void| -> T) -> T { // On win32 we pass an "environment block" which is not a char**, but // rather a concatenation of null-terminated k=v\0 sequences, with a final // \0 to terminate. @@ -690,9 +668,9 @@ fn with_envp(env: Option<~[(~str, ~str)]>, cb: |*mut c_void| -> T) -> T { let mut blk = Vec::new(); for pair in env.iter() { - let kv = format!("{}={}", *pair.ref0(), *pair.ref1()); - blk.push_all(kv.as_bytes()); - blk.push(0); + blk.push_all(pair.ref0().as_bytes_no_nul()); + blk.push('=' as u8); + blk.push_all(pair.ref1().as_bytes()); // includes terminal \0 } blk.push(0); @@ -704,9 +682,9 @@ fn with_envp(env: Option<~[(~str, ~str)]>, cb: |*mut c_void| -> T) -> T { } #[cfg(windows)] -fn with_dirp(d: Option<&Path>, cb: |*libc::c_char| -> T) -> T { +fn with_dirp(d: Option<&CString>, cb: |*libc::c_char| -> T) -> T { match d { - Some(dir) => dir.with_c_str(|buf| cb(buf)), + Some(dir) => dir.with_ref(|buf| cb(buf)), None => cb(ptr::null()) } } @@ -843,21 +821,34 @@ mod tests { #[test] #[cfg(windows)] fn test_make_command_line() { - use super::make_command_line; + use std::str; + use std::c_str::CString; + + fn test_wrapper(prog: &str, args: &[&str]) -> ~str { + str::from_utf8_lossy( + super::make_command_line(&prog.to_c_str(), + args.iter() + .map(|a| a.to_c_str()) + .collect::>() + .as_slice()) + .as_bytes_no_nul()).to_owned() + } + assert_eq!( - make_command_line("prog", ["aaa".to_owned(), "bbb".to_owned(), "ccc".to_owned()]), + test_wrapper("prog", ["aaa", "bbb", "ccc"]), "prog aaa bbb ccc".to_owned() ); + assert_eq!( - make_command_line("C:\\Program Files\\blah\\blah.exe", ["aaa".to_owned()]), + test_wrapper("C:\\Program Files\\blah\\blah.exe", ["aaa"]), "\"C:\\Program Files\\blah\\blah.exe\" aaa".to_owned() ); assert_eq!( - make_command_line("C:\\Program Files\\test", ["aa\"bb".to_owned()]), + test_wrapper("C:\\Program Files\\test", ["aa\"bb"]), "\"C:\\Program Files\\test\" aa\\\"bb".to_owned() ); assert_eq!( - make_command_line("echo", ["a b c".to_owned()]), + test_wrapper("echo", ["a b c"]), "echo \"a b c\"".to_owned() ); } diff --git a/src/librustc/back/archive.rs b/src/librustc/back/archive.rs index 67140ac1920e3..842f74cab38b3 100644 --- a/src/librustc/back/archive.rs +++ b/src/librustc/back/archive.rs @@ -16,7 +16,7 @@ use metadata::filesearch; use lib::llvm::{ArchiveRef, llvm}; use libc; -use std::io::process::{ProcessConfig, Process, ProcessOutput}; +use std::io::process::{Command, ProcessOutput}; use std::io::{fs, TempDir}; use std::io; use std::mem; @@ -39,26 +39,24 @@ pub struct ArchiveRO { fn run_ar(sess: &Session, args: &str, cwd: Option<&Path>, paths: &[&Path]) -> ProcessOutput { let ar = get_ar_prog(sess); + let mut cmd = Command::new(ar.as_slice()); + + cmd.arg(args).args(paths); + debug!("{}", cmd); - let mut args = vec!(args.to_owned()); - let paths = paths.iter().map(|p| p.as_str().unwrap().to_owned()); - args.extend(paths); - debug!("{} {}", ar, args.connect(" ")); match cwd { - Some(p) => { debug!("inside {}", p.display()); } + Some(p) => { + cmd.cwd(p); + debug!("inside {}", p.display()); + } None => {} } - match Process::configure(ProcessConfig { - program: ar.as_slice(), - args: args.as_slice(), - cwd: cwd.map(|a| &*a), - .. ProcessConfig::new() - }) { + + match cmd.spawn() { Ok(mut prog) => { let o = prog.wait_with_output(); if !o.status.success() { - sess.err(format!("{} {} failed with: {}", ar, args.connect(" "), - o.status)); + sess.err(format!("{} failed with: {}", cmd, o.status)); sess.note(format!("stdout ---\n{}", str::from_utf8(o.output.as_slice()).unwrap())); sess.note(format!("stderr ---\n{}", @@ -68,7 +66,7 @@ fn run_ar(sess: &Session, args: &str, cwd: Option<&Path>, o }, Err(e) => { - sess.err(format!("could not exec `{}`: {}", ar, e)); + sess.err(format!("could not exec `{}`: {}", ar.as_slice(), e)); sess.abort_if_errors(); fail!("rustc::back::archive::run_ar() should not reach this point"); } diff --git a/src/librustc/back/link.rs b/src/librustc/back/link.rs index 588d30ee4af01..0de5f6e675337 100644 --- a/src/librustc/back/link.rs +++ b/src/librustc/back/link.rs @@ -29,7 +29,7 @@ use util::sha2::{Digest, Sha256}; use std::c_str::{ToCStr, CString}; use std::char; -use std::io::{fs, TempDir, Process}; +use std::io::{fs, TempDir, Command}; use std::io; use std::ptr; use std::str; @@ -103,7 +103,7 @@ pub mod write { use syntax::abi; use std::c_str::ToCStr; - use std::io::Process; + use std::io::{Command}; use libc::{c_uint, c_int}; use std::str; @@ -348,22 +348,18 @@ pub mod write { } pub fn run_assembler(sess: &Session, outputs: &OutputFilenames) { - let cc = super::get_cc_prog(sess); - let assembly = outputs.temp_path(OutputTypeAssembly); - let object = outputs.path(OutputTypeObject); - - // FIXME (#9639): This needs to handle non-utf8 paths - let args = [ - "-c".to_owned(), - "-o".to_owned(), object.as_str().unwrap().to_owned(), - assembly.as_str().unwrap().to_owned()]; - - debug!("{} '{}'", cc, args.connect("' '")); - match Process::output(cc.as_slice(), args) { + let pname = super::get_cc_prog(sess); + let mut cmd = Command::new(pname.as_slice()); + + cmd.arg("-c").arg("-o").arg(outputs.path(OutputTypeObject)) + .arg(outputs.temp_path(OutputTypeAssembly)); + debug!("{}", &cmd); + + match cmd.output() { Ok(prog) => { if !prog.status.success() { - sess.err(format!("linking with `{}` failed: {}", cc, prog.status)); - sess.note(format!("{} arguments: '{}'", cc, args.connect("' '"))); + sess.err(format!("linking with `{}` failed: {}", pname, prog.status)); + sess.note(format!("{}", &cmd)); let mut note = prog.error.clone(); note.push_all(prog.output.as_slice()); sess.note(str::from_utf8(note.as_slice()).unwrap().to_owned()); @@ -371,7 +367,7 @@ pub mod write { } }, Err(e) => { - sess.err(format!("could not exec the linker `{}`: {}", cc, e)); + sess.err(format!("could not exec the linker `{}`: {}", pname, e)); sess.abort_if_errors(); } } @@ -527,6 +523,7 @@ pub mod write { * system linkers understand. */ +// FIXME (#9639): This needs to handle non-utf8 `out_filestem` values pub fn find_crate_id(attrs: &[ast::Attribute], out_filestem: &str) -> CrateId { match attr::find_crateid(attrs) { None => from_str(out_filestem).unwrap_or_else(|| { @@ -547,6 +544,7 @@ pub fn crate_id_hash(crate_id: &CrateId) -> StrBuf { truncated_hash_result(&mut s).as_slice().slice_to(8).to_strbuf() } +// FIXME (#9639): This needs to handle non-utf8 `out_filestem` values pub fn build_link_meta(krate: &ast::Crate, out_filestem: &str) -> LinkMeta { let r = LinkMeta { crateid: find_crate_id(krate.attrs.as_slice(), out_filestem), @@ -770,7 +768,7 @@ pub fn get_ar_prog(sess: &Session) -> StrBuf { None => {} } - get_system_tool(sess, "ar") + get_system_tool(sess, "ar").to_strbuf() } fn get_system_tool(sess: &Session, tool: &str) -> StrBuf { @@ -1051,31 +1049,30 @@ fn link_staticlib(sess: &Session, obj_filename: &Path, out_filename: &Path) { fn link_natively(sess: &Session, trans: &CrateTranslation, dylib: bool, obj_filename: &Path, out_filename: &Path) { let tmpdir = TempDir::new("rustc").expect("needs a temp dir"); + // The invocations of cc share some flags across platforms - let cc_prog = get_cc_prog(sess); - let mut cc_args = sess.targ_cfg.target_strs.cc_args.clone(); - cc_args.push_all_move(link_args(sess, dylib, tmpdir.path(), trans, - obj_filename, out_filename)); + let pname = get_cc_prog(sess); + let mut cmd = Command::new(pname.as_slice()); + + cmd.args(sess.targ_cfg.target_strs.cc_args.as_slice()); + link_args(&mut cmd, sess, dylib, tmpdir.path(), + trans, obj_filename, out_filename); + if (sess.opts.debugging_opts & config::PRINT_LINK_ARGS) != 0 { - println!("{} link args: '{}'", cc_prog, cc_args.connect("' '")); + println!("{}", &cmd); } // May have not found libraries in the right formats. sess.abort_if_errors(); // Invoke the system linker - debug!("{} {}", cc_prog, cc_args.connect(" ")); - let prog = time(sess.time_passes(), "running linker", (), |()| - Process::output(cc_prog.as_slice(), - cc_args.iter() - .map(|x| (*x).to_owned()) - .collect::>() - .as_slice())); + debug!("{}", &cmd); + let prog = time(sess.time_passes(), "running linker", (), |()| cmd.output()); match prog { Ok(prog) => { if !prog.status.success() { - sess.err(format!("linking with `{}` failed: {}", cc_prog, prog.status)); - sess.note(format!("{} arguments: '{}'", cc_prog, cc_args.connect("' '"))); + sess.err(format!("linking with `{}` failed: {}", pname, prog.status)); + sess.note(format!("{}", &cmd)); let mut output = prog.error.clone(); output.push_all(prog.output.as_slice()); sess.note(str::from_utf8(output.as_slice()).unwrap().to_owned()); @@ -1083,7 +1080,7 @@ fn link_natively(sess: &Session, trans: &CrateTranslation, dylib: bool, } }, Err(e) => { - sess.err(format!("could not exec the linker `{}`: {}", cc_prog, e)); + sess.err(format!("could not exec the linker `{}`: {}", pname, e)); sess.abort_if_errors(); } } @@ -1092,9 +1089,7 @@ fn link_natively(sess: &Session, trans: &CrateTranslation, dylib: bool, // On OSX, debuggers need this utility to get run to do some munging of // the symbols if sess.targ_cfg.os == abi::OsMacos && (sess.opts.debuginfo != NoDebugInfo) { - // FIXME (#9639): This needs to handle non-utf8 paths - match Process::status("dsymutil", - [out_filename.as_str().unwrap().to_owned()]) { + match Command::new("dsymutil").arg(out_filename).status() { Ok(..) => {} Err(e) => { sess.err(format!("failed to run dsymutil: {}", e)); @@ -1104,25 +1099,20 @@ fn link_natively(sess: &Session, trans: &CrateTranslation, dylib: bool, } } -fn link_args(sess: &Session, +fn link_args(cmd: &mut Command, + sess: &Session, dylib: bool, tmpdir: &Path, trans: &CrateTranslation, obj_filename: &Path, - out_filename: &Path) -> Vec { + out_filename: &Path) { // The default library location, we need this to find the runtime. // The location of crates will be determined as needed. - // FIXME (#9639): This needs to handle non-utf8 paths let lib_path = sess.target_filesearch().get_lib_path(); - let stage = ("-L".to_owned() + lib_path.as_str().unwrap()).to_strbuf(); + cmd.arg("-L").arg(lib_path); - let mut args = vec!(stage); - - // FIXME (#9639): This needs to handle non-utf8 paths - args.push_all([ - "-o".to_strbuf(), out_filename.as_str().unwrap().to_strbuf(), - obj_filename.as_str().unwrap().to_strbuf()]); + cmd.arg("-o").arg(out_filename).arg(obj_filename); // Stack growth requires statically linking a __morestack function. Note // that this is listed *before* all other libraries, even though it may be @@ -1139,14 +1129,13 @@ fn link_args(sess: &Session, // line, but inserting this farther to the left makes the // "rust_stack_exhausted" symbol an outstanding undefined symbol, which // flags libstd as a required library (or whatever provides the symbol). - args.push("-lmorestack".to_strbuf()); + cmd.arg("-lmorestack"); // When linking a dynamic library, we put the metadata into a section of the // executable. This metadata is in a separate object file from the main // object file, so we link that in here. if dylib { - let metadata = obj_filename.with_extension("metadata.o"); - args.push(metadata.as_str().unwrap().to_strbuf()); + cmd.arg(obj_filename.with_extension("metadata.o")); } // We want to prevent the compiler from accidentally leaking in any system @@ -1157,7 +1146,7 @@ fn link_args(sess: &Session, // // FIXME(#11937) we should invoke the system linker directly if sess.targ_cfg.os != abi::OsWin32 { - args.push("-nodefaultlibs".to_strbuf()); + cmd.arg("-nodefaultlibs"); } // If we're building a dylib, we don't use --gc-sections because LLVM has @@ -1165,20 +1154,20 @@ fn link_args(sess: &Session, // metadata. If we're building an executable, however, --gc-sections drops // the size of hello world from 1.8MB to 597K, a 67% reduction. if !dylib && sess.targ_cfg.os != abi::OsMacos { - args.push("-Wl,--gc-sections".to_strbuf()); + cmd.arg("-Wl,--gc-sections"); } if sess.targ_cfg.os == abi::OsLinux { // GNU-style linkers will use this to omit linking to libraries which // don't actually fulfill any relocations, but only for libraries which // follow this flag. Thus, use it before specifying libraries to link to. - args.push("-Wl,--as-needed".to_strbuf()); + cmd.arg("-Wl,--as-needed"); // GNU-style linkers support optimization with -O. GNU ld doesn't need a // numeric argument, but other linkers do. if sess.opts.optimize == config::Default || sess.opts.optimize == config::Aggressive { - args.push("-Wl,-O1".to_strbuf()); + cmd.arg("-Wl,-O1"); } } else if sess.targ_cfg.os == abi::OsMacos { // The dead_strip option to the linker specifies that functions and data @@ -1191,14 +1180,14 @@ fn link_args(sess: &Session, // won't get much benefit from dylibs because LLVM will have already // stripped away as much as it could. This has not been seen to impact // link times negatively. - args.push("-Wl,-dead_strip".to_strbuf()); + cmd.arg("-Wl,-dead_strip"); } if sess.targ_cfg.os == abi::OsWin32 { // Make sure that we link to the dynamic libgcc, otherwise cross-module // DWARF stack unwinding will not work. // This behavior may be overridden by --link-args "-static-libgcc" - args.push("-shared-libgcc".to_strbuf()); + cmd.arg("-shared-libgcc"); // And here, we see obscure linker flags #45. On windows, it has been // found to be necessary to have this flag to compile liblibc. @@ -1225,13 +1214,13 @@ fn link_args(sess: &Session, // // [1] - https://sourceware.org/bugzilla/show_bug.cgi?id=13130 // [2] - https://code.google.com/p/go/issues/detail?id=2139 - args.push("-Wl,--enable-long-section-names".to_strbuf()); + cmd.arg("-Wl,--enable-long-section-names"); } if sess.targ_cfg.os == abi::OsAndroid { // Many of the symbols defined in compiler-rt are also defined in libgcc. // Android linker doesn't like that by default. - args.push("-Wl,--allow-multiple-definition".to_strbuf()); + cmd.arg("-Wl,--allow-multiple-definition"); } // Take careful note of the ordering of the arguments we pass to the linker @@ -1267,39 +1256,38 @@ fn link_args(sess: &Session, // this kind of behavior is pretty platform specific and generally not // recommended anyway, so I don't think we're shooting ourself in the foot // much with that. - add_upstream_rust_crates(&mut args, sess, dylib, tmpdir, trans); - add_local_native_libraries(&mut args, sess); - add_upstream_native_libraries(&mut args, sess); + add_upstream_rust_crates(cmd, sess, dylib, tmpdir, trans); + add_local_native_libraries(cmd, sess); + add_upstream_native_libraries(cmd, sess); // # Telling the linker what we're doing if dylib { // On mac we need to tell the linker to let this library be rpathed if sess.targ_cfg.os == abi::OsMacos { - args.push("-dynamiclib".to_strbuf()); - args.push("-Wl,-dylib".to_strbuf()); - // FIXME (#9639): This needs to handle non-utf8 paths + cmd.args(["-dynamiclib", "-Wl,-dylib"]); + if !sess.opts.cg.no_rpath { - args.push(format_strbuf!("-Wl,-install_name,@rpath/{}", - out_filename.filename_str() - .unwrap())); + let mut v = Vec::from_slice("-Wl,-install_name,@rpath/".as_bytes()); + v.push_all(out_filename.filename().unwrap()); + cmd.arg(v.as_slice()); } } else { - args.push("-shared".to_strbuf()) + cmd.arg("-shared"); } } if sess.targ_cfg.os == abi::OsFreebsd { - args.push_all(["-L/usr/local/lib".to_strbuf(), - "-L/usr/local/lib/gcc46".to_strbuf(), - "-L/usr/local/lib/gcc44".to_strbuf()]); + cmd.args(["-L/usr/local/lib", + "-L/usr/local/lib/gcc46", + "-L/usr/local/lib/gcc44"]); } // FIXME (#2397): At some point we want to rpath our guesses as to // where extern libraries might live, based on the // addl_lib_search_paths if !sess.opts.cg.no_rpath { - args.push_all(rpath::get_rpath_flags(sess, out_filename).as_slice()); + cmd.args(rpath::get_rpath_flags(sess, out_filename).as_slice()); } // compiler-rt contains implementations of low-level LLVM helpers. This is @@ -1309,15 +1297,14 @@ fn link_args(sess: &Session, // // This is the end of the command line, so this library is used to resolve // *all* undefined symbols in all other libraries, and this is intentional. - args.push("-lcompiler-rt".to_strbuf()); + cmd.arg("-lcompiler-rt"); // Finally add all the linker arguments provided on the command line along // with any #[link_args] attributes found inside the crate - args.push_all(sess.opts.cg.link_args.as_slice()); + cmd.args(sess.opts.cg.link_args.as_slice()); for arg in sess.cstore.get_used_link_args().borrow().iter() { - args.push(arg.clone()); + cmd.arg(arg.as_slice()); } - return args; } // # Native library linking @@ -1331,16 +1318,14 @@ fn link_args(sess: &Session, // Also note that the native libraries linked here are only the ones located // in the current crate. Upstream crates with native library dependencies // may have their native library pulled in above. -fn add_local_native_libraries(args: &mut Vec, sess: &Session) { +fn add_local_native_libraries(cmd: &mut Command, sess: &Session) { for path in sess.opts.addl_lib_search_paths.borrow().iter() { - // FIXME (#9639): This needs to handle non-utf8 paths - args.push(("-L" + path.as_str().unwrap().to_owned()).to_strbuf()); + cmd.arg("-L").arg(path); } let rustpath = filesearch::rust_path(); for path in rustpath.iter() { - // FIXME (#9639): This needs to handle non-utf8 paths - args.push(("-L" + path.as_str().unwrap().to_owned()).to_strbuf()); + cmd.arg("-L").arg(path); } // Some platforms take hints about whether a library is static or dynamic. @@ -1354,21 +1339,21 @@ fn add_local_native_libraries(args: &mut Vec, sess: &Session) { cstore::NativeUnknown | cstore::NativeStatic => { if takes_hints { if kind == cstore::NativeStatic { - args.push("-Wl,-Bstatic".to_strbuf()); + cmd.arg("-Wl,-Bstatic"); } else { - args.push("-Wl,-Bdynamic".to_strbuf()); + cmd.arg("-Wl,-Bdynamic"); } } - args.push(format_strbuf!("-l{}", *l)); + cmd.arg(format_strbuf!("-l{}", *l)); } cstore::NativeFramework => { - args.push("-framework".to_strbuf()); - args.push(l.to_strbuf()); + cmd.arg("-framework"); + cmd.arg(l.as_slice()); } } } if takes_hints { - args.push("-Wl,-Bdynamic".to_strbuf()); + cmd.arg("-Wl,-Bdynamic"); } } @@ -1377,7 +1362,7 @@ fn add_local_native_libraries(args: &mut Vec, sess: &Session) { // Rust crates are not considered at all when creating an rlib output. All // dependencies will be linked when producing the final output (instead of // the intermediate rlib version) -fn add_upstream_rust_crates(args: &mut Vec, sess: &Session, +fn add_upstream_rust_crates(cmd: &mut Command, sess: &Session, dylib: bool, tmpdir: &Path, trans: &CrateTranslation) { // All of the heavy lifting has previously been accomplished by the @@ -1409,26 +1394,26 @@ fn add_upstream_rust_crates(args: &mut Vec, sess: &Session, let src = sess.cstore.get_used_crate_source(cnum).unwrap(); match kind { cstore::RequireDynamic => { - add_dynamic_crate(args, sess, src.dylib.unwrap()) + add_dynamic_crate(cmd, sess, src.dylib.unwrap()) } cstore::RequireStatic => { - add_static_crate(args, sess, tmpdir, cnum, src.rlib.unwrap()) + add_static_crate(cmd, sess, tmpdir, cnum, src.rlib.unwrap()) } } } // Converts a library file-stem into a cc -l argument - fn unlib(config: &config::Config, stem: &str) -> StrBuf { - if stem.starts_with("lib") && config.os != abi::OsWin32 { - stem.slice(3, stem.len()).to_strbuf() + fn unlib<'a>(config: &config::Config, stem: &'a [u8]) -> &'a [u8] { + if stem.starts_with("lib".as_bytes()) && config.os != abi::OsWin32 { + stem.tailn(3) } else { - stem.to_strbuf() + stem } } // Adds the static "rlib" versions of all crates to the command line. - fn add_static_crate(args: &mut Vec, sess: &Session, tmpdir: &Path, + fn add_static_crate(cmd: &mut Command, sess: &Session, tmpdir: &Path, cnum: ast::CrateNum, cratepath: Path) { // When performing LTO on an executable output, all of the // bytecode from the upstream libraries has already been @@ -1459,34 +1444,32 @@ fn add_upstream_rust_crates(args: &mut Vec, sess: &Session, sess.abort_if_errors(); } } - let dst_str = dst.as_str().unwrap().to_strbuf(); - let mut archive = Archive::open(sess, dst); + let mut archive = Archive::open(sess, dst.clone()); archive.remove_file(format!("{}.o", name)); let files = archive.files(); if files.iter().any(|s| s.as_slice().ends_with(".o")) { - args.push(dst_str); + cmd.arg(dst); } }); } else { - args.push(cratepath.as_str().unwrap().to_strbuf()); + cmd.arg(cratepath); } } // Same thing as above, but for dynamic crates instead of static crates. - fn add_dynamic_crate(args: &mut Vec, sess: &Session, - cratepath: Path) { + fn add_dynamic_crate(cmd: &mut Command, sess: &Session, cratepath: Path) { // If we're performing LTO, then it should have been previously required // that all upstream rust dependencies were available in an rlib format. assert!(!sess.lto()); // Just need to tell the linker about where the library lives and // what its name is - let dir = cratepath.dirname_str().unwrap(); - if !dir.is_empty() { - args.push(format_strbuf!("-L{}", dir)); - } - let libarg = unlib(&sess.targ_cfg, cratepath.filestem_str().unwrap()); - args.push(format_strbuf!("-l{}", libarg)); + let dir = cratepath.dirname(); + if !dir.is_empty() { cmd.arg("-L").arg(dir); } + + let mut v = Vec::from_slice("-l".as_bytes()); + v.push_all(unlib(&sess.targ_cfg, cratepath.filestem().unwrap())); + cmd.arg(v.as_slice()); } } @@ -1495,12 +1478,12 @@ fn add_upstream_rust_crates(args: &mut Vec, sess: &Session, // dependencies. We've got two cases then: // // 1. The upstream crate is an rlib. In this case we *must* link in the -// native dependency because the rlib is just an archive. +// native dependency because the rlib is just an archive. // // 2. The upstream crate is a dylib. In order to use the dylib, we have to -// have the dependency present on the system somewhere. Thus, we don't -// gain a whole lot from not linking in the dynamic dependency to this -// crate as well. +// have the dependency present on the system somewhere. Thus, we don't +// gain a whole lot from not linking in the dynamic dependency to this +// crate as well. // // The use case for this is a little subtle. In theory the native // dependencies of a crate are purely an implementation detail of the crate @@ -1508,7 +1491,7 @@ fn add_upstream_rust_crates(args: &mut Vec, sess: &Session, // generic function calls a native function, then the generic function must // be instantiated in the target crate, meaning that the native symbol must // also be resolved in the target crate. -fn add_upstream_native_libraries(args: &mut Vec, sess: &Session) { +fn add_upstream_native_libraries(cmd: &mut Command, sess: &Session) { // Be sure to use a topological sorting of crates because there may be // interdependencies between native libraries. When passing -nodefaultlibs, // for example, almost all native libraries depend on libc, so we have to @@ -1524,11 +1507,11 @@ fn add_upstream_native_libraries(args: &mut Vec, sess: &Session) { for &(kind, ref lib) in libs.iter() { match kind { cstore::NativeUnknown => { - args.push(format_strbuf!("-l{}", *lib)) + cmd.arg(format_strbuf!("-l{}", *lib)); } cstore::NativeFramework => { - args.push("-framework".to_strbuf()); - args.push(lib.to_strbuf()); + cmd.arg("-framework"); + cmd.arg(lib.as_slice()); } cstore::NativeStatic => { sess.bug("statics shouldn't be propagated"); diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs index c1da6396d0e29..aecdb4675782d 100644 --- a/src/librustdoc/test.rs +++ b/src/librustdoc/test.rs @@ -11,7 +11,7 @@ use std::cell::RefCell; use std::char; use std::io; -use std::io::{Process, TempDir}; +use std::io::{Command, TempDir}; use std::os; use std::str; use std::strbuf::StrBuf; @@ -158,9 +158,7 @@ fn runtest(test: &str, cratename: &str, libs: HashSet, should_fail: bool, if no_run { return } // Run the code! - let exe = outdir.path().join("rust_out"); - let out = Process::output(exe.as_str().unwrap(), []); - match out { + match Command::new(outdir.path().join("rust_out")).output() { Err(e) => fail!("couldn't run the test: {}{}", e, if e.kind == io::PermissionDenied { " - maybe your tempdir is mounted with noexec?" diff --git a/src/librustuv/process.rs b/src/librustuv/process.rs index d671e20868c5c..756327f4948e0 100644 --- a/src/librustuv/process.rs +++ b/src/librustuv/process.rs @@ -13,7 +13,8 @@ use libc; use std::io::IoError; use std::io::process; use std::ptr; -use std::rt::rtio::RtioProcess; +use std::c_str::CString; +use std::rt::rtio::{ProcessConfig, RtioProcess}; use std::rt::task::BlockedTask; use homing::{HomingIO, HomeHandle}; @@ -39,12 +40,10 @@ impl Process { /// /// Returns either the corresponding process object or an error which /// occurred. - pub fn spawn(io_loop: &mut UvIoFactory, config: process::ProcessConfig) - -> Result<(Box, Vec>), UvError> - { - let cwd = config.cwd.map(|s| s.to_c_str()); - let mut io = vec![config.stdin, config.stdout, config.stderr]; - for slot in config.extra_io.iter() { + pub fn spawn(io_loop: &mut UvIoFactory, cfg: ProcessConfig) + -> Result<(Box, Vec>), UvError> { + let mut io = vec![cfg.stdin, cfg.stdout, cfg.stderr]; + for slot in cfg.extra_io.iter() { io.push(*slot); } let mut stdio = Vec::::with_capacity(io.len()); @@ -58,16 +57,16 @@ impl Process { } } - let ret = with_argv(config.program, config.args, |argv| { - with_env(config.env, |envp| { + let ret = with_argv(cfg.program, cfg.args, |argv| { + with_env(cfg.env, |envp| { let mut flags = 0; - if config.uid.is_some() { + if cfg.uid.is_some() { flags |= uvll::PROCESS_SETUID; } - if config.gid.is_some() { + if cfg.gid.is_some() { flags |= uvll::PROCESS_SETGID; } - if config.detach { + if cfg.detach { flags |= uvll::PROCESS_DETACHED; } let options = uvll::uv_process_options_t { @@ -75,15 +74,15 @@ impl Process { file: unsafe { *argv }, args: argv, env: envp, - cwd: match cwd { - Some(ref cwd) => cwd.with_ref(|p| p), + cwd: match cfg.cwd { + Some(cwd) => cwd.with_ref(|p| p), None => ptr::null(), }, flags: flags as libc::c_uint, stdio_count: stdio.len() as libc::c_int, stdio: stdio.as_ptr(), - uid: config.uid.unwrap_or(0) as uvll::uv_uid_t, - gid: config.gid.unwrap_or(0) as uvll::uv_gid_t, + uid: cfg.uid.unwrap_or(0) as uvll::uv_uid_t, + gid: cfg.gid.unwrap_or(0) as uvll::uv_gid_t, }; let handle = UvHandle::alloc(None::, uvll::UV_PROCESS); @@ -162,42 +161,53 @@ unsafe fn set_stdio(dst: *uvll::uv_stdio_container_t, } } -/// Converts the program and arguments to the argv array expected by libuv -fn with_argv(prog: &str, args: &[~str], f: |**libc::c_char| -> T) -> T { - // First, allocation space to put all the C-strings (we need to have - // ownership of them somewhere - let mut c_strs = Vec::with_capacity(args.len() + 1); - c_strs.push(prog.to_c_str()); - for arg in args.iter() { - c_strs.push(arg.to_c_str()); - } +/// Converts the program and arguments to the argv array expected by libuv. +fn with_argv(prog: &CString, args: &[CString], cb: |**libc::c_char| -> T) -> T { + let mut ptrs: Vec<*libc::c_char> = Vec::with_capacity(args.len()+1); - // Next, create the char** array - let mut c_args = Vec::with_capacity(c_strs.len() + 1); - for s in c_strs.iter() { - c_args.push(s.with_ref(|p| p)); - } - c_args.push(ptr::null()); - f(c_args.as_ptr()) + // Convert the CStrings into an array of pointers. Note: the + // lifetime of the various CStrings involved is guaranteed to be + // larger than the lifetime of our invocation of cb, but this is + // technically unsafe as the callback could leak these pointers + // out of our scope. + ptrs.push(prog.with_ref(|buf| buf)); + ptrs.extend(args.iter().map(|tmp| tmp.with_ref(|buf| buf))); + + // Add a terminating null pointer (required by libc). + ptrs.push(ptr::null()); + + cb(ptrs.as_ptr()) } /// Converts the environment to the env array expected by libuv -fn with_env(env: Option<&[(~str, ~str)]>, f: |**libc::c_char| -> T) -> T { - let env = match env { - Some(s) => s, - None => { return f(ptr::null()); } - }; - // As with argv, create some temporary storage and then the actual array - let mut envp = Vec::with_capacity(env.len()); - for &(ref key, ref value) in env.iter() { - envp.push(format!("{}={}", *key, *value).to_c_str()); - } - let mut c_envp = Vec::with_capacity(envp.len() + 1); - for s in envp.iter() { - c_envp.push(s.with_ref(|p| p)); +fn with_env(env: Option<&[(CString, CString)]>, cb: |**libc::c_char| -> T) -> T { + // We can pass a char** for envp, which is a null-terminated array + // of "k=v\0" strings. Since we must create these strings locally, + // yet expose a raw pointer to them, we create a temporary vector + // to own the CStrings that outlives the call to cb. + match env { + Some(env) => { + let mut tmps = Vec::with_capacity(env.len()); + + for pair in env.iter() { + let mut kv = Vec::new(); + kv.push_all(pair.ref0().as_bytes_no_nul()); + kv.push('=' as u8); + kv.push_all(pair.ref1().as_bytes()); // includes terminal \0 + tmps.push(kv); + } + + // As with `with_argv`, this is unsafe, since cb could leak the pointers. + let mut ptrs: Vec<*libc::c_char> = + tmps.iter() + .map(|tmp| tmp.as_ptr() as *libc::c_char) + .collect(); + ptrs.push(ptr::null()); + + cb(ptrs.as_ptr()) + } + _ => cb(ptr::null()) } - c_envp.push(ptr::null()); - f(c_envp.as_ptr()) } impl HomingIO for Process { diff --git a/src/librustuv/uvio.rs b/src/librustuv/uvio.rs index c42b17cc3256e..1b8175448fc90 100644 --- a/src/librustuv/uvio.rs +++ b/src/librustuv/uvio.rs @@ -13,7 +13,6 @@ use std::c_str::CString; use std::io::IoError; use std::io::net::ip::SocketAddr; -use std::io::process::ProcessConfig; use std::io::signal::Signum; use std::io::{FileMode, FileAccess, Open, Append, Truncate, Read, Write, ReadWrite, FileStat}; @@ -25,7 +24,7 @@ use libc::{O_CREAT, O_APPEND, O_TRUNC, O_RDWR, O_RDONLY, O_WRONLY, S_IRUSR, use libc; use std::path::Path; use std::rt::rtio; -use std::rt::rtio::{IoFactory, EventLoop}; +use std::rt::rtio::{ProcessConfig, IoFactory, EventLoop}; use ai = std::io::net::addrinfo; #[cfg(test)] use std::unstable::run_in_bare_thread; @@ -270,12 +269,12 @@ impl IoFactory for UvIoFactory { r.map_err(uv_error_to_io_error) } - fn spawn(&mut self, config: ProcessConfig) + fn spawn(&mut self, cfg: ProcessConfig) -> Result<(Box, Vec>>), IoError> { - match Process::spawn(self, config) { + match Process::spawn(self, cfg) { Ok((p, io)) => { Ok((p as Box, io.move_iter().map(|i| i.map(|p| { diff --git a/src/libstd/io/mod.rs b/src/libstd/io/mod.rs index 0f8e37b4ee011..44d3c4817a632 100644 --- a/src/libstd/io/mod.rs +++ b/src/libstd/io/mod.rs @@ -246,7 +246,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, ProcessConfig}; +pub use self::process::{Process, Command}; pub use self::tempfile::TempDir; pub use self::mem::{MemReader, BufReader, MemWriter, BufWriter}; diff --git a/src/libstd/io/process.rs b/src/libstd/io/process.rs index 529fd25dc50e9..84fb27ccfaf5b 100644 --- a/src/libstd/io/process.rs +++ b/src/libstd/io/process.rs @@ -12,13 +12,15 @@ use prelude::*; +use std::str; use fmt; use io::IoResult; use io; use libc; use mem; use owned::Box; -use rt::rtio::{RtioProcess, IoFactory, LocalIo}; +use rt::rtio::{RtioProcess, ProcessConfig, IoFactory, LocalIo}; +use c_str::CString; /// Signal a process to exit, without forcibly killing it. Corresponds to /// SIGTERM on unix platforms. @@ -35,16 +37,16 @@ use rt::rtio::{RtioProcess, IoFactory, LocalIo}; /// 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. +/// This structure is used to represent and manage child processes. A child +/// process is created via the `Command` struct, which configures the spawning +/// process and can itself be constructed using a builder-style interface. /// /// # Example /// /// ```should_fail -/// use std::io::Process; +/// use std::io::Command; /// -/// let mut child = match Process::new("/bin/cat", ["file.txt".to_owned()]) { +/// let mut child = match Command::new("/bin/cat").arg("file.txt").spawn() { /// Ok(child) => child, /// Err(e) => fail!("failed to execute child: {}", e), /// }; @@ -72,71 +74,244 @@ pub struct Process { pub extra_io: Vec>, } -/// 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: +/// The `Command` type acts as a process builder, providing fine-grained control +/// over how a new process should be spawned. A default configuration can be +/// generated using `Command::new(program)`, where `program` gives a path to the +/// program to be executed. Additional builder methods allow the configuration +/// to be changed (for example, by adding arguments) prior to spawning: /// /// ``` -/// use std::io::ProcessConfig; +/// use std::io::Command; /// -/// let config = ProcessConfig { -/// program: "/bin/sh", -/// args: &["-c".to_owned(), "true".to_owned()], -/// .. ProcessConfig::new() +/// let mut process = match Command::new("sh").arg("-c").arg("echo hello").spawn() { +/// Ok(p) => p, +/// Err(e) => fail!("failed to execute process: {}", e), /// }; +/// +/// let output = process.stdout.get_mut_ref().read_to_end(); /// ``` -pub struct ProcessConfig<'a> { - /// Path to the program to run - pub program: &'a str, +pub struct Command { + // The internal data for the builder. Documented by the builder + // methods below, and serialized into rt::rtio::ProcessConfig. + program: CString, + args: Vec, + env: Option>, + cwd: Option, + stdin: StdioContainer, + stdout: StdioContainer, + stderr: StdioContainer, + extra_io: Vec, + uid: Option, + gid: Option, + detach: bool, +} + +// FIXME (#12938): Until DST lands, we cannot decompose &str into & and str, so +// we cannot usefully take ToCStr arguments by reference (without forcing an +// additional & around &str). So we are instead temporarily adding an instance +// for &Path, so that we can take ToCStr as owned. When DST lands, the &Path +// instance should be removed, and arguments bound by ToCStr should be passed by +// reference. (Here: {new, arg, args, env}.) + +impl Command { + /// Constructs a new `Command` for launching the program at + /// path `program`, with the following default configuration: + /// + /// * No arguments to the program + /// * Inherit the current process's environment + /// * Inherit the current process's working directory + /// * A readable pipe for stdin (file descriptor 0) + /// * A writeable pipe for stdour and stderr (file descriptors 1 and 2) + /// + /// Builder methods are provided to change these defaults and + /// otherwise configure the process. + pub fn new(program: T) -> Command { + Command { + program: program.to_c_str(), + args: Vec::new(), + env: None, + cwd: None, + stdin: CreatePipe(true, false), + stdout: CreatePipe(false, true), + stderr: CreatePipe(false, true), + extra_io: Vec::new(), + uid: None, + gid: None, + detach: false, + } + } - /// Arguments to pass to the program (doesn't include the program itself) - pub args: &'a [~str], + /// Add an argument to pass to the program. + pub fn arg<'a, T:ToCStr>(&'a mut self, arg: T) -> &'a mut Command { + self.args.push(arg.to_c_str()); + self + } - /// Optional environment to specify for the program. If this is None, then - /// it will inherit the current process's environment. - pub env: Option<&'a [(~str, ~str)]>, + /// Add multiple arguments to pass to the program. + pub fn args<'a, T:ToCStr>(&'a mut self, args: &[T]) -> &'a mut Command { + self.args.extend(args.iter().map(|arg| arg.to_c_str()));; + self + } + + /// Sets the environment for the child process (rather than inheriting it + /// from the current process). + + // FIXME (#13851): We should change this interface to allow clients to (1) + // build up the env vector incrementally and (2) allow both inheriting the + // current process's environment AND overriding/adding additional + // environment variables. The underlying syscalls assume that the + // environment has no duplicate names, so we really want to use a hashtable + // to compute the environment to pass down to the syscall; resolving issue + // #13851 will make it possible to use the standard hashtable. + pub fn env<'a, T:ToCStr>(&'a mut self, env: &[(T,T)]) -> &'a mut Command { + self.env = Some(env.iter().map(|&(ref name, ref val)| { + (name.to_c_str(), val.to_c_str()) + }).collect()); + self + } - /// Optional working directory for the new process. If this is None, then - /// the current directory of the running process is inherited. - pub cwd: Option<&'a Path>, + /// Set the working directory for the child process. + pub fn cwd<'a>(&'a mut self, dir: &Path) -> &'a mut Command { + self.cwd = Some(dir.to_c_str()); + self + } /// 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. - pub stdin: StdioContainer, + /// Defaults to `CreatePipe(true, false)` so the input can be written to. + pub fn stdin<'a>(&'a mut self, cfg: StdioContainer) -> &'a mut Command { + self.stdin = cfg; + self + } /// Configuration for the child process's stdout handle (file descriptor 1). - /// This field defaults to `CreatePipe(false, true)` so the output can be - /// collected. - pub 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. - pub 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 - /// 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. - pub extra_io: &'a [StdioContainer], + /// Defaults to `CreatePipe(false, true)` so the output can be collected. + pub fn stdout<'a>(&'a mut self, cfg: StdioContainer) -> &'a mut Command { + self.stdout = cfg; + self + } + + /// Configuration for the child process's stderr handle (file descriptor 2). + /// Defaults to `CreatePipe(false, true)` so the output can be collected. + pub fn stderr<'a>(&'a mut self, cfg: StdioContainer) -> &'a mut Command { + self.stderr = cfg; + self + } + /// Attaches a stream/file descriptor/pipe to the child process. Inherited + /// file descriptors are numbered consecutively, starting at 3; the first + /// three file descriptors (stdin/stdout/stderr) are configured with the + /// `stdin`, `stdout`, and `stderr` methods. + pub fn extra_io<'a>(&'a mut self, cfg: StdioContainer) -> &'a mut Command { + self.extra_io.push(cfg); + self + } /// Sets the child process's user id. This translates to a `setuid` call in /// the child process. Setting this value on windows will cause the spawn to /// fail. Failure in the `setuid` call on unix will also cause the spawn to /// fail. - pub uid: Option, + pub fn uid<'a>(&'a mut self, id: uint) -> &'a mut Command { + self.uid = Some(id); + self + } /// Similar to `uid`, but sets the group id of the child process. This has /// the same semantics as the `uid` field. - pub gid: Option, + pub fn gid<'a>(&'a mut self, id: uint) -> &'a mut Command { + self.gid = Some(id); + self + } - /// If true, the child process is spawned in a detached state. On unix, this + /// Sets the child process to be spawned in a detached state. On unix, this /// means that the child is the leader of a new process group. - pub detach: bool, + pub fn detached<'a>(&'a mut self) -> &'a mut Command { + self.detach = true; + self + } + + /// Executes the command as a child process, which is returned. + pub fn spawn(&self) -> IoResult { + LocalIo::maybe_raise(|io| { + let cfg = ProcessConfig { + program: &self.program, + args: self.args.as_slice(), + env: self.env.as_ref().map(|env| env.as_slice()), + cwd: self.cwd.as_ref(), + stdin: self.stdin, + stdout: self.stdout, + stderr: self.stderr, + extra_io: self.extra_io.as_slice(), + uid: self.uid, + gid: self.gid, + detach: self.detach, + }; + io.spawn(cfg).map(|(p, io)| { + let mut io = io.move_iter().map(|p| { + p.map(|p| io::PipeStream::new(p)) + }); + Process { + handle: p, + stdin: io.next().unwrap(), + stdout: io.next().unwrap(), + stderr: io.next().unwrap(), + extra_io: io.collect(), + } + }) + }) + } + + /// Executes the command as a child process, waiting for it to finish and + /// collecting all of its output. + /// + /// # Example + /// + /// ``` + /// use std::io::Command; + /// use std::str; + /// + /// let output = match Command::new("cat").arg("foot.txt").output() { + /// Ok(output) => output, + /// Err(e) => fail!("failed to execute process: {}", e), + /// }; + /// + /// println!("status: {}", output.status); + /// println!("stdout: {}", str::from_utf8_lossy(output.output.as_slice())); + /// println!("stderr: {}", str::from_utf8_lossy(output.error.as_slice())); + /// ``` + pub fn output(&self) -> IoResult { + self.spawn().map(|mut p| p.wait_with_output()) + } + + /// Executes a command as a child process, waiting for it to finish and + /// collecting its exit status. + /// + /// # Example + /// + /// ``` + /// use std::io::Command; + /// + /// let status = match Command::new("ls").status() { + /// Ok(status) => status, + /// Err(e) => fail!("failed to execute process: {}", e), + /// }; + /// + /// println!("process exited with: {}", status); + /// ``` + pub fn status(&self) -> IoResult { + self.spawn().map(|mut p| p.wait()) + } +} + +impl fmt::Show for Command { + /// Format the program and arguments of a Command for display. Any + /// non-utf8 data is lossily converted using the utf8 replacement + /// character. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(write!(f.buf, "{}", str::from_utf8_lossy(self.program.as_bytes_no_nul()))); + for arg in self.args.iter() { + try!(write!(f.buf, " '{}'", str::from_utf8_lossy(arg.as_bytes_no_nul()))); + } + Ok(()) + } } /// The output of a finished process. @@ -204,127 +379,7 @@ impl ProcessExit { } } -impl<'a> ProcessConfig<'a> { - /// Creates a new configuration with blanks as all of the defaults. This is - /// useful when using functional struct updates: - /// - /// ```rust - /// use std::io::process::{ProcessConfig, Process}; - /// - /// let config = ProcessConfig { - /// program: "/bin/sh", - /// args: &["-c".to_owned(), "echo hello".to_owned()], - /// .. ProcessConfig::new() - /// }; - /// - /// let p = Process::configure(config); - /// ``` - /// - pub fn new<'a>() -> ProcessConfig<'a> { - ProcessConfig { - program: "", - args: &[], - env: None, - cwd: None, - stdin: CreatePipe(true, false), - stdout: CreatePipe(false, true), - stderr: CreatePipe(false, true), - extra_io: &[], - uid: None, - gid: None, - detach: false, - } - } -} - impl 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".to_owned(), "echo hello".to_owned()]) { - /// 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".to_owned()]) { - /// Ok(output) => output, - /// Err(e) => fail!("failed to execute process: {}", e), - /// }; - /// - /// println!("status: {}", output.status); - /// println!("stdout: {}", str::from_utf8_lossy(output.output.as_slice())); - /// println!("stderr: {}", str::from_utf8_lossy(output.error.as_slice())); - /// ``` - 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, - 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 @@ -427,18 +482,14 @@ impl Drop for Process { #[cfg(test)] mod tests { - use io::process::{ProcessConfig, Process}; + use io::process::{Command, Process}; use prelude::*; // FIXME(#10380) these tests should not all be ignored on android. #[cfg(not(target_os="android"))] iotest!(fn smoke() { - let args = ProcessConfig { - program: "true", - .. ProcessConfig::new() - }; - let p = Process::configure(args); + let p = Command::new("true").spawn(); assert!(p.is_ok()); let mut p = p.unwrap(); assert!(p.wait().success()); @@ -446,11 +497,7 @@ mod tests { #[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::configure(args) { + match Command::new("if-this-is-a-binary-then-the-world-has-ended").spawn() { Ok(..) => fail!(), Err(..) => {} } @@ -458,11 +505,7 @@ mod tests { #[cfg(not(target_os="android"))] iotest!(fn exit_reported_right() { - let args = ProcessConfig { - program: "false", - .. ProcessConfig::new() - }; - let p = Process::configure(args); + let p = Command::new("false").spawn(); assert!(p.is_ok()); let mut p = p.unwrap(); assert!(p.wait().matches_exit_status(1)); @@ -471,12 +514,7 @@ mod tests { #[cfg(unix, not(target_os="android"))] iotest!(fn signal_reported_right() { - let args = ProcessConfig { - program: "/bin/sh", - args: &["-c".to_owned(), "kill -1 $$".to_owned()], - .. ProcessConfig::new() - }; - let p = Process::configure(args); + let p = Command::new("/bin/sh").arg("-c").arg("kill -1 $$").spawn(); assert!(p.is_ok()); let mut p = p.unwrap(); match p.wait() { @@ -489,8 +527,8 @@ mod tests { input.read_to_str().unwrap() } - pub fn run_output(args: ProcessConfig) -> ~str { - let p = Process::configure(args); + pub fn run_output(cmd: Command) -> ~str { + let p = cmd.spawn(); assert!(p.is_ok()); let mut p = p.unwrap(); assert!(p.stdout.is_some()); @@ -501,38 +539,27 @@ mod tests { #[cfg(not(target_os="android"))] iotest!(fn stdout_works() { - let args = ProcessConfig { - program: "echo", - args: &["foobar".to_owned()], - stdout: CreatePipe(false, true), - .. ProcessConfig::new() - }; - assert_eq!(run_output(args), "foobar\n".to_owned()); + let mut cmd = Command::new("echo"); + cmd.arg("foobar").stdout(CreatePipe(false, true)); + assert_eq!(run_output(cmd), "foobar\n".to_owned()); }) #[cfg(unix, not(target_os="android"))] iotest!(fn set_cwd_works() { - let cwd = Path::new("/"); - let args = ProcessConfig { - program: "/bin/sh", - args: &["-c".to_owned(), "pwd".to_owned()], - cwd: Some(&cwd), - stdout: CreatePipe(false, true), - .. ProcessConfig::new() - }; - assert_eq!(run_output(args), "/\n".to_owned()); + let mut cmd = Command::new("/bin/sh"); + cmd.arg("-c").arg("pwd") + .cwd(&Path::new("/")) + .stdout(CreatePipe(false, true)); + assert_eq!(run_output(cmd), "/\n".to_owned()); }) #[cfg(unix, not(target_os="android"))] iotest!(fn stdin_works() { - let args = ProcessConfig { - program: "/bin/sh", - args: &["-c".to_owned(), "read line; echo $line".to_owned()], - stdin: CreatePipe(true, false), - stdout: CreatePipe(false, true), - .. ProcessConfig::new() - }; - let mut p = Process::configure(args).unwrap(); + let mut p = Command::new("/bin/sh") + .arg("-c").arg("read line; echo $line") + .stdin(CreatePipe(true, false)) + .stdout(CreatePipe(false, true)) + .spawn().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); @@ -542,36 +569,23 @@ mod tests { #[cfg(not(target_os="android"))] iotest!(fn detach_works() { - let args = ProcessConfig { - program: "true", - detach: true, - .. ProcessConfig::new() - }; - let mut p = Process::configure(args).unwrap(); + let mut p = Command::new("true").detached().spawn().unwrap(); assert!(p.wait().success()); }) #[cfg(windows)] iotest!(fn uid_fails_on_windows() { - let args = ProcessConfig { - program: "test", - uid: Some(10), - .. ProcessConfig::new() - }; - assert!(Process::configure(args).is_err()); + assert!(Command::new("test").uid(10).spawn().is_err()); }) #[cfg(unix, not(target_os="android"))] iotest!(fn uid_works() { use libc; - let args = ProcessConfig { - program: "/bin/sh", - args: &["-c".to_owned(), "true".to_owned()], - uid: Some(unsafe { libc::getuid() as uint }), - gid: Some(unsafe { libc::getgid() as uint }), - .. ProcessConfig::new() - }; - let mut p = Process::configure(args).unwrap(); + let mut p = Command::new("/bin/sh") + .arg("-c").arg("true") + .uid(unsafe { libc::getuid() as uint }) + .gid(unsafe { libc::getgid() as uint }) + .spawn().unwrap(); assert!(p.wait().success()); }) @@ -582,26 +596,20 @@ mod tests { // if we're already root, this isn't a valid test. Most of the bots run // as non-root though (android is an exception). if unsafe { libc::getuid() == 0 } { return } - let args = ProcessConfig { - program: "/bin/ls", - uid: Some(0), - gid: Some(0), - .. ProcessConfig::new() - }; - assert!(Process::configure(args).is_err()); + assert!(Command::new("/bin/ls").uid(0).gid(0).spawn().is_err()); }) #[cfg(not(target_os="android"))] iotest!(fn test_process_status() { - let mut status = Process::status("false", []).unwrap(); + let mut status = Command::new("false").status().unwrap(); assert!(status.matches_exit_status(1)); - status = Process::status("true", []).unwrap(); + status = Command::new("true").status().unwrap(); assert!(status.success()); }) iotest!(fn test_process_output_fail_to_start() { - match Process::output("/no-binary-by-this-name-should-exist", []) { + match Command::new("/no-binary-by-this-name-should-exist").output() { Err(e) => assert_eq!(e.kind, FileNotFound), Ok(..) => fail!() } @@ -609,9 +617,8 @@ mod tests { #[cfg(not(target_os="android"))] iotest!(fn test_process_output_output() { - let ProcessOutput {status, output, error} - = Process::output("echo", ["hello".to_owned()]).unwrap(); + = Command::new("echo").arg("hello").output().unwrap(); let output_str = str::from_utf8(output.as_slice()).unwrap(); assert!(status.success()); @@ -625,7 +632,7 @@ mod tests { #[cfg(not(target_os="android"))] iotest!(fn test_process_output_error() { let ProcessOutput {status, output, error} - = Process::output("mkdir", [".".to_owned()]).unwrap(); + = Command::new("mkdir").arg(".").output().unwrap(); assert!(status.matches_exit_status(1)); assert_eq!(output, Vec::new()); @@ -634,21 +641,20 @@ mod tests { #[cfg(not(target_os="android"))] iotest!(fn test_finish_once() { - let mut prog = Process::new("false", []).unwrap(); + let mut prog = Command::new("false").spawn().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(); + let mut prog = Command::new("false").spawn().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".to_owned()]).unwrap(); + let mut prog = Command::new("echo").arg("hello").spawn().unwrap(); let ProcessOutput {status, output, error} = prog.wait_with_output(); let output_str = str::from_utf8(output.as_slice()).unwrap(); @@ -662,7 +668,7 @@ mod tests { #[cfg(not(target_os="android"))] iotest!(fn test_wait_with_output_twice() { - let mut prog = Process::new("echo", ["hello".to_owned()]).unwrap(); + let mut prog = Command::new("echo").arg("hello").spawn().unwrap(); let ProcessOutput {status, output, error} = prog.wait_with_output(); let output_str = str::from_utf8(output.as_slice()).unwrap(); @@ -685,36 +691,26 @@ mod tests { }) #[cfg(unix,not(target_os="android"))] - pub fn run_pwd(dir: Option<&Path>) -> Process { - Process::configure(ProcessConfig { - program: "pwd", - cwd: dir, - .. ProcessConfig::new() - }).unwrap() + pub fn pwd_cmd() -> Command { + Command::new("pwd") } #[cfg(target_os="android")] - pub fn run_pwd(dir: Option<&Path>) -> Process { - Process::configure(ProcessConfig { - program: "/system/bin/sh", - args: &["-c".to_owned(),"pwd".to_owned()], - cwd: dir.map(|a| &*a), - .. ProcessConfig::new() - }).unwrap() + pub fn pwd_cmd() -> Command { + let mut cmd = Command::new("/system/bin/sh"); + cmd.arg("-c").arg("pwd"); + cmd } #[cfg(windows)] - pub fn run_pwd(dir: Option<&Path>) -> Process { - Process::configure(ProcessConfig { - program: "cmd", - args: &["/c".to_owned(), "cd".to_owned()], - cwd: dir.map(|a| &*a), - .. ProcessConfig::new() - }).unwrap() + pub fn pwd_cmd() -> Command { + let mut cmd = Command::new("cmd"); + cmd.arg("/c").arg("cd"); + cmd } iotest!(fn test_keep_current_working_dir() { use os; - let mut prog = run_pwd(None); + let mut prog = pwd_cmd().spawn().unwrap(); let output = str::from_utf8(prog.wait_with_output().output.as_slice()).unwrap().to_owned(); let parent_dir = os::getcwd(); @@ -732,7 +728,7 @@ mod tests { // 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 mut prog = pwd_cmd().cwd(&parent_dir).spawn().unwrap(); let output = str::from_utf8(prog.wait_with_output().output.as_slice()).unwrap().to_owned(); let child_dir = Path::new(output.trim()); @@ -745,31 +741,21 @@ mod tests { }) #[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() + pub fn env_cmd() -> Command { + Command::new("env") } #[cfg(target_os="android")] - pub fn run_env(env: Option<~[(~str, ~str)]>) -> Process { - Process::configure(ProcessConfig { - program: "/system/bin/sh", - args: &["-c".to_owned(),"set".to_owned()], - env: env.as_ref().map(|e| e.as_slice()), - .. ProcessConfig::new() - }).unwrap() + pub fn env_cmd() -> Command { + let mut cmd = Command::new("/system/bin/sh"); + cmd.arg("-c").arg("set"); + cmd } #[cfg(windows)] - pub fn run_env(env: Option<~[(~str, ~str)]>) -> Process { - Process::configure(ProcessConfig { - program: "cmd", - args: &["/c".to_owned(), "set".to_owned()], - env: env.as_ref().map(|e| e.as_slice()), - .. ProcessConfig::new() - }).unwrap() + pub fn env_cmd() -> Command { + let mut cmd = Command::new("cmd"); + cmd.arg("/c").arg("set"); + cmd } #[cfg(not(target_os="android"))] @@ -777,7 +763,7 @@ mod tests { use os; if running_on_valgrind() { return; } - let mut prog = run_env(None); + let mut prog = env_cmd().spawn().unwrap(); let output = str::from_utf8(prog.wait_with_output().output.as_slice()).unwrap().to_owned(); let r = os::env(); @@ -791,7 +777,7 @@ mod tests { use os; if running_on_valgrind() { return; } - let mut prog = run_env(None); + let mut prog = env_cmd().spawn().unwrap(); let output = str::from_utf8(prog.wait_with_output().output.as_slice()).unwrap().to_owned(); let r = os::env(); @@ -805,9 +791,9 @@ mod tests { }) iotest!(fn test_add_to_env() { - let new_env = box [("RUN_TEST_NEW_ENV".to_owned(), "123".to_owned())]; + let new_env = box [("RUN_TEST_NEW_ENV", "123")]; - let mut prog = run_env(Some(new_env)); + let mut prog = env_cmd().env(new_env).spawn().unwrap(); let result = prog.wait_with_output(); let output = str::from_utf8_lossy(result.output.as_slice()).into_owned(); @@ -817,14 +803,14 @@ mod tests { #[cfg(unix)] pub fn sleeper() -> Process { - Process::new("sleep", ["1000".to_owned()]).unwrap() + Command::new("sleep").arg("1000").spawn().unwrap() } #[cfg(windows)] pub fn sleeper() -> Process { // There's a `timeout` command on windows, but it doesn't like having // its output piped, so instead just ping ourselves a few times with // gaps inbetweeen so we're sure this process is alive for awhile - Process::new("ping", ["127.0.0.1".to_owned(), "-n".to_owned(), "1000".to_owned()]).unwrap() + Command::new("ping").arg("127.0.0.1").arg("-n").arg("1000").spawn().unwrap() } iotest!(fn test_kill() { diff --git a/src/libstd/rt/rtio.rs b/src/libstd/rt/rtio.rs index d23d327d55881..f65bd69ea8049 100644 --- a/src/libstd/rt/rtio.rs +++ b/src/libstd/rt/rtio.rs @@ -29,7 +29,7 @@ use ai = io::net::addrinfo; use io; use io::IoResult; use io::net::ip::{IpAddr, SocketAddr}; -use io::process::{ProcessConfig, ProcessExit}; +use io::process::{StdioContainer, ProcessExit}; use io::signal::Signum; use io::{FileMode, FileAccess, FileStat, FilePermission}; use io::{SeekStyle}; @@ -87,6 +87,61 @@ pub enum CloseBehavior { CloseAsynchronously, } +/// Data needed to spawn a process. Serializes the `std::io::process::Command` +/// builder. +pub struct ProcessConfig<'a> { + /// Path to the program to run. + pub program: &'a CString, + + /// Arguments to pass to the program (doesn't include the program itself). + pub args: &'a [CString], + + /// Optional environment to specify for the program. If this is None, then + /// it will inherit the current process's environment. + pub env: Option<&'a [(CString, CString)]>, + + /// Optional working directory for the new process. If this is None, then + /// the current directory of the running process is inherited. + pub cwd: Option<&'a CString>, + + /// 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. + pub 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. + pub 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. + pub 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 + /// 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. + pub 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 + /// fail. Failure in the `setuid` call on unix will also cause the spawn to + /// fail. + pub uid: Option, + + /// Similar to `uid`, but sets the group id of the child process. This has + /// the same semantics as the `uid` field. + pub gid: Option, + + /// If true, the child process is spawned in a detached state. On unix, this + /// means that the child is the leader of a new process group. + pub detach: bool, +} + pub struct LocalIo<'a> { factory: &'a mut IoFactory, } @@ -189,7 +244,7 @@ pub trait IoFactory { // misc fn timer_init(&mut self) -> IoResult>; - fn spawn(&mut self, config: ProcessConfig) + fn spawn(&mut self, cfg: ProcessConfig) -> IoResult<(Box, Vec>>)>; fn kill(&mut self, pid: libc::pid_t, signal: int) -> IoResult<()>; diff --git a/src/libstd/unstable/dynamic_lib.rs b/src/libstd/unstable/dynamic_lib.rs index 87d531cc627e0..1e88bffd00ea9 100644 --- a/src/libstd/unstable/dynamic_lib.rs +++ b/src/libstd/unstable/dynamic_lib.rs @@ -221,8 +221,6 @@ pub mod dl { pub mod dl { use libc; use os; - use path::GenericPath; - use path; use ptr; use result::{Ok, Err, Result}; diff --git a/src/libworkcache/lib.rs b/src/libworkcache/lib.rs index 1dd87f3954e4f..cdb58d6d41810 100644 --- a/src/libworkcache/lib.rs +++ b/src/libworkcache/lib.rs @@ -479,7 +479,7 @@ impl<'a, T:Send + #[cfg(not(target_os="android"))] // FIXME(#10455) fn test() { use std::os; - use std::io::{fs, Process}; + use std::io::{fs, Command}; use std::str::from_utf8; // Create a path to a new file 'filename' in the directory in which @@ -513,10 +513,7 @@ fn test() { prep.exec(proc(_exe) { let out = make_path("foo.o".to_owned()); let compiler = if cfg!(windows) {"gcc"} else {"cc"}; - // FIXME (#9639): This needs to handle non-utf8 paths - Process::status(compiler, [pth.as_str().unwrap().to_owned(), - "-o".to_owned(), - out.as_str().unwrap().to_owned()]).unwrap(); + Command::new(compiler).arg(pth).arg("-o").arg(out.clone()).status().unwrap(); let _proof_of_concept = subcx.prep("subfn"); // Could run sub-rules inside here. diff --git a/src/test/auxiliary/linkage-visibility.rs b/src/test/auxiliary/linkage-visibility.rs index ab3539ebf6f54..4ae0b6f14f59d 100644 --- a/src/test/auxiliary/linkage-visibility.rs +++ b/src/test/auxiliary/linkage-visibility.rs @@ -27,7 +27,8 @@ fn bar() { } fn baz() { } pub fn test() { - let lib = DynamicLibrary::open(None).unwrap(); + let none: Option = None; // appease the typechecker + let lib = DynamicLibrary::open(none).unwrap(); unsafe { assert!(lib.symbol::("foo").is_ok()); assert!(lib.symbol::("baz").is_err()); diff --git a/src/test/run-make/unicode-input/multiple_files.rs b/src/test/run-make/unicode-input/multiple_files.rs index a08d6bb0bf87f..219eb1a3ebd45 100644 --- a/src/test/run-make/unicode-input/multiple_files.rs +++ b/src/test/run-make/unicode-input/multiple_files.rs @@ -12,7 +12,7 @@ extern crate rand; use rand::{task_rng, Rng}; use std::{char, os, str}; -use std::io::{File, Process}; +use std::io::{File, Command}; // creates unicode_input_multiple_files_{main,chars}.rs, where the // former imports the latter. `_chars` just contains an indentifier @@ -40,7 +40,6 @@ fn main() { let tmpdir = Path::new(args.get(2).as_slice()); let main_file = tmpdir.join("unicode_input_multiple_files_main.rs"); - let main_file_str = main_file.as_str().unwrap(); { let _ = File::create(&main_file).unwrap() .write_str("mod unicode_input_multiple_files_chars;"); @@ -57,7 +56,9 @@ fn main() { // rustc is passed to us with --out-dir and -L etc., so we // can't exec it directly - let result = Process::output("sh", ["-c".to_owned(), rustc + " " + main_file_str]).unwrap(); + let result = Command::new("sh") + .arg("-c").arg(rustc + " " + main_file.as_str().unwrap()) + .output().unwrap(); let err = str::from_utf8_lossy(result.error.as_slice()); // positive test so that this test will be updated when the diff --git a/src/test/run-make/unicode-input/span_length.rs b/src/test/run-make/unicode-input/span_length.rs index 13f141008b792..b776c535c9900 100644 --- a/src/test/run-make/unicode-input/span_length.rs +++ b/src/test/run-make/unicode-input/span_length.rs @@ -12,7 +12,7 @@ extern crate rand; use rand::{task_rng, Rng}; use std::{char, os, str}; -use std::io::{File, Process}; +use std::io::{File, Command}; // creates a file with `fn main() { }` and checks the // compiler emits a span of the appropriate length (for the @@ -37,9 +37,7 @@ fn main() { let args = os::args(); let rustc = args.get(1).as_slice(); let tmpdir = Path::new(args.get(2).as_slice()); - let main_file = tmpdir.join("span_main.rs"); - let main_file_str = main_file.as_str().unwrap(); for _ in range(0, 100) { let n = task_rng().gen_range(3u, 20); @@ -53,7 +51,9 @@ fn main() { // rustc is passed to us with --out-dir and -L etc., so we // can't exec it directly - let result = Process::output("sh", ["-c".to_owned(), rustc + " " + main_file_str]).unwrap(); + let result = Command::new("sh") + .arg("-c").arg(rustc + " " + main_file.as_str().unwrap()) + .output().unwrap(); let err = str::from_utf8_lossy(result.error.as_slice()); diff --git a/src/test/run-pass/backtrace.rs b/src/test/run-pass/backtrace.rs index 989453d8570d1..50f4ce6465592 100644 --- a/src/test/run-pass/backtrace.rs +++ b/src/test/run-pass/backtrace.rs @@ -14,7 +14,7 @@ extern crate native; use std::os; -use std::io::process::{Process, ProcessConfig}; +use std::io::process::Command; use std::unstable::finally::Finally; use std::str; @@ -44,12 +44,7 @@ fn runtest(me: &str) { env.push(("RUST_BACKTRACE".to_owned(), "1".to_owned())); // Make sure that the stack trace is printed - let mut p = Process::configure(ProcessConfig { - program: me, - args: ["fail".to_owned()], - env: Some(env.as_slice()), - .. ProcessConfig::new() - }).unwrap(); + let mut p = Command::new(me).arg("fail").env(env.as_slice()).spawn().unwrap(); let out = p.wait_with_output(); assert!(!out.status.success()); let s = str::from_utf8(out.error.as_slice()).unwrap(); @@ -57,11 +52,7 @@ fn runtest(me: &str) { "bad output: {}", s); // Make sure the stack trace is *not* printed - let mut p = Process::configure(ProcessConfig { - program: me, - args: ["fail".to_owned()], - .. ProcessConfig::new() - }).unwrap(); + let mut p = Command::new(me).arg("fail").spawn().unwrap(); let out = p.wait_with_output(); assert!(!out.status.success()); let s = str::from_utf8(out.error.as_slice()).unwrap(); @@ -69,11 +60,7 @@ fn runtest(me: &str) { "bad output2: {}", s); // Make sure a stack trace is printed - let mut p = Process::configure(ProcessConfig { - program: me, - args: ["double-fail".to_owned()], - .. ProcessConfig::new() - }).unwrap(); + let mut p = Command::new(me).arg("double-fail").spawn().unwrap(); let out = p.wait_with_output(); assert!(!out.status.success()); let s = str::from_utf8(out.error.as_slice()).unwrap(); @@ -81,12 +68,7 @@ fn runtest(me: &str) { "bad output3: {}", s); // Make sure a stack trace isn't printed too many times - let mut p = Process::configure(ProcessConfig { - program: me, - args: ["double-fail".to_owned()], - env: Some(env.as_slice()), - .. ProcessConfig::new() - }).unwrap(); + let mut p = Command::new(me).arg("double-fail").env(env.as_slice()).spawn().unwrap(); let out = p.wait_with_output(); assert!(!out.status.success()); let s = str::from_utf8(out.error.as_slice()).unwrap(); diff --git a/src/test/run-pass/core-run-destroy.rs b/src/test/run-pass/core-run-destroy.rs index 83d3b51f74a4f..bee3eb3825cbb 100644 --- a/src/test/run-pass/core-run-destroy.rs +++ b/src/test/run-pass/core-run-destroy.rs @@ -22,7 +22,7 @@ extern crate native; extern crate green; extern crate rustuv; -use std::io::Process; +use std::io::{Process, Command}; macro_rules! succeed( ($e:expr) => ( match $e { Ok(..) => {}, Err(e) => fail!("failure: {}", e) } @@ -36,7 +36,7 @@ macro_rules! iotest ( use std::io::timer; use libc; use std::str; - use std::io::process::{Process, ProcessOutput}; + use std::io::process::Command; use native; use super::*; @@ -68,14 +68,14 @@ iotest!(fn test_destroy_once() { #[cfg(unix)] pub fn sleeper() -> Process { - Process::new("sleep", ["1000".to_owned()]).unwrap() + Command::new("sleep").arg("1000").spawn().unwrap() } #[cfg(windows)] pub fn sleeper() -> Process { // There's a `timeout` command on windows, but it doesn't like having // its output piped, so instead just ping ourselves a few times with // gaps inbetweeen so we're sure this process is alive for awhile - Process::new("ping", ["127.0.0.1".to_owned(), "-n".to_owned(), "1000".to_owned()]).unwrap() + Command::new("ping").arg("127.0.0.1").arg("-n").arg("1000").spawn().unwrap() } iotest!(fn test_destroy_twice() { @@ -85,7 +85,7 @@ iotest!(fn test_destroy_twice() { }) pub fn test_destroy_actually_kills(force: bool) { - use std::io::process::{Process, ProcessOutput, ExitStatus, ExitSignal}; + use std::io::process::{Command, ProcessOutput, ExitStatus, ExitSignal}; use std::io::timer; use libc; use std::str; @@ -100,7 +100,7 @@ pub fn test_destroy_actually_kills(force: bool) { static BLOCK_COMMAND: &'static str = "cmd"; // this process will stay alive indefinitely trying to read from stdin - let mut p = Process::new(BLOCK_COMMAND, []).unwrap(); + let mut p = Command::new(BLOCK_COMMAND).spawn().unwrap(); assert!(p.signal(0).is_ok()); diff --git a/src/test/run-pass/issue-10626.rs b/src/test/run-pass/issue-10626.rs index 38030eb6c1fdc..dd51354721266 100644 --- a/src/test/run-pass/issue-10626.rs +++ b/src/test/run-pass/issue-10626.rs @@ -18,7 +18,7 @@ use std::io::process; pub fn main () { let args = os::args(); let args = args.as_slice(); - if args.len() > 1 && args[1] == "child".to_owned() { + if args.len() > 1 && args[1].as_slice() == "child" { for _ in range(0, 1000) { println!("hello?"); } @@ -28,14 +28,7 @@ pub fn main () { return; } - let config = process::ProcessConfig { - program : args[0].as_slice(), - args : &["child".to_owned()], - stdout: process::Ignored, - stderr: process::Ignored, - .. process::ProcessConfig::new() - }; - - let mut p = process::Process::configure(config).unwrap(); - println!("{}", p.wait()); + let mut p = process::Command::new(args[0].as_slice()); + p.arg("child").stdout(process::Ignored).stderr(process::Ignored); + println!("{}", p.spawn().unwrap().wait()); } diff --git a/src/test/run-pass/issue-13304.rs b/src/test/run-pass/issue-13304.rs index f66b943d85f64..a689deb11f47c 100644 --- a/src/test/run-pass/issue-13304.rs +++ b/src/test/run-pass/issue-13304.rs @@ -35,22 +35,23 @@ fn main() { rx.recv(); } } else { - parent("green".to_owned()); - parent("native".to_owned()); + parent("green"); + parent("native"); let (tx, rx) = channel(); native::task::spawn(proc() { - parent("green".to_owned()); - parent("native".to_owned()); + parent("green"); + parent("native"); tx.send(()); }); rx.recv(); } } -fn parent(flavor: ~str) { +fn parent(flavor: &str) { let args = os::args(); let args = args.as_slice(); - let mut p = io::Process::new(args[0].as_slice(), ["child".to_owned(), flavor]).unwrap(); + let mut p = io::process::Command::new(args[0].as_slice()) + .arg("child").arg(flavor).spawn().unwrap(); p.stdin.get_mut_ref().write_str("test1\ntest2\ntest3").unwrap(); let out = p.wait_with_output(); assert!(out.status.success()); diff --git a/src/test/run-pass/logging-separate-lines.rs b/src/test/run-pass/logging-separate-lines.rs index a5e632b94a288..0f3f2fe03d823 100644 --- a/src/test/run-pass/logging-separate-lines.rs +++ b/src/test/run-pass/logging-separate-lines.rs @@ -16,7 +16,7 @@ #[phase(syntax, link)] extern crate log; -use std::io::{Process, ProcessConfig}; +use std::io::Command; use std::os; use std::str; @@ -30,16 +30,11 @@ fn main() { } let env = [("RUST_LOG".to_owned(), "debug".to_owned())]; - let config = ProcessConfig { - program: args[0].as_slice(), - args: &["child".to_owned()], - env: Some(env.as_slice()), - ..ProcessConfig::new() - }; - let p = Process::configure(config).unwrap().wait_with_output(); + let p = Command::new(args[0].as_slice()) + .arg("child").env(env.as_slice()) + .spawn().unwrap().wait_with_output(); assert!(p.status.success()); let mut lines = str::from_utf8(p.error.as_slice()).unwrap().lines(); assert!(lines.next().unwrap().contains("foo")); assert!(lines.next().unwrap().contains("bar")); } - diff --git a/src/test/run-pass/out-of-stack.rs b/src/test/run-pass/out-of-stack.rs index ac3a9ef2d5330..1566b9ed6f19c 100644 --- a/src/test/run-pass/out-of-stack.rs +++ b/src/test/run-pass/out-of-stack.rs @@ -10,7 +10,7 @@ #![feature(asm)] -use std::io::Process; +use std::io::process::Command; use std::os; use std::str; @@ -40,12 +40,12 @@ fn main() { } else if args.len() > 1 && args[1].as_slice() == "loud" { loud_recurse(); } else { - let silent = Process::output(args[0], ["silent".to_owned()]).unwrap(); + let silent = Command::new(args[0].as_slice()).arg("silent").output().unwrap(); assert!(!silent.status.success()); let error = str::from_utf8_lossy(silent.error.as_slice()); assert!(error.as_slice().contains("has overflowed its stack")); - let loud = Process::output(args[0], ["loud".to_owned()]).unwrap(); + let loud = Command::new(args[0].as_slice()).arg("loud").output().unwrap(); assert!(!loud.status.success()); let error = str::from_utf8_lossy(silent.error.as_slice()); assert!(error.as_slice().contains("has overflowed its stack")); diff --git a/src/test/run-pass/process-detach.rs b/src/test/run-pass/process-detach.rs index 2a814956631d4..c958a3fd372d0 100644 --- a/src/test/run-pass/process-detach.rs +++ b/src/test/run-pass/process-detach.rs @@ -24,6 +24,7 @@ extern crate rustuv; extern crate libc; use std::io::process; +use std::io::process::Command; use std::io::signal::{Listener, Interrupt}; #[start] @@ -34,19 +35,12 @@ fn start(argc: int, argv: **u8) -> int { fn main() { unsafe { libc::setsid(); } - let config = process::ProcessConfig { - program : "/bin/sh", - args: &["-c".to_owned(), "read a".to_owned()], - detach: true, - .. process::ProcessConfig::new() - }; - // we shouldn't die because of an interrupt let mut l = Listener::new(); l.register(Interrupt).unwrap(); // spawn the child - let mut p = process::Process::configure(config).unwrap(); + let mut p = Command::new("/bin/sh").arg("-c").arg("read a").detached().spawn().unwrap(); // send an interrupt to everyone in our process group unsafe { libc::funcs::posix88::signal::kill(0, libc::SIGINT); } @@ -59,4 +53,3 @@ fn main() { process::ExitSignal(..) => fail!() } } - diff --git a/src/test/run-pass/signal-exit-status.rs b/src/test/run-pass/signal-exit-status.rs index a0459e6e8c1ad..174a441ace575 100644 --- a/src/test/run-pass/signal-exit-status.rs +++ b/src/test/run-pass/signal-exit-status.rs @@ -21,7 +21,7 @@ // ignore-win32 use std::os; -use std::io::process::{Process, ExitSignal, ExitStatus}; +use std::io::process::{Command, ExitSignal, ExitStatus}; pub fn main() { let args = os::args(); @@ -30,7 +30,7 @@ pub fn main() { // Raise a segfault. unsafe { *(0 as *mut int) = 0; } } else { - let status = Process::status(args[0], ["signal".to_owned()]).unwrap(); + let status = Command::new(args[0].as_slice()).arg("signal").status().unwrap(); // Windows does not have signal, so we get exit status 0xC0000028 (STATUS_BAD_STACK). match status { ExitSignal(_) if cfg!(unix) => {}, @@ -39,4 +39,3 @@ pub fn main() { } } } - diff --git a/src/test/run-pass/sigpipe-should-be-ignored.rs b/src/test/run-pass/sigpipe-should-be-ignored.rs index 34d1f5e66c678..bc6cc931b7521 100644 --- a/src/test/run-pass/sigpipe-should-be-ignored.rs +++ b/src/test/run-pass/sigpipe-should-be-ignored.rs @@ -12,7 +12,8 @@ // doesn't die in a ball of fire, but rather it's gracefully handled. use std::os; -use std::io::{PipeStream, Process}; +use std::io::PipeStream; +use std::io::Command; fn test() { let os::Pipe { input, out } = os::pipe(); @@ -30,6 +31,7 @@ fn main() { return test(); } - let mut p = Process::new(args[0], ["test".to_owned()]).unwrap(); + let mut p = Command::new(args[0].as_slice()) + .arg("test").spawn().unwrap(); assert!(p.wait().success()); }