Skip to content

Commit 4dacd73

Browse files
committed
auto merge of #9260 : alexcrichton/rust/libuv-processes, r=brson
This is a re-landing of #8645, except that the bindings are *not* being used to power std::run just yet. Instead, this adds the bindings as standalone bindings inside the rt::io::process module. I made one major change from before, having to do with how pipes are created/bound. It's much clearer now when you can read/write to a pipe, as there's an explicit difference (different types) between an unbound and a bound pipe. The process configuration now takes unbound pipes (and consumes ownership of them), and will return corresponding pipe structures back if spawning is successful (otherwise everything is destroyed normally).
2 parents 36cc414 + cb7756a commit 4dacd73

File tree

11 files changed

+1076
-67
lines changed

11 files changed

+1076
-67
lines changed

src/libstd/rt/io/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@ pub use self::net::ip::IpAddr;
260260
pub use self::net::tcp::TcpListener;
261261
pub use self::net::tcp::TcpStream;
262262
pub use self::net::udp::UdpStream;
263+
pub use self::pipe::PipeStream;
264+
pub use self::pipe::UnboundPipeStream;
265+
pub use self::process::Process;
263266

264267
// Some extension traits that all Readers and Writers get.
265268
pub use self::extensions::ReaderUtil;
@@ -269,6 +272,12 @@ pub use self::extensions::WriterByteConversions;
269272
/// Synchronous, non-blocking file I/O.
270273
pub mod file;
271274

275+
/// Synchronous, in-memory I/O.
276+
pub mod pipe;
277+
278+
/// Child process management.
279+
pub mod process;
280+
272281
/// Synchronous, non-blocking network I/O.
273282
pub mod net;
274283

src/libstd/rt/io/pipe.rs

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2013 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.
4+
//
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.
10+
11+
//! Synchronous, in-memory pipes.
12+
//!
13+
//! Currently these aren't particularly useful, there only exists bindings
14+
//! enough so that pipes can be created to child processes.
15+
16+
use prelude::*;
17+
use super::{Reader, Writer};
18+
use rt::io::{io_error, read_error, EndOfFile};
19+
use rt::local::Local;
20+
use rt::rtio::{RtioPipe, RtioPipeObject, IoFactoryObject, IoFactory};
21+
use rt::rtio::RtioUnboundPipeObject;
22+
23+
pub struct PipeStream(RtioPipeObject);
24+
pub struct UnboundPipeStream(~RtioUnboundPipeObject);
25+
26+
impl PipeStream {
27+
/// Creates a new pipe initialized, but not bound to any particular
28+
/// source/destination
29+
pub fn new() -> Option<UnboundPipeStream> {
30+
let pipe = unsafe {
31+
let io: *mut IoFactoryObject = Local::unsafe_borrow();
32+
(*io).pipe_init(false)
33+
};
34+
match pipe {
35+
Ok(p) => Some(UnboundPipeStream(p)),
36+
Err(ioerr) => {
37+
io_error::cond.raise(ioerr);
38+
None
39+
}
40+
}
41+
}
42+
43+
pub fn bind(inner: RtioPipeObject) -> PipeStream {
44+
PipeStream(inner)
45+
}
46+
}
47+
48+
impl Reader for PipeStream {
49+
fn read(&mut self, buf: &mut [u8]) -> Option<uint> {
50+
match (**self).read(buf) {
51+
Ok(read) => Some(read),
52+
Err(ioerr) => {
53+
// EOF is indicated by returning None
54+
if ioerr.kind != EndOfFile {
55+
read_error::cond.raise(ioerr);
56+
}
57+
return None;
58+
}
59+
}
60+
}
61+
62+
fn eof(&mut self) -> bool { fail!() }
63+
}
64+
65+
impl Writer for PipeStream {
66+
fn write(&mut self, buf: &[u8]) {
67+
match (**self).write(buf) {
68+
Ok(_) => (),
69+
Err(ioerr) => {
70+
io_error::cond.raise(ioerr);
71+
}
72+
}
73+
}
74+
75+
fn flush(&mut self) { fail!() }
76+
}

src/libstd/rt/io/process.rs

+278
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
// Copyright 2013 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.
4+
//
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.
10+
11+
//! Bindings for executing child processes
12+
13+
use prelude::*;
14+
15+
use libc;
16+
use rt::io;
17+
use rt::io::io_error;
18+
use rt::local::Local;
19+
use rt::rtio::{RtioProcess, RtioProcessObject, IoFactoryObject, IoFactory};
20+
21+
pub struct Process {
22+
priv handle: ~RtioProcessObject,
23+
io: ~[Option<io::PipeStream>],
24+
}
25+
26+
/// This configuration describes how a new process should be spawned. This is
27+
/// translated to libuv's own configuration
28+
pub struct ProcessConfig<'self> {
29+
/// Path to the program to run
30+
program: &'self str,
31+
32+
/// Arguments to pass to the program (doesn't include the program itself)
33+
args: &'self [~str],
34+
35+
/// Optional environment to specify for the program. If this is None, then
36+
/// it will inherit the current process's environment.
37+
env: Option<&'self [(~str, ~str)]>,
38+
39+
/// Optional working directory for the new process. If this is None, then
40+
/// the current directory of the running process is inherited.
41+
cwd: Option<&'self str>,
42+
43+
/// Any number of streams/file descriptors/pipes may be attached to this
44+
/// process. This list enumerates the file descriptors and such for the
45+
/// process to be spawned, and the file descriptors inherited will start at
46+
/// 0 and go to the length of this array.
47+
///
48+
/// Standard file descriptors are:
49+
///
50+
/// 0 - stdin
51+
/// 1 - stdout
52+
/// 2 - stderr
53+
io: ~[StdioContainer]
54+
}
55+
56+
/// Describes what to do with a standard io stream for a child process.
57+
pub enum StdioContainer {
58+
/// This stream will be ignored. This is the equivalent of attaching the
59+
/// stream to `/dev/null`
60+
Ignored,
61+
62+
/// The specified file descriptor is inherited for the stream which it is
63+
/// specified for.
64+
InheritFd(libc::c_int),
65+
66+
// XXX: these two shouldn't have libuv-specific implementation details
67+
68+
/// The specified libuv stream is inherited for the corresponding file
69+
/// descriptor it is assigned to.
70+
// XXX: this needs to be thought out more.
71+
//InheritStream(uv::net::StreamWatcher),
72+
73+
/// Creates a pipe for the specified file descriptor which will be directed
74+
/// into the previously-initialized pipe passed in.
75+
///
76+
/// The first boolean argument is whether the pipe is readable, and the
77+
/// second is whether it is writable. These properties are from the view of
78+
/// the *child* process, not the parent process.
79+
CreatePipe(io::UnboundPipeStream,
80+
bool /* readable */,
81+
bool /* writable */),
82+
}
83+
84+
impl Process {
85+
/// Creates a new pipe initialized, but not bound to any particular
86+
/// source/destination
87+
pub fn new(config: ProcessConfig) -> Option<Process> {
88+
let process = unsafe {
89+
let io: *mut IoFactoryObject = Local::unsafe_borrow();
90+
(*io).spawn(config)
91+
};
92+
match process {
93+
Ok((p, io)) => Some(Process{
94+
handle: p,
95+
io: io.move_iter().map(|p|
96+
p.map_move(|p| io::PipeStream::bind(p))
97+
).collect()
98+
}),
99+
Err(ioerr) => {
100+
io_error::cond.raise(ioerr);
101+
None
102+
}
103+
}
104+
}
105+
106+
/// Returns the process id of this child process
107+
pub fn id(&self) -> libc::pid_t { self.handle.id() }
108+
109+
/// Sends the specified signal to the child process, returning whether the
110+
/// signal could be delivered or not.
111+
///
112+
/// Note that this is purely a wrapper around libuv's `uv_process_kill`
113+
/// function.
114+
///
115+
/// If the signal delivery fails, then the `io_error` condition is raised on
116+
pub fn signal(&mut self, signal: int) {
117+
match self.handle.kill(signal) {
118+
Ok(()) => {}
119+
Err(err) => {
120+
io_error::cond.raise(err)
121+
}
122+
}
123+
}
124+
125+
/// Wait for the child to exit completely, returning the status that it
126+
/// exited with. This function will continue to have the same return value
127+
/// after it has been called at least once.
128+
pub fn wait(&mut self) -> int { self.handle.wait() }
129+
}
130+
131+
impl Drop for Process {
132+
fn drop(&mut self) {
133+
// Close all I/O before exiting to ensure that the child doesn't wait
134+
// forever to print some text or something similar.
135+
for _ in range(0, self.io.len()) {
136+
self.io.pop();
137+
}
138+
139+
self.wait();
140+
}
141+
}
142+
143+
#[cfg(test)]
144+
mod tests {
145+
use prelude::*;
146+
use super::*;
147+
148+
use rt::io::{Reader, Writer};
149+
use rt::io::pipe::*;
150+
use str;
151+
152+
#[test]
153+
#[cfg(unix, not(android))]
154+
fn smoke() {
155+
let io = ~[];
156+
let args = ProcessConfig {
157+
program: "/bin/sh",
158+
args: [~"-c", ~"true"],
159+
env: None,
160+
cwd: None,
161+
io: io,
162+
};
163+
let p = Process::new(args);
164+
assert!(p.is_some());
165+
let mut p = p.unwrap();
166+
assert_eq!(p.wait(), 0);
167+
}
168+
169+
#[test]
170+
#[cfg(unix, not(android))]
171+
fn smoke_failure() {
172+
let io = ~[];
173+
let args = ProcessConfig {
174+
program: "if-this-is-a-binary-then-the-world-has-ended",
175+
args: [],
176+
env: None,
177+
cwd: None,
178+
io: io,
179+
};
180+
let p = Process::new(args);
181+
assert!(p.is_some());
182+
let mut p = p.unwrap();
183+
assert!(p.wait() != 0);
184+
}
185+
186+
#[test]
187+
#[cfg(unix, not(android))]
188+
fn exit_reported_right() {
189+
let io = ~[];
190+
let args = ProcessConfig {
191+
program: "/bin/sh",
192+
args: [~"-c", ~"exit 1"],
193+
env: None,
194+
cwd: None,
195+
io: io,
196+
};
197+
let p = Process::new(args);
198+
assert!(p.is_some());
199+
let mut p = p.unwrap();
200+
assert_eq!(p.wait(), 1);
201+
}
202+
203+
fn read_all(input: &mut Reader) -> ~str {
204+
let mut ret = ~"";
205+
let mut buf = [0, ..1024];
206+
loop {
207+
match input.read(buf) {
208+
None | Some(0) => { break }
209+
Some(n) => { ret = ret + str::from_utf8(buf.slice_to(n)); }
210+
}
211+
}
212+
return ret;
213+
}
214+
215+
fn run_output(args: ProcessConfig) -> ~str {
216+
let p = Process::new(args);
217+
assert!(p.is_some());
218+
let mut p = p.unwrap();
219+
assert!(p.io[0].is_none());
220+
assert!(p.io[1].is_some());
221+
let ret = read_all(p.io[1].get_mut_ref() as &mut Reader);
222+
assert_eq!(p.wait(), 0);
223+
return ret;
224+
}
225+
226+
#[test]
227+
#[cfg(unix, not(android))]
228+
fn stdout_works() {
229+
let pipe = PipeStream::new().unwrap();
230+
let io = ~[Ignored, CreatePipe(pipe, false, true)];
231+
let args = ProcessConfig {
232+
program: "/bin/sh",
233+
args: [~"-c", ~"echo foobar"],
234+
env: None,
235+
cwd: None,
236+
io: io,
237+
};
238+
assert_eq!(run_output(args), ~"foobar\n");
239+
}
240+
241+
#[test]
242+
#[cfg(unix, not(android))]
243+
fn set_cwd_works() {
244+
let pipe = PipeStream::new().unwrap();
245+
let io = ~[Ignored, CreatePipe(pipe, false, true)];
246+
let cwd = Some("/");
247+
let args = ProcessConfig {
248+
program: "/bin/sh",
249+
args: [~"-c", ~"pwd"],
250+
env: None,
251+
cwd: cwd,
252+
io: io,
253+
};
254+
assert_eq!(run_output(args), ~"/\n");
255+
}
256+
257+
#[test]
258+
#[cfg(unix, not(android))]
259+
fn stdin_works() {
260+
let input = PipeStream::new().unwrap();
261+
let output = PipeStream::new().unwrap();
262+
let io = ~[CreatePipe(input, true, false),
263+
CreatePipe(output, false, true)];
264+
let args = ProcessConfig {
265+
program: "/bin/sh",
266+
args: [~"-c", ~"read line; echo $line"],
267+
env: None,
268+
cwd: None,
269+
io: io,
270+
};
271+
let mut p = Process::new(args).expect("didn't create a proces?!");
272+
p.io[0].get_mut_ref().write("foobar".as_bytes());
273+
p.io[0] = None; // close stdin;
274+
let out = read_all(p.io[1].get_mut_ref() as &mut Reader);
275+
assert_eq!(p.wait(), 0);
276+
assert_eq!(out, ~"foobar\n");
277+
}
278+
}

0 commit comments

Comments
 (0)