1 // Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 // NB: transitionary, de-mode-ing.
12 #[forbid(deprecated_mode)];
13 #[forbid(deprecated_pattern)];
19 use libc::{pid_t, c_void, c_int};
21 use option::{Some, None};
32 unsafe fn rust_run_program(argv: **libc::c_char, envp: *c_void,
34 in_fd: c_int, out_fd: c_int, err_fd: c_int)
38 /// A value representing a child process
40 /// Returns the process id of the program
43 /// Returns an io::writer that can be used to write to stdin
44 fn input() -> io::Writer;
46 /// Returns an io::reader that can be used to read from stdout
47 fn output() -> io::Reader;
49 /// Returns an io::reader that can be used to read from stderr
50 fn err() -> io::Reader;
52 /// Closes the handle to the child processes standard input
56 * Waits for the child process to terminate. Closes the handle
57 * to stdin if necessary.
61 /// Closes open handles
67 * Run a program, providing stdin, stdout and stderr handles
71 * * prog - The path to an executable
72 * * args - Vector of arguments to pass to the child process
73 * * env - optional env-modification for child
74 * * dir - optional dir to run child in (default current dir)
75 * * in_fd - A file descriptor for the child to use as std input
76 * * out_fd - A file descriptor for the child to use as std output
77 * * err_fd - A file descriptor for the child to use as std error
81 * The process id of the spawned process
83 pub fn spawn_process(prog: &str, args: &[~str],
84 env: &Option<~[(~str,~str)]>,
86 in_fd: c_int, out_fd: c_int, err_fd: c_int)
89 do with_argv(prog, args) |argv| {
90 do with_envp(env) |envp| {
91 do with_dirp(dir) |dirp| {
92 rustrt::rust_run_program(argv, envp, dirp,
93 in_fd, out_fd, err_fd)
100 fn with_argv<T>(prog: &str, args: &[~str],
101 cb: fn(**libc::c_char) -> T) -> T {
102 let mut argptrs = str::as_c_str(prog, |b| ~[b]);
104 for vec::each(args) |arg| {
107 argptrs.push_all(str::as_c_str(*t, |b| ~[b]));
109 argptrs.push(ptr::null());
110 vec::as_imm_buf(argptrs, |buf, _len| cb(buf))
114 fn with_envp<T>(env: &Option<~[(~str,~str)]>,
115 cb: fn(*c_void) -> T) -> T {
116 // On posixy systems we can pass a char** for envp, which is
117 // a null-terminated array of "k=v\n" strings.
119 Some(ref es) if !vec::is_empty(*es) => {
123 for vec::each(*es) |e| {
125 let t = @(fmt!("%s=%s", k, v));
127 ptrs.push_all(str::as_c_str(*t, |b| ~[b]));
129 ptrs.push(ptr::null());
130 vec::as_imm_buf(ptrs, |p, _len|
131 unsafe { cb(::cast::reinterpret_cast(&p)) }
139 fn with_envp<T>(env: &Option<~[(~str,~str)]>,
140 cb: fn(*c_void) -> T) -> T {
141 // On win32 we pass an "environment block" which is not a char**, but
142 // rather a concatenation of null-terminated k=v\0 sequences, with a final
146 Some(ref es) if !vec::is_empty(*es) => {
147 let mut blk : ~[u8] = ~[];
148 for vec::each(*es) |e| {
150 let t = fmt!("%s=%s", k, v);
151 let mut v : ~[u8] = ::cast::reinterpret_cast(&t);
156 vec::as_imm_buf(blk, |p, _len| cb(::cast::reinterpret_cast(&p)))
163 fn with_dirp<T>(d: &Option<~str>,
164 cb: fn(*libc::c_char) -> T) -> T {
166 Some(ref dir) => str::as_c_str(*dir, cb),
167 None => cb(ptr::null())
172 * Spawns a process and waits for it to terminate
176 * * prog - The path to an executable
177 * * args - Vector of arguments to pass to the child process
183 pub fn run_program(prog: &str, args: &[~str]) -> int {
184 let pid = spawn_process(prog, args, &None, &None,
186 if pid == -1 as pid_t { fail; }
191 * Spawns a process and returns a program
193 * The returned value is a boxed class containing a <program> object that can
194 * be used for sending and receiving data over the standard file descriptors.
195 * The class will ensure that file descriptors are closed properly.
199 * * prog - The path to an executable
200 * * args - Vector of arguments to pass to the child process
204 * A class with a <program> field
206 pub fn start_program(prog: &str, args: &[~str]) -> Program {
208 let pipe_input = os::pipe();
209 let pipe_output = os::pipe();
210 let pipe_err = os::pipe();
212 spawn_process(prog, args, &None, &None,
213 pipe_input.in, pipe_output.out,
217 if pid == -1 as pid_t { fail; }
218 libc::close(pipe_input.in);
219 libc::close(pipe_output.out);
220 libc::close(pipe_err.out);
223 type ProgRepr = {pid: pid_t,
225 out_file: *libc::FILE,
226 err_file: *libc::FILE,
229 fn close_repr_input(r: &ProgRepr) {
230 let invalid_fd = -1i32;
231 if r.in_fd != invalid_fd {
233 libc::close(r.in_fd);
235 r.in_fd = invalid_fd;
238 fn finish_repr(r: &ProgRepr) -> int {
239 if r.finished { return 0; }
242 return waitpid(r.pid);
244 fn destroy_repr(r: &ProgRepr) {
247 libc::fclose(r.out_file);
248 libc::fclose(r.err_file);
253 drop { destroy_repr(&self.r); }
256 fn ProgRes(r: ProgRepr) -> ProgRes {
262 impl ProgRes: Program {
263 fn get_id() -> pid_t { return self.r.pid; }
264 fn input() -> io::Writer {
265 io::fd_writer(self.r.in_fd, false)
267 fn output() -> io::Reader {
268 io::FILE_reader(self.r.out_file, false)
270 fn err() -> io::Reader {
271 io::FILE_reader(self.r.err_file, false)
273 fn close_input() { close_repr_input(&self.r); }
274 fn finish() -> int { finish_repr(&self.r) }
275 fn destroy() { destroy_repr(&self.r); }
277 let repr = {pid: pid,
278 mut in_fd: pipe_input.out,
279 out_file: os::fdopen(pipe_output.in),
280 err_file: os::fdopen(pipe_err.in),
281 mut finished: false};
282 return ProgRes(move repr) as Program;
286 fn read_all(rd: io::Reader) -> ~str {
287 let buf = io::with_bytes_writer(|wr| {
288 let mut bytes = [mut 0, ..4096];
290 let nread = rd.read(bytes, bytes.len());
291 wr.write(bytes.view(0, nread));
298 * Spawns a process, waits for it to exit, and returns the exit code, and
299 * contents of stdout and stderr.
303 * * prog - The path to an executable
304 * * args - Vector of arguments to pass to the child process
308 * A record, {status: int, out: str, err: str} containing the exit code,
309 * the contents of stdout and the contents of stderr.
311 pub fn program_output(prog: &str, args: &[~str]) ->
312 {status: int, out: ~str, err: ~str} {
314 let pipe_in = os::pipe();
315 let pipe_out = os::pipe();
316 let pipe_err = os::pipe();
317 let pid = spawn_process(prog, args, &None, &None,
318 pipe_in.in, pipe_out.out, pipe_err.out);
320 os::close(pipe_in.in);
321 os::close(pipe_out.out);
322 os::close(pipe_err.out);
324 os::close(pipe_in.out);
325 os::close(pipe_out.in);
326 os::close(pipe_err.in);
330 os::close(pipe_in.out);
332 // Spawn two entire schedulers to read both stdout and sterr
333 // in parallel so we don't deadlock while blocking on one
334 // or the other. FIXME (#2625): Surely there's a much more
335 // clever way to do this.
336 let p = oldcomm::Port();
337 let ch = oldcomm::Chan(&p);
338 do task::spawn_sched(task::SingleThreaded) {
339 let errput = readclose(pipe_err.in);
340 oldcomm::send(ch, (2, move errput));
342 do task::spawn_sched(task::SingleThreaded) {
343 let output = readclose(pipe_out.in);
344 oldcomm::send(ch, (1, move output));
346 let status = run::waitpid(pid);
351 let stream = oldcomm::recv(p);
360 fail(fmt!("program_output received an unexpected file \
366 return {status: status, out: move outs, err: move errs};
370 pub fn writeclose(fd: c_int, s: ~str) {
373 error!("writeclose %d, %s", fd as int, s);
374 let writer = io::fd_writer(fd, false);
380 pub fn readclose(fd: c_int) -> ~str {
382 let file = os::fdopen(fd);
383 let reader = io::FILE_reader(file, false);
384 let buf = io::with_bytes_writer(|writer| {
385 let mut bytes = [mut 0, ..4096];
386 while !reader.eof() {
387 let nread = reader.read(bytes, bytes.len());
388 writer.write(bytes.view(0, nread));
396 /// Waits for a process to exit and returns the exit code
397 pub fn waitpid(pid: pid_t) -> int {
398 return waitpid_os(pid);
401 fn waitpid_os(pid: pid_t) -> int {
402 os::waitpid(pid) as int
406 fn waitpid_os(pid: pid_t) -> int {
407 #[cfg(target_os = "linux")]
408 fn WIFEXITED(status: i32) -> bool {
409 (status & 0xffi32) == 0i32
412 #[cfg(target_os = "macos")]
413 #[cfg(target_os = "freebsd")]
414 fn WIFEXITED(status: i32) -> bool {
415 (status & 0x7fi32) == 0i32
418 #[cfg(target_os = "linux")]
419 fn WEXITSTATUS(status: i32) -> i32 {
420 (status >> 8i32) & 0xffi32
423 #[cfg(target_os = "macos")]
424 #[cfg(target_os = "freebsd")]
425 fn WEXITSTATUS(status: i32) -> i32 {
429 let status = os::waitpid(pid);
430 return if WIFEXITED(status) {
431 WEXITSTATUS(status) as int
442 use option::{None, Some};
444 use run::{readclose, writeclose};
447 // Regression test for memory leaks
448 #[ignore(cfg(windows))] // FIXME (#2626)
449 pub fn test_leaks() {
450 run::run_program("echo", []);
451 run::start_program("echo", []);
452 run::program_output("echo", []);
456 #[allow(non_implicitly_copyable_typarams)]
457 pub fn test_pipes() {
458 let pipe_in = os::pipe();
459 let pipe_out = os::pipe();
460 let pipe_err = os::pipe();
464 "cat", [], &None, &None,
465 pipe_in.in, pipe_out.out, pipe_err.out);
466 os::close(pipe_in.in);
467 os::close(pipe_out.out);
468 os::close(pipe_err.out);
470 if pid == -1i32 { fail; }
471 let expected = ~"test";
472 writeclose(pipe_in.out, copy expected);
473 let actual = readclose(pipe_out.in);
474 readclose(pipe_err.in);
477 log(debug, copy expected);
478 log(debug, copy actual);
479 assert (expected == actual);
484 let pid = run::spawn_process("false", [],
487 let status = run::waitpid(pid);
496 // indent-tabs-mode: nil
498 // buffer-file-coding-system: utf-8-unix