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]
This commit is contained in:
Aaron Turon 2014-05-05 14:33:55 -07:00
parent 8f9cbe08c6
commit 046062d3bf
27 changed files with 668 additions and 719 deletions

View File

@ -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<Result> {
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();
@ -100,14 +93,7 @@ pub fn run_background(lib_path: &str,
input: Option<~str>) -> Option<Process> {
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();

View File

@ -420,7 +420,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());
@ -483,25 +483,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 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 args = &[lldb_batchmode_script, test_executable_str, debugger_script_str];
let env = &[("PYTHONPATH".to_owned(), config.lldb_python_dir.clone().unwrap())];
let opt_process = Process::configure(ProcessConfig {
program: "python",
args: args,
env: Some(env),
.. ProcessConfig::new()
});
let (status, out, err) = match opt_process {
let (status, out, err) = match cmd.spawn() {
Ok(process) => {
let ProcessOutput { status, output, error } =
process.wait_with_output().unwrap();
@ -520,7 +508,7 @@ fn run_debuginfo_lldb_test(config: &Config, props: &TestProps, testfile: &Path)
status: status,
stdout: out,
stderr: err,
cmdline: commandline
cmdline: format!("{}", cmd)
};
}
}

View File

@ -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<Box<RtioTimer:Send>> {
timer::Timer::new().map(|t| box t as Box<RtioTimer:Send>)
}
fn spawn(&mut self, config: ProcessConfig)
fn spawn(&mut self, cfg: ProcessConfig)
-> IoResult<(Box<RtioProcess:Send>,
Vec<Option<Box<RtioPipe:Send>>>)> {
process::Process::spawn(config).map(|(p, io)| {
process::Process::spawn(cfg).map(|(p, io)| {
(box p as Box<RtioProcess:Send>,
io.move_iter().map(|p| p.map(|p| {
box p as Box<RtioPipe:Send>

View File

@ -15,9 +15,10 @@ use std::mem;
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;
use super::util;
@ -65,27 +66,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<Option<file::FileDesc>>), 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());
}
@ -109,14 +94,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); }
@ -262,11 +244,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<SpawnProcessResult> {
fn spawn_process_os(cfg: ProcessConfig, in_fd: c_int, out_fd: c_int, err_fd: c_int)
-> IoResult<SpawnProcessResult> {
use libc::types::os::arch::extra::{DWORD, HANDLE, STARTUPINFO};
use libc::consts::os::extra::{
TRUE, FALSE,
@ -284,7 +263,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",
@ -293,7 +272,6 @@ fn spawn_process_os(config: p::ProcessConfig,
}
unsafe {
let mut si = zeroed_startupinfo();
si.cb = mem::size_of::<STARTUPINFO>() as DWORD;
si.dwFlags = STARTF_USESTDHANDLES;
@ -333,23 +311,26 @@ fn spawn_process_os(config: p::ProcessConfig,
}
}
let cmd = make_command_line(config.program, config.args);
let 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 = libc::CREATE_UNICODE_ENVIRONMENT;
if config.detach {
if cfg.detach {
flags |= libc::DETACHED_PROCESS | libc::CREATE_NEW_PROCESS_GROUP;
}
with_envp(env, |envp| {
with_dirp(dir, |dirp| {
os::win32::as_mut_utf16_p(cmd, |cmdp| {
let created = CreateProcessW(ptr::null(), cmdp,
ptr::mut_null(), ptr::mut_null(), TRUE,
flags, envp, dirp, &mut si,
&mut pi);
with_envp(cfg.env, |envp| {
with_dirp(cfg.cwd, |dirp| {
os::win32::as_mut_utf16_p(cmd_str, |cmdp| {
let created = CreateProcessW(ptr::null(),
cmdp,
ptr::mut_null(),
ptr::mut_null(),
TRUE,
flags, envp, dirp,
&mut si, &mut pi);
if created == FALSE {
create_err = Some(super::last_error());
}
@ -415,12 +396,14 @@ fn zeroed_process_information() -> libc::types::os::arch::extra::PROCESS_INFORMA
}
#[cfg(windows)]
fn make_command_line(prog: &str, args: &[~str]) -> ~str {
fn make_command_line(prog: &CString, args: &[CString]) -> ~str {
let mut cmd = StrBuf::new();
append_arg(&mut cmd, prog);
append_arg(&mut cmd, prog.as_str()
.expect("expected program name to be utf-8 encoded"));
for arg in args.iter() {
cmd.push_char(' ');
append_arg(&mut cmd, *arg);
append_arg(&mut cmd, arg.as_str()
.expect("expected argument to be utf-8 encoded"));
}
return cmd.into_owned();
@ -468,11 +451,9 @@ fn make_command_line(prog: &str, args: &[~str]) -> ~str {
}
#[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<SpawnProcessResult> {
fn spawn_process_os(cfg: ProcessConfig, in_fd: c_int, out_fd: c_int, err_fd: c_int)
-> IoResult<SpawnProcessResult>
{
use libc::funcs::posix88::unistd::{fork, dup2, close, chdir, execvp};
use libc::funcs::bsd44::getdtablesize;
use io::c;
@ -500,11 +481,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);
@ -605,7 +585,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);
@ -613,7 +593,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
@ -633,7 +613,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.
@ -652,47 +632,47 @@ fn spawn_process_os(config: p::ProcessConfig,
}
#[cfg(unix)]
fn with_argv<T>(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);
fn with_argv<T>(prog: &CString, args: &[CString], cb: proc(**libc::c_char) -> T) -> T {
let mut ptrs: Vec<*libc::c_char> = Vec::with_capacity(args.len()+1);
tmps.push(prog.to_c_str());
// 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)));
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.
// Add a terminating null pointer (required by libc).
ptrs.push(ptr::null());
cb(ptrs.as_ptr())
}
#[cfg(unix)]
fn with_envp<T>(env: Option<~[(~str, ~str)]>, cb: proc(*c_void) -> T) -> T {
fn with_envp<T>(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)
@ -702,7 +682,7 @@ fn with_envp<T>(env: Option<~[(~str, ~str)]>, cb: proc(*c_void) -> T) -> T {
}
#[cfg(windows)]
fn with_envp<T>(env: Option<~[(~str, ~str)]>, cb: |*mut c_void| -> T) -> T {
fn with_envp<T>(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.
@ -711,7 +691,9 @@ fn with_envp<T>(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());
let kv = format!("{}={}",
pair.ref0().as_str().unwrap(),
pair.ref1().as_str().unwrap());
blk.push_all(kv.to_utf16().as_slice());
blk.push(0);
}
@ -725,11 +707,12 @@ fn with_envp<T>(env: Option<~[(~str, ~str)]>, cb: |*mut c_void| -> T) -> T {
}
#[cfg(windows)]
fn with_dirp<T>(d: Option<&Path>, cb: |*u16| -> T) -> T {
fn with_dirp<T>(d: Option<&CString>, cb: |*u16| -> T) -> T {
match d {
Some(dir) => match dir.as_str() {
Some(dir_str) => os::win32::as_utf16_p(dir_str, cb),
None => cb(ptr::null())
Some(dir) => {
let dir_str = dir.as_str()
.expect("expected workingdirectory to be utf-8 encoded");
os::win32::as_utf16_p(dir_str, cb)
},
None => cb(ptr::null())
}
@ -1106,25 +1089,37 @@ mod tests {
#[test] #[cfg(windows)]
fn test_make_command_line() {
use std::str;
use std::c_str::CString;
use super::make_command_line;
fn test_wrapper(prog: &str, args: &[&str]) -> ~str {
make_command_line(&prog.to_c_str(),
args.iter()
.map(|a| a.to_c_str())
.collect::<Vec<CString>>()
.as_slice())
}
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()
);
assert_eq!(
make_command_line("\u03c0\u042f\u97f3\u00e6\u221e", []),
test_wrapper("\u03c0\u042f\u97f3\u00e6\u221e", []),
"\u03c0\u042f\u97f3\u00e6\u221e".to_owned()
);
}

View File

@ -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(prog) => {
let o = prog.wait_with_output().unwrap();
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");
}

View File

@ -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);
let pname = super::get_cc_prog(sess);
let mut cmd = Command::new(pname.as_slice());
// 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()];
cmd.arg("-c").arg("-o").arg(outputs.path(OutputTypeObject))
.arg(outputs.temp_path(OutputTypeAssembly));
debug!("{}", &cmd);
debug!("{} '{}'", cc, args.connect("' '"));
match Process::output(cc.as_slice(), args) {
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),
@ -1026,31 +1024,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::<Vec<_>>()
.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());
@ -1058,7 +1055,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();
}
}
@ -1067,9 +1064,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));
@ -1079,25 +1074,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<StrBuf> {
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
@ -1114,14 +1104,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
@ -1132,7 +1121,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
@ -1140,20 +1129,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
@ -1166,14 +1155,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.
@ -1200,13 +1189,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
@ -1242,39 +1231,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
@ -1284,15 +1272,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
@ -1306,16 +1293,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<StrBuf>, 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.
@ -1329,21 +1314,21 @@ fn add_local_native_libraries(args: &mut Vec<StrBuf>, 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");
}
}
@ -1352,7 +1337,7 @@ fn add_local_native_libraries(args: &mut Vec<StrBuf>, 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<StrBuf>, 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
@ -1384,26 +1369,26 @@ fn add_upstream_rust_crates(args: &mut Vec<StrBuf>, 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<StrBuf>, 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
@ -1434,34 +1419,32 @@ fn add_upstream_rust_crates(args: &mut Vec<StrBuf>, 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<StrBuf>, 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());
}
}
@ -1470,12 +1453,12 @@ fn add_upstream_rust_crates(args: &mut Vec<StrBuf>, 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
@ -1483,7 +1466,7 @@ fn add_upstream_rust_crates(args: &mut Vec<StrBuf>, 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<StrBuf>, 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
@ -1499,11 +1482,11 @@ fn add_upstream_native_libraries(args: &mut Vec<StrBuf>, 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");

View File

@ -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;
@ -155,9 +155,7 @@ fn runtest(test: &str, cratename: &str, libs: HashSet<Path>, 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?"

View File

@ -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};
@ -50,12 +51,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<Process>, Vec<Option<PipeWatcher>>), 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<Process>, Vec<Option<PipeWatcher>>), 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::<uvll::uv_stdio_container_t>::with_capacity(io.len());
@ -69,16 +68,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 {
@ -86,15 +85,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::<Process>, uvll::UV_PROCESS);
@ -175,42 +174,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<T>(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<T>(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<T>(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());
fn with_env<T>(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())
}
let mut c_envp = Vec::with_capacity(envp.len() + 1);
for s in envp.iter() {
c_envp.push(s.with_ref(|p| p));
}
c_envp.push(ptr::null());
f(c_envp.as_ptr())
}
impl HomingIO for Process {

View File

@ -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<rtio::RtioProcess:Send>,
Vec<Option<Box<rtio::RtioPipe:Send>>>),
IoError>
{
match Process::spawn(self, config) {
match Process::spawn(self, cfg) {
Ok((p, io)) => {
Ok((p as Box<rtio::RtioProcess:Send>,
io.move_iter().map(|i| i.map(|p| {

View File

@ -245,7 +245,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};

View File

@ -14,13 +14,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.
@ -37,16 +39,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),
/// };
@ -74,71 +76,244 @@ pub struct Process {
pub extra_io: Vec<Option<io::PipeStream>>,
}
/// 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<CString>,
env: Option<Vec<(CString, CString)>>,
cwd: Option<CString>,
stdin: StdioContainer,
stdout: StdioContainer,
stderr: StdioContainer,
extra_io: Vec<StdioContainer>,
uid: Option<uint>,
gid: Option<uint>,
detach: bool,
}
/// Arguments to pass to the program (doesn't include the program itself)
pub args: &'a [~str],
// 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}.)
/// 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)]>,
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<T:ToCStr>(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,
}
}
/// 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>,
/// 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
}
/// 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
}
/// 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,
/// 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 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],
/// 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<uint>,
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<uint>,
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<Process> {
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<ProcessOutput> {
self.spawn().and_then(|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<ProcessExit> {
self.spawn().and_then(|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.
@ -206,127 +381,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> {
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<ProcessOutput> {
Process::new(prog, args).and_then(|p| p.wait_with_output())
}
/// Executes a child process and collects its exit status. This will block
/// waiting for the child to exit.
///
/// # Example
///
/// ```
/// use std::io::Process;
///
/// let status = match Process::status("ls", []) {
/// Ok(status) => status,
/// Err(e) => fail!("failed to execute process: {}", e),
/// };
///
/// println!("process exited with: {}", status);
/// ```
pub fn status(prog: &str, args: &[~str]) -> IoResult<ProcessExit> {
Process::new(prog, args).and_then(|mut p| p.wait())
}
/// Creates a new process with the specified configuration.
pub fn configure(config: ProcessConfig) -> IoResult<Process> {
let mut config = Some(config);
LocalIo::maybe_raise(|io| {
io.spawn(config.take_unwrap()).map(|(p, io)| {
let mut io = io.move_iter().map(|p| {
p.map(|p| io::PipeStream::new(p))
});
Process {
handle: p,
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
@ -403,11 +458,11 @@ impl Process {
///
/// ```no_run
/// # #![allow(experimental)]
/// use std::io::process::{Process, ProcessExit};
/// use std::io::process::{Command, ProcessExit};
/// use std::io::IoResult;
///
/// fn run_gracefully(prog: &str) -> IoResult<ProcessExit> {
/// let mut p = try!(Process::new("long-running-process", []));
/// let mut p = try!(Command::new("long-running-process").spawn());
///
/// // give the process 10 seconds to finish completely
/// p.set_timeout(Some(10_000));
@ -487,18 +542,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().unwrap().success());
@ -506,11 +557,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(..) => {}
}
@ -518,11 +565,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().unwrap().matches_exit_status(1));
@ -531,12 +574,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().unwrap() {
@ -549,8 +587,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());
@ -561,38 +599,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);
@ -602,36 +629,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().unwrap().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().unwrap().success());
})
@ -642,26 +656,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!()
}
@ -669,9 +677,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());
@ -685,7 +692,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());
@ -694,21 +701,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().unwrap().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().unwrap().matches_exit_status(1));
assert!(prog.wait().unwrap().matches_exit_status(1));
})
#[cfg(not(target_os="android"))]
iotest!(fn test_wait_with_output_once() {
let prog = Process::new("echo", ["hello".to_owned()]).unwrap();
let prog = Command::new("echo").arg("hello").spawn().unwrap();
let ProcessOutput {status, output, error} = prog.wait_with_output().unwrap();
let output_str = str::from_utf8(output.as_slice()).unwrap();
@ -721,36 +727,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 prog = run_pwd(None);
let prog = pwd_cmd().spawn().unwrap();
let output = str::from_utf8(prog.wait_with_output().unwrap()
.output.as_slice()).unwrap().to_owned();
@ -769,7 +765,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 prog = run_pwd(Some(&parent_dir));
let prog = pwd_cmd().cwd(&parent_dir).spawn().unwrap();
let output = str::from_utf8(prog.wait_with_output().unwrap()
.output.as_slice()).unwrap().to_owned();
@ -783,31 +779,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"))]
@ -815,7 +801,7 @@ mod tests {
use os;
if running_on_valgrind() { return; }
let prog = run_env(None);
let prog = env_cmd().spawn().unwrap();
let output = str::from_utf8(prog.wait_with_output().unwrap()
.output.as_slice()).unwrap().to_owned();
@ -830,7 +816,7 @@ mod tests {
use os;
if running_on_valgrind() { return; }
let prog = run_env(None);
let mut prog = env_cmd().spawn().unwrap();
let output = str::from_utf8(prog.wait_with_output()
.unwrap().output.as_slice())
.unwrap().to_owned();
@ -846,9 +832,8 @@ mod tests {
})
iotest!(fn test_add_to_env() {
let new_env = box [("RUN_TEST_NEW_ENV".to_owned(), "123".to_owned())];
let prog = run_env(Some(new_env));
let new_env = box [("RUN_TEST_NEW_ENV", "123")];
let prog = env_cmd().env(new_env).spawn().unwrap();
let result = prog.wait_with_output().unwrap();
let output = str::from_utf8_lossy(result.output.as_slice()).into_owned();
@ -858,14 +843,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() {

View File

@ -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<uint>,
/// Similar to `uid`, but sets the group id of the child process. This has
/// the same semantics as the `uid` field.
pub gid: Option<uint>,
/// 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<Box<RtioTimer:Send>>;
fn spawn(&mut self, config: ProcessConfig)
fn spawn(&mut self, cfg: ProcessConfig)
-> IoResult<(Box<RtioProcess:Send>,
Vec<Option<Box<RtioPipe:Send>>>)>;
fn kill(&mut self, pid: libc::pid_t, signal: int) -> IoResult<()>;

View File

@ -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};

View File

@ -488,7 +488,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
@ -522,10 +522,7 @@ fn test() {
prep.exec(proc(_exe) {
let out = make_path("foo.o".to_strbuf());
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.

View File

@ -27,7 +27,8 @@ fn bar() { }
fn baz() { }
pub fn test() {
let lib = DynamicLibrary::open(None).unwrap();
let none: Option<Path> = None; // appease the typechecker
let lib = DynamicLibrary::open(none).unwrap();
unsafe {
assert!(lib.symbol::<int>("foo").is_ok());
assert!(lib.symbol::<int>("baz").is_err());

View File

@ -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

View File

@ -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() { <random ident> }` 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());

View File

@ -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;
@ -48,15 +48,7 @@ fn runtest(me: &str) {
env.push(("RUST_BACKTRACE".to_strbuf(), "1".to_strbuf()));
// Make sure that the stack trace is printed
let env = env.iter()
.map(|&(ref k, ref v)| (k.to_owned(), v.to_owned()))
.collect::<Vec<_>>();
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().unwrap();
assert!(!out.status.success());
let s = str::from_utf8(out.error.as_slice()).unwrap();
@ -64,11 +56,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().unwrap();
assert!(!out.status.success());
let s = str::from_utf8(out.error.as_slice()).unwrap();
@ -76,11 +64,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().unwrap();
assert!(!out.status.success());
let s = str::from_utf8(out.error.as_slice()).unwrap();
@ -88,12 +72,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().unwrap();
assert!(!out.status.success());
let s = str::from_utf8(out.error.as_slice()).unwrap();

View File

@ -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());

View File

@ -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());
}

View File

@ -50,10 +50,8 @@ fn main() {
fn parent(flavor: StrBuf) {
let args = os::args();
let args = args.as_slice();
let mut p = io::Process::new(args[0].as_slice(), [
"child".to_owned(),
flavor.to_owned()
]).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().unwrap();
assert!(out.status.success());

View File

@ -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().unwrap();
let p = Command::new(args[0].as_slice())
.arg("child").env(env.as_slice())
.spawn().unwrap().wait_with_output().unwrap();
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"));
}

View File

@ -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"));

View File

@ -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!()
}
}

View File

@ -20,8 +20,7 @@ extern crate native;
use std::io;
use std::io::fs;
use std::io::process::Process;
use std::io::process::ProcessConfig;
use std::io::Command;
use std::os;
use std::path::Path;
@ -56,13 +55,11 @@ fn main() {
assert!(fs::copy(&my_path, &child_path).is_ok());
// run child
let p = Process::configure(ProcessConfig {
program: child_path.as_str().unwrap(),
args: [arg.to_owned()],
cwd: Some(&cwd),
env: Some(my_env.append_one(env).as_slice()),
.. ProcessConfig::new()
}).unwrap().wait_with_output().unwrap();
let p = Command::new(&child_path)
.arg(arg)
.cwd(&cwd)
.env(my_env.append_one(env).as_slice())
.spawn().unwrap().wait_with_output().unwrap();
// display the output
assert!(io::stdout().write(p.output.as_slice()).is_ok());

View File

@ -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() {
}
}
}

View File

@ -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().unwrap().success());
}