auto merge of #9749 : alexcrichton/rust/less-io, r=brson

This implements a number of the baby steps needed to start eliminating everything inside of `std::io`. It turns out that there are a *lot* of users of that module, so I'm going to try to tackle them separately instead of bringing down the whole system all at once.

This pull implements a large amount of unimplemented functionality inside of `std::rt::io` including:

* Native file I/O (file descriptors, *FILE)
* Native stdio (through the native file descriptors)
* Native processes (extracted from `std::run`)

I also found that there are a number of users of `std::io` which desire to read an input line-by-line, so I added an implementation of `read_until` and `read_line` to `BufferedReader`.

With all of these changes in place, I started to axe various usages of `std::io`. There's a lot of one-off uses here-and-there, but the major use-case remaining that doesn't have a fantastic solution is `extra::json`. I ran into a few compiler bugs when attempting to remove that, so I figured I'd come back to it later instead. 

There is one fairly major change in this pull, and it's moving from native stdio to uv stdio via `print` and `println`. Unfortunately logging still goes through native I/O (via `dumb_println`). This is going to need some thinking, because I still want the goal of logging/printing to be 0 allocations, and this is not possible if `io::stdio::stderr()` is called on each log message. Instead I think that this may need to be cached as the `logger` field inside the `Task` struct, but that will require a little more workings to get right (this is also a similar problem for print/println, do we cache `stdout()` to not have to re-create it every time?).
This commit is contained in:
bors 2013-10-10 04:31:24 -07:00
commit 0ede2ea4e2
21 changed files with 1385 additions and 1007 deletions

View file

@ -8,17 +8,21 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::io;
pub struct ExpectedError { line: uint, kind: ~str, msg: ~str }
// Load any test directives embedded in the file
pub fn load_errors(testfile: &Path) -> ~[ExpectedError] {
use std::rt::io::Open;
use std::rt::io::file::FileInfo;
use std::rt::io::buffered::BufferedReader;
let mut error_patterns = ~[];
let rdr = io::file_reader(testfile).unwrap();
let mut rdr = BufferedReader::new(testfile.open_reader(Open).unwrap());
let mut line_num = 1u;
while !rdr.eof() {
let ln = rdr.read_line();
loop {
let ln = match rdr.read_line() {
Some(ln) => ln, None => break,
};
error_patterns.push_all_move(parse_expected(line_num, ln));
line_num += 1u;
}
@ -26,6 +30,7 @@ pub fn load_errors(testfile: &Path) -> ~[ExpectedError] {
}
fn parse_expected(line_num: uint, line: ~str) -> ~[ExpectedError] {
let line = line.trim();
let error_tag = ~"//~";
let mut idx;
match line.find_str(error_tag) {

View file

@ -12,8 +12,6 @@ use common::config;
use common;
use util;
use std::io;
pub struct TestProps {
// Lines that should be expected, in order, on standard out
error_patterns: ~[~str],
@ -104,17 +102,23 @@ pub fn is_test_ignored(config: &config, testfile: &Path) -> bool {
!val
}
fn iter_header(testfile: &Path, it: &fn(~str) -> bool) -> bool {
let rdr = io::file_reader(testfile).unwrap();
while !rdr.eof() {
let ln = rdr.read_line();
fn iter_header(testfile: &Path, it: &fn(&str) -> bool) -> bool {
use std::rt::io::Open;
use std::rt::io::file::FileInfo;
use std::rt::io::buffered::BufferedReader;
let mut rdr = BufferedReader::new(testfile.open_reader(Open).unwrap());
loop {
let ln = match rdr.read_line() {
Some(ln) => ln, None => break
};
// Assume that any directives will be found before the first
// module or function. This doesn't seem to be an optimization
// with a warm page cache. Maybe with a cold one.
if ln.starts_with("fn") || ln.starts_with("mod") {
return true;
} else { if !(it(ln)) { return false; } }
} else { if !(it(ln.trim())) { return false; } }
}
return true;
}

View file

@ -57,7 +57,7 @@ pub fn run(lib_path: &str,
});
for input in input.iter() {
proc.input().write_str(*input);
proc.input().write(input.as_bytes());
}
let output = proc.finish_with_output();

View file

@ -68,9 +68,6 @@ fn debug_mem() -> bool {
/// Destroys all managed memory (i.e. @ boxes) held by the current task.
pub unsafe fn annihilate() {
use rt::local_heap::local_free;
use io::WriterUtil;
use io;
use libc;
use sys;
use managed;
@ -126,14 +123,10 @@ pub unsafe fn annihilate() {
if debug_mem() {
// We do logging here w/o allocation.
let dbg = libc::STDERR_FILENO as io::fd_t;
dbg.write_str("annihilator stats:");
dbg.write_str("\n total_boxes: ");
dbg.write_uint(stats.n_total_boxes);
dbg.write_str("\n unique_boxes: ");
dbg.write_uint(stats.n_unique_boxes);
dbg.write_str("\n bytes_freed: ");
dbg.write_uint(stats.n_bytes_freed);
dbg.write_str("\n");
rterrln!("annihilator stats:\n \
total boxes: {}\n \
unique boxes: {}\n \
bytes freed: {}",
stats.n_total_boxes, stats.n_unique_boxes, stats.n_bytes_freed);
}
}

View file

@ -1233,14 +1233,6 @@ impl Writer for *libc::FILE {
}
}
pub fn FILE_writer(f: *libc::FILE, cleanup: bool) -> @Writer {
if cleanup {
@Wrapper { base: f, cleanup: FILERes::new(f) } as @Writer
} else {
@f as @Writer
}
}
impl Writer for fd_t {
fn write(&self, v: &[u8]) {
#[fixed_stack_segment]; #[inline(never)];
@ -1618,25 +1610,6 @@ pub fn file_writer(path: &Path, flags: &[FileFlag]) -> Result<@Writer, ~str> {
mk_file_writer(path, flags).and_then(|w| Ok(w))
}
// FIXME: fileflags // #2004
pub fn buffered_file_writer(path: &Path) -> Result<@Writer, ~str> {
#[fixed_stack_segment]; #[inline(never)];
unsafe {
let f = do path.with_c_str |pathbuf| {
do "w".with_c_str |modebuf| {
libc::fopen(pathbuf, modebuf)
}
};
return if f as uint == 0u {
Err(~"error opening " + path.to_str())
} else {
Ok(FILE_writer(f, true))
}
}
}
// FIXME (#2004) it would be great if this could be a const
// FIXME (#2004) why are these different from the way stdin() is
// implemented?
@ -2086,16 +2059,6 @@ mod tests {
}
}
#[test]
fn buffered_file_writer_bad_name() {
match io::buffered_file_writer(&Path("?/?")) {
Err(e) => {
assert!(e.starts_with("error opening"));
}
Ok(_) => fail2!()
}
}
#[test]
fn bytes_buffer_overwrite() {
let wr = BytesWriter::new();

View file

@ -13,7 +13,7 @@
macro_rules! rterrln (
($($arg:tt)*) => ( {
::rt::util::dumb_println(format!($($arg)*));
format_args!(::rt::util::dumb_println, $($arg)*)
} )
)

View file

@ -39,7 +39,7 @@ pub use option::{Option, Some, None};
pub use result::{Result, Ok, Err};
// Reexported functions
pub use io::{print, println};
pub use rt::io::stdio::{print, println};
pub use iter::range;
pub use from_str::from_str;

View file

@ -9,11 +9,8 @@
// except according to those terms.
use cell::Cell;
use c_str::ToCStr;
use cast::transmute;
use io::{Writer, WriterUtil};
use io;
use libc::{c_char, size_t, STDERR_FILENO};
use c_str::{ToCStr, CString};
use libc::{c_char, size_t};
use option::{Option, None, Some};
use ptr::RawPtr;
use rt::env;
@ -113,51 +110,10 @@ unsafe fn debug_borrow<T,P:RawPtr<T>>(tag: &'static str,
new_bits: uint,
filename: *c_char,
line: size_t) {
let dbg = STDERR_FILENO as io::fd_t;
dbg.write_str(tag);
dbg.write_hex(p.to_uint());
dbg.write_str(" ");
dbg.write_hex(old_bits);
dbg.write_str(" ");
dbg.write_hex(new_bits);
dbg.write_str(" ");
dbg.write_cstr(filename);
dbg.write_str(":");
dbg.write_hex(line as uint);
dbg.write_str("\n");
}
}
trait DebugPrints {
fn write_hex(&self, val: uint);
unsafe fn write_cstr(&self, str: *c_char);
}
impl DebugPrints for io::fd_t {
fn write_hex(&self, mut i: uint) {
let letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f'];
static UINT_NIBBLES: uint = ::uint::bytes << 1;
let mut buffer = [0_u8, ..UINT_NIBBLES+1];
let mut c = UINT_NIBBLES;
while c > 0 {
c -= 1;
buffer[c] = letters[i & 0xF] as u8;
i >>= 4;
}
self.write(buffer.slice(0, UINT_NIBBLES));
}
unsafe fn write_cstr(&self, p: *c_char) {
#[fixed_stack_segment]; #[inline(never)];
use libc::strlen;
use vec;
let len = strlen(p);
let p: *u8 = transmute(p);
do vec::raw::buf_as_slice(p, len as uint) |s| {
self.write(s);
}
let filename = CString::new(filename, false);
rterrln!("{}{:#x} {:x} {:x} {}:{}",
tag, p.to_uint(), old_bits, new_bits,
filename.as_str().unwrap(), line);
}
}

View file

@ -55,6 +55,7 @@ use prelude::*;
use num;
use vec;
use str;
use super::{Reader, Writer, Stream, Decorator};
// libuv recommends 64k buffers to maximize throughput
@ -84,23 +85,69 @@ impl<R: Reader> BufferedReader<R> {
pub fn new(inner: R) -> BufferedReader<R> {
BufferedReader::with_capacity(DEFAULT_CAPACITY, inner)
}
}
impl<R: Reader> Reader for BufferedReader<R> {
fn read(&mut self, buf: &mut [u8]) -> Option<uint> {
/// Reads the next line of input, interpreted as a sequence of utf-8
/// encoded unicode codepoints. If a newline is encountered, then the
/// newline is contained in the returned string.
pub fn read_line(&mut self) -> Option<~str> {
self.read_until('\n' as u8).map(str::from_utf8_owned)
}
/// Reads a sequence of bytes leading up to a specified delimeter. Once the
/// specified byte is encountered, reading ceases and the bytes up to and
/// including the delimiter are returned.
pub fn read_until(&mut self, byte: u8) -> Option<~[u8]> {
let mut res = ~[];
let mut used;
loop {
{
let available = self.fill_buffer();
match available.iter().position(|&b| b == byte) {
Some(i) => {
res.push_all(available.slice_to(i + 1));
used = i + 1;
break
}
None => {
res.push_all(available);
used = available.len();
}
}
}
if used == 0 {
break
}
self.pos += used;
}
self.pos += used;
return if res.len() == 0 {None} else {Some(res)};
}
fn fill_buffer<'a>(&'a mut self) -> &'a [u8] {
if self.pos == self.cap {
match self.inner.read(self.buf) {
Some(cap) => {
self.pos = 0;
self.cap = cap;
}
None => return None
None => {}
}
}
return self.buf.slice(self.pos, self.cap);
}
}
let src = self.buf.slice(self.pos, self.cap);
let nread = num::min(src.len(), buf.len());
vec::bytes::copy_memory(buf, src, nread);
impl<R: Reader> Reader for BufferedReader<R> {
fn read(&mut self, buf: &mut [u8]) -> Option<uint> {
let nread = {
let available = self.fill_buffer();
if available.len() == 0 {
return None;
}
let nread = num::min(available.len(), buf.len());
vec::bytes::copy_memory(buf, available, nread);
nread
};
self.pos += nread;
Some(nread)
}
@ -355,4 +402,15 @@ mod test {
stream.write(buf);
stream.flush();
}
#[test]
fn test_read_until() {
let inner = MemReader::new(~[0, 1, 2, 1, 0]);
let mut reader = BufferedReader::with_capacity(2, inner);
assert_eq!(reader.read_until(0), Some(~[0]));
assert_eq!(reader.read_until(2), Some(~[1, 2]));
assert_eq!(reader.read_until(1), Some(~[1]));
assert_eq!(reader.read_until(8), Some(~[0]));
assert_eq!(reader.read_until(9), None);
}
}

View file

@ -599,7 +599,7 @@ impl FileInfo for Path { }
/// else { fail2!("nope"); }
/// }
/// ```
trait DirectoryInfo : FileSystemInfo {
pub trait DirectoryInfo : FileSystemInfo {
/// Whether the underlying implemention (be it a file path,
/// or something else) is pointing at a directory in the underlying FS.
/// Will return false for paths to non-existent locations or if the item is

View file

@ -313,8 +313,11 @@ pub mod buffered;
pub mod native {
/// Posix file I/O
pub mod file;
/// # XXX - implement this
pub mod stdio { }
/// Process spawning and child management
pub mod process;
/// Posix stdio
pub mod stdio;
/// Sockets
/// # XXX - implement this
pub mod net {
@ -459,6 +462,16 @@ pub trait Reader {
fn eof(&mut self) -> bool;
}
impl Reader for ~Reader {
fn read(&mut self, buf: &mut [u8]) -> Option<uint> { self.read(buf) }
fn eof(&mut self) -> bool { self.eof() }
}
impl<'self> Reader for &'self mut Reader {
fn read(&mut self, buf: &mut [u8]) -> Option<uint> { self.read(buf) }
fn eof(&mut self) -> bool { self.eof() }
}
pub trait Writer {
/// Write the given buffer
///
@ -471,6 +484,16 @@ pub trait Writer {
fn flush(&mut self);
}
impl Writer for ~Writer {
fn write(&mut self, buf: &[u8]) { self.write(buf) }
fn flush(&mut self) { self.flush() }
}
impl<'self> Writer for &'self mut Writer {
fn write(&mut self, buf: &[u8]) { self.write(buf) }
fn flush(&mut self) { self.flush() }
}
pub trait Stream: Reader + Writer { }
impl<T: Reader + Writer> Stream for T {}

View file

@ -10,68 +10,274 @@
//! Blocking posix-based file I/O
#[allow(non_camel_case_types)];
use libc;
use os;
use prelude::*;
use super::super::*;
use libc::{c_int, FILE};
#[allow(non_camel_case_types)]
pub type fd_t = c_int;
fn raise_error() {
// XXX: this should probably be a bit more descriptive...
let (kind, desc) = match os::errno() as i32 {
libc::EOF => (EndOfFile, "end of file"),
_ => (OtherIoError, "unknown error"),
};
io_error::cond.raise(IoError {
kind: kind,
desc: desc,
detail: Some(os::last_os_error())
});
}
fn keep_going(data: &[u8], f: &fn(*u8, uint) -> i64) -> i64 {
#[cfg(windows)] static eintr: int = 0; // doesn't matter
#[cfg(not(windows))] static eintr: int = libc::EINTR as int;
let (data, origamt) = do data.as_imm_buf |data, amt| { (data, amt) };
let mut data = data;
let mut amt = origamt;
while amt > 0 {
let mut ret;
loop {
ret = f(data, amt);
if cfg!(not(windows)) { break } // windows has no eintr
// if we get an eintr, then try again
if ret != -1 || os::errno() as int != eintr { break }
}
if ret == 0 {
break
} else if ret != -1 {
amt -= ret as uint;
data = unsafe { data.offset(ret as int) };
} else {
return ret;
}
}
return (origamt - amt) as i64;
}
pub type fd_t = libc::c_int;
pub struct FileDesc {
priv fd: fd_t
priv fd: fd_t,
}
impl FileDesc {
/// Create a `FileDesc` from an open C file descriptor.
///
/// The `FileDesc` takes ownership of the file descriptor
/// and will close it upon destruction.
pub fn new(_fd: fd_t) -> FileDesc { fail2!() }
/// The `FileDesc` will take ownership of the specified file descriptor and
/// close it upon destruction.
///
/// Note that all I/O operations done on this object will be *blocking*, but
/// they do not require the runtime to be active.
pub fn new(fd: fd_t) -> FileDesc {
FileDesc { fd: fd }
}
}
impl Reader for FileDesc {
fn read(&mut self, _buf: &mut [u8]) -> Option<uint> { fail2!() }
#[fixed_stack_segment] #[inline(never)]
fn read(&mut self, buf: &mut [u8]) -> Option<uint> {
#[cfg(windows)] type rlen = libc::c_uint;
#[cfg(not(windows))] type rlen = libc::size_t;
let ret = do keep_going(buf) |buf, len| {
unsafe {
libc::read(self.fd, buf as *mut libc::c_void, len as rlen) as i64
}
};
if ret == 0 {
None
} else if ret < 0 {
raise_error();
None
} else {
Some(ret as uint)
}
}
fn eof(&mut self) -> bool { fail2!() }
fn eof(&mut self) -> bool { false }
}
impl Writer for FileDesc {
fn write(&mut self, _buf: &[u8]) { fail2!() }
#[fixed_stack_segment] #[inline(never)]
fn write(&mut self, buf: &[u8]) {
#[cfg(windows)] type wlen = libc::c_uint;
#[cfg(not(windows))] type wlen = libc::size_t;
let ret = do keep_going(buf) |buf, len| {
unsafe {
libc::write(self.fd, buf as *libc::c_void, len as wlen) as i64
}
};
if ret < 0 {
raise_error();
}
}
fn flush(&mut self) { fail2!() }
fn flush(&mut self) {}
}
impl Seek for FileDesc {
fn tell(&self) -> u64 { fail2!() }
fn seek(&mut self, _pos: i64, _style: SeekStyle) { fail2!() }
impl Drop for FileDesc {
#[fixed_stack_segment] #[inline(never)]
fn drop(&mut self) {
unsafe { libc::close(self.fd); }
}
}
pub struct CFile {
priv file: *FILE
priv file: *libc::FILE
}
impl CFile {
/// Create a `CFile` from an open `FILE` pointer.
///
/// The `CFile` takes ownership of the file descriptor
/// and will close it upon destruction.
pub fn new(_file: *FILE) -> CFile { fail2!() }
/// The `CFile` takes ownership of the `FILE` pointer and will close it upon
/// destruction.
pub fn new(file: *libc::FILE) -> CFile { CFile { file: file } }
}
impl Reader for CFile {
fn read(&mut self, _buf: &mut [u8]) -> Option<uint> { fail2!() }
#[fixed_stack_segment] #[inline(never)]
fn read(&mut self, buf: &mut [u8]) -> Option<uint> {
let ret = do keep_going(buf) |buf, len| {
unsafe {
libc::fread(buf as *mut libc::c_void, 1, len as libc::size_t,
self.file) as i64
}
};
if ret == 0 {
None
} else if ret < 0 {
raise_error();
None
} else {
Some(ret as uint)
}
}
fn eof(&mut self) -> bool { fail2!() }
#[fixed_stack_segment] #[inline(never)]
fn eof(&mut self) -> bool {
unsafe { libc::feof(self.file) != 0 }
}
}
impl Writer for CFile {
fn write(&mut self, _buf: &[u8]) { fail2!() }
#[fixed_stack_segment] #[inline(never)]
fn write(&mut self, buf: &[u8]) {
let ret = do keep_going(buf) |buf, len| {
unsafe {
libc::fwrite(buf as *libc::c_void, 1, len as libc::size_t,
self.file) as i64
}
};
if ret < 0 {
raise_error();
}
}
fn flush(&mut self) { fail2!() }
#[fixed_stack_segment] #[inline(never)]
fn flush(&mut self) {
if unsafe { libc::fflush(self.file) } < 0 {
raise_error();
}
}
}
impl Seek for CFile {
fn tell(&self) -> u64 { fail2!() }
fn seek(&mut self, _pos: i64, _style: SeekStyle) { fail2!() }
#[fixed_stack_segment] #[inline(never)]
fn tell(&self) -> u64 {
let ret = unsafe { libc::ftell(self.file) };
if ret < 0 {
raise_error();
}
return ret as u64;
}
#[fixed_stack_segment] #[inline(never)]
fn seek(&mut self, pos: i64, style: SeekStyle) {
let whence = match style {
SeekSet => libc::SEEK_SET,
SeekEnd => libc::SEEK_END,
SeekCur => libc::SEEK_CUR,
};
if unsafe { libc::fseek(self.file, pos as libc::c_long, whence) } < 0 {
raise_error();
}
}
}
impl Drop for CFile {
#[fixed_stack_segment] #[inline(never)]
fn drop(&mut self) {
unsafe { libc::fclose(self.file); }
}
}
#[cfg(test)]
mod tests {
use libc;
use os;
use prelude::*;
use rt::io::{io_error, SeekSet};
use super::*;
#[test] #[fixed_stack_segment]
#[ignore(cfg(target_os = "freebsd"))] // hmm, maybe pipes have a tiny buffer
fn test_file_desc() {
// Run this test with some pipes so we don't have to mess around with
// opening or closing files.
unsafe {
let os::Pipe { input, out } = os::pipe();
let mut reader = FileDesc::new(input);
let mut writer = FileDesc::new(out);
writer.write(bytes!("test"));
let mut buf = [0u8, ..4];
match reader.read(buf) {
Some(4) => {
assert_eq!(buf[0], 't' as u8);
assert_eq!(buf[1], 'e' as u8);
assert_eq!(buf[2], 's' as u8);
assert_eq!(buf[3], 't' as u8);
}
r => fail2!("invalid read: {:?}", r)
}
let mut raised = false;
do io_error::cond.trap(|_| { raised = true; }).inside {
writer.read(buf);
}
assert!(raised);
raised = false;
do io_error::cond.trap(|_| { raised = true; }).inside {
reader.write(buf);
}
assert!(raised);
}
}
#[test] #[fixed_stack_segment]
#[ignore(cfg(windows))] // apparently windows doesn't like tmpfile
fn test_cfile() {
unsafe {
let f = libc::tmpfile();
assert!(!f.is_null());
let mut file = CFile::new(f);
file.write(bytes!("test"));
let mut buf = [0u8, ..4];
file.seek(0, SeekSet);
match file.read(buf) {
Some(4) => {
assert_eq!(buf[0], 't' as u8);
assert_eq!(buf[1], 'e' as u8);
assert_eq!(buf[2], 's' as u8);
assert_eq!(buf[3], 't' as u8);
}
r => fail2!("invalid read: {:?}", r)
}
}
}
}

View file

@ -0,0 +1,745 @@
// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use cast;
use libc::{pid_t, c_void, c_int};
use libc;
use os;
use prelude::*;
use ptr;
use rt::io;
use super::file;
/**
* A value representing a child process.
*
* The lifetime of this value is linked to the lifetime of the actual
* process - the Process destructor calls self.finish() which waits
* for the process to terminate.
*/
pub struct Process {
/// The unique id of the process (this should never be negative).
priv pid: pid_t,
/// A handle to the process - on unix this will always be NULL, but on
/// windows it will be a HANDLE to the process, which will prevent the
/// pid being re-used until the handle is closed.
priv handle: *(),
/// Currently known stdin of the child, if any
priv input: Option<file::FileDesc>,
/// Currently known stdout of the child, if any
priv output: Option<file::FileDesc>,
/// Currently known stderr of the child, if any
priv error: Option<file::FileDesc>,
/// None until finish() is called.
priv exit_code: Option<int>,
}
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 envrionment 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 new(prog: &str, args: &[~str], env: Option<~[(~str, ~str)]>,
cwd: Option<&Path>,
stdin: Option<file::fd_t>,
stdout: Option<file::fd_t>,
stderr: Option<file::fd_t>) -> Process {
#[fixed_stack_segment]; #[inline(never)];
let (in_pipe, in_fd) = match stdin {
None => {
let pipe = os::pipe();
(Some(pipe), pipe.input)
},
Some(fd) => (None, fd)
};
let (out_pipe, out_fd) = match stdout {
None => {
let pipe = os::pipe();
(Some(pipe), pipe.out)
},
Some(fd) => (None, fd)
};
let (err_pipe, err_fd) = match stderr {
None => {
let pipe = os::pipe();
(Some(pipe), pipe.out)
},
Some(fd) => (None, fd)
};
let res = spawn_process_os(prog, args, env, cwd,
in_fd, out_fd, err_fd);
unsafe {
for pipe in in_pipe.iter() { libc::close(pipe.input); }
for pipe in out_pipe.iter() { libc::close(pipe.out); }
for pipe in err_pipe.iter() { libc::close(pipe.out); }
}
Process {
pid: res.pid,
handle: res.handle,
input: in_pipe.map(|pipe| file::FileDesc::new(pipe.out)),
output: out_pipe.map(|pipe| file::FileDesc::new(pipe.input)),
error: err_pipe.map(|pipe| file::FileDesc::new(pipe.input)),
exit_code: None,
}
}
/// Returns the unique id of the process
pub fn id(&self) -> pid_t { self.pid }
/**
* Returns an io::Writer that can be used to write to this Process's stdin.
*
* Fails if there is no stdinavailable (it's already been removed by
* take_input)
*/
pub fn input<'a>(&'a mut self) -> &'a mut io::Writer {
match self.input {
Some(ref mut fd) => fd as &mut io::Writer,
None => fail2!("This process has no stdin")
}
}
/**
* Returns an io::Reader that can be used to read from this Process's
* stdout.
*
* Fails if there is no stdin available (it's already been removed by
* take_output)
*/
pub fn output<'a>(&'a mut self) -> &'a mut io::Reader {
match self.input {
Some(ref mut fd) => fd as &mut io::Reader,
None => fail2!("This process has no stdout")
}
}
/**
* Returns an io::Reader that can be used to read from this Process's
* stderr.
*
* Fails if there is no stdin available (it's already been removed by
* take_error)
*/
pub fn error<'a>(&'a mut self) -> &'a mut io::Reader {
match self.error {
Some(ref mut fd) => fd as &mut io::Reader,
None => fail2!("This process has no stderr")
}
}
/**
* Takes the stdin of this process, transferring ownership to the caller.
* Note that when the return value is destroyed, the handle will be closed
* for the child process.
*/
pub fn take_input(&mut self) -> Option<~io::Writer> {
self.input.take().map(|fd| ~fd as ~io::Writer)
}
/**
* Takes the stdout of this process, transferring ownership to the caller.
* Note that when the return value is destroyed, the handle will be closed
* for the child process.
*/
pub fn take_output(&mut self) -> Option<~io::Reader> {
self.output.take().map(|fd| ~fd as ~io::Reader)
}
/**
* Takes the stderr of this process, transferring ownership to the caller.
* Note that when the return value is destroyed, the handle will be closed
* for the child process.
*/
pub fn take_error(&mut self) -> Option<~io::Reader> {
self.error.take().map(|fd| ~fd as ~io::Reader)
}
pub fn wait(&mut self) -> int {
for &code in self.exit_code.iter() {
return code;
}
let code = waitpid(self.pid);
self.exit_code = Some(code);
return code;
}
pub fn signal(&mut self, signum: int) -> Result<(), io::IoError> {
// if the process has finished, and therefore had waitpid called,
// and we kill it, then on unix we might ending up killing a
// newer process that happens to have the same (re-used) id
match self.exit_code {
Some(*) => return Err(io::IoError {
kind: io::OtherIoError,
desc: "can't kill an exited process",
detail: None,
}),
None => {}
}
return unsafe { killpid(self.pid, signum) };
#[cfg(windows)]
unsafe fn killpid(pid: pid_t, signal: int) -> Result<(), io::IoError> {
#[fixed_stack_segment]; #[inline(never)];
match signal {
io::process::PleaseExitSignal |
io::process::MustDieSignal => {
libc::funcs::extra::kernel32::TerminateProcess(
cast::transmute(pid), 1);
Ok(())
}
_ => Err(io::IoError {
kind: io::OtherIoError,
desc: "unsupported signal on windows",
detail: None,
})
}
}
#[cfg(not(windows))]
unsafe fn killpid(pid: pid_t, signal: int) -> Result<(), io::IoError> {
#[fixed_stack_segment]; #[inline(never)];
libc::funcs::posix88::signal::kill(pid, signal as c_int);
Ok(())
}
}
}
impl Drop for Process {
fn drop(&mut self) {
// close all these handles
self.take_input();
self.take_output();
self.take_error();
self.wait();
free_handle(self.handle);
}
}
struct SpawnProcessResult {
pid: pid_t,
handle: *(),
}
#[cfg(windows)]
fn spawn_process_os(prog: &str, args: &[~str],
env: Option<~[(~str, ~str)]>,
dir: Option<&Path>,
in_fd: c_int, out_fd: c_int, err_fd: c_int) -> SpawnProcessResult {
#[fixed_stack_segment]; #[inline(never)];
use libc::types::os::arch::extra::{DWORD, HANDLE, STARTUPINFO};
use libc::consts::os::extra::{
TRUE, FALSE,
STARTF_USESTDHANDLES,
INVALID_HANDLE_VALUE,
DUPLICATE_SAME_ACCESS
};
use libc::funcs::extra::kernel32::{
GetCurrentProcess,
DuplicateHandle,
CloseHandle,
CreateProcessA
};
use libc::funcs::extra::msvcrt::get_osfhandle;
use sys;
unsafe {
let mut si = zeroed_startupinfo();
si.cb = sys::size_of::<STARTUPINFO>() as DWORD;
si.dwFlags = STARTF_USESTDHANDLES;
let cur_proc = GetCurrentProcess();
let orig_std_in = get_osfhandle(in_fd) as HANDLE;
if orig_std_in == INVALID_HANDLE_VALUE as HANDLE {
fail2!("failure in get_osfhandle: {}", os::last_os_error());
}
if DuplicateHandle(cur_proc, orig_std_in, cur_proc, &mut si.hStdInput,
0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE {
fail2!("failure in DuplicateHandle: {}", os::last_os_error());
}
let orig_std_out = get_osfhandle(out_fd) as HANDLE;
if orig_std_out == INVALID_HANDLE_VALUE as HANDLE {
fail2!("failure in get_osfhandle: {}", os::last_os_error());
}
if DuplicateHandle(cur_proc, orig_std_out, cur_proc, &mut si.hStdOutput,
0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE {
fail2!("failure in DuplicateHandle: {}", os::last_os_error());
}
let orig_std_err = get_osfhandle(err_fd) as HANDLE;
if orig_std_err == INVALID_HANDLE_VALUE as HANDLE {
fail2!("failure in get_osfhandle: {}", os::last_os_error());
}
if DuplicateHandle(cur_proc, orig_std_err, cur_proc, &mut si.hStdError,
0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE {
fail2!("failure in DuplicateHandle: {}", os::last_os_error());
}
let cmd = make_command_line(prog, args);
let mut pi = zeroed_process_information();
let mut create_err = None;
do with_envp(env) |envp| {
do with_dirp(dir) |dirp| {
do cmd.with_c_str |cmdp| {
let created = CreateProcessA(ptr::null(), cast::transmute(cmdp),
ptr::mut_null(), ptr::mut_null(), TRUE,
0, envp, dirp, &mut si, &mut pi);
if created == FALSE {
create_err = Some(os::last_os_error());
}
}
}
}
CloseHandle(si.hStdInput);
CloseHandle(si.hStdOutput);
CloseHandle(si.hStdError);
for msg in create_err.iter() {
fail2!("failure in CreateProcess: {}", *msg);
}
// We close the thread handle because we don't care about keeping the
// thread id valid, and we aren't keeping the thread handle around to be
// able to close it later. We don't close the process handle however
// because we want the process id to stay valid at least until the
// calling code closes the process handle.
CloseHandle(pi.hThread);
SpawnProcessResult {
pid: pi.dwProcessId as pid_t,
handle: pi.hProcess as *()
}
}
}
#[cfg(windows)]
fn zeroed_startupinfo() -> libc::types::os::arch::extra::STARTUPINFO {
libc::types::os::arch::extra::STARTUPINFO {
cb: 0,
lpReserved: ptr::mut_null(),
lpDesktop: ptr::mut_null(),
lpTitle: ptr::mut_null(),
dwX: 0,
dwY: 0,
dwXSize: 0,
dwYSize: 0,
dwXCountChars: 0,
dwYCountCharts: 0,
dwFillAttribute: 0,
dwFlags: 0,
wShowWindow: 0,
cbReserved2: 0,
lpReserved2: ptr::mut_null(),
hStdInput: ptr::mut_null(),
hStdOutput: ptr::mut_null(),
hStdError: ptr::mut_null()
}
}
#[cfg(windows)]
fn zeroed_process_information() -> libc::types::os::arch::extra::PROCESS_INFORMATION {
libc::types::os::arch::extra::PROCESS_INFORMATION {
hProcess: ptr::mut_null(),
hThread: ptr::mut_null(),
dwProcessId: 0,
dwThreadId: 0
}
}
// FIXME: this is only pub so it can be tested (see issue #4536)
#[cfg(windows)]
pub fn make_command_line(prog: &str, args: &[~str]) -> ~str {
let mut cmd = ~"";
append_arg(&mut cmd, prog);
for arg in args.iter() {
cmd.push_char(' ');
append_arg(&mut cmd, *arg);
}
return cmd;
fn append_arg(cmd: &mut ~str, arg: &str) {
let quote = arg.iter().any(|c| c == ' ' || c == '\t');
if quote {
cmd.push_char('"');
}
for i in range(0u, arg.len()) {
append_char_at(cmd, arg, i);
}
if quote {
cmd.push_char('"');
}
}
fn append_char_at(cmd: &mut ~str, arg: &str, i: uint) {
match arg[i] as char {
'"' => {
// Escape quotes.
cmd.push_str("\\\"");
}
'\\' => {
if backslash_run_ends_in_quote(arg, i) {
// Double all backslashes that are in runs before quotes.
cmd.push_str("\\\\");
} else {
// Pass other backslashes through unescaped.
cmd.push_char('\\');
}
}
c => {
cmd.push_char(c);
}
}
}
fn backslash_run_ends_in_quote(s: &str, mut i: uint) -> bool {
while i < s.len() && s[i] as char == '\\' {
i += 1;
}
return i < s.len() && s[i] as char == '"';
}
}
#[cfg(unix)]
fn spawn_process_os(prog: &str, args: &[~str],
env: Option<~[(~str, ~str)]>,
dir: Option<&Path>,
in_fd: c_int, out_fd: c_int, err_fd: c_int) -> SpawnProcessResult {
#[fixed_stack_segment]; #[inline(never)];
use libc::funcs::posix88::unistd::{fork, dup2, close, chdir, execvp};
use libc::funcs::bsd44::getdtablesize;
mod rustrt {
#[abi = "cdecl"]
extern {
pub fn rust_unset_sigprocmask();
}
}
#[cfg(windows)]
unsafe fn set_environ(_envp: *c_void) {}
#[cfg(target_os = "macos")]
unsafe fn set_environ(envp: *c_void) {
externfn!(fn _NSGetEnviron() -> *mut *c_void);
*_NSGetEnviron() = envp;
}
#[cfg(not(target_os = "macos"), not(windows))]
unsafe fn set_environ(envp: *c_void) {
extern {
static mut environ: *c_void;
}
environ = envp;
}
unsafe {
let pid = fork();
if pid < 0 {
fail2!("failure in fork: {}", os::last_os_error());
} else if pid > 0 {
return SpawnProcessResult {pid: pid, handle: ptr::null()};
}
rustrt::rust_unset_sigprocmask();
if dup2(in_fd, 0) == -1 {
fail2!("failure in dup2(in_fd, 0): {}", os::last_os_error());
}
if dup2(out_fd, 1) == -1 {
fail2!("failure in dup2(out_fd, 1): {}", os::last_os_error());
}
if dup2(err_fd, 2) == -1 {
fail2!("failure in dup3(err_fd, 2): {}", os::last_os_error());
}
// close all other fds
for fd in range(3, getdtablesize()).invert() {
close(fd as c_int);
}
do with_dirp(dir) |dirp| {
if !dirp.is_null() && chdir(dirp) == -1 {
fail2!("failure in chdir: {}", os::last_os_error());
}
}
do with_envp(env) |envp| {
if !envp.is_null() {
set_environ(envp);
}
do with_argv(prog, args) |argv| {
execvp(*argv, argv);
// execvp only returns if an error occurred
fail2!("failure in execvp: {}", os::last_os_error());
}
}
}
}
#[cfg(unix)]
fn with_argv<T>(prog: &str, args: &[~str], cb: &fn(**libc::c_char) -> T) -> T {
use vec;
// We can't directly convert `str`s into `*char`s, as someone needs to hold
// a reference to the intermediary byte buffers. So first build an array to
// hold all the ~[u8] byte strings.
let mut tmps = vec::with_capacity(args.len() + 1);
tmps.push(prog.to_c_str());
for arg in args.iter() {
tmps.push(arg.to_c_str());
}
// Next, convert each of the byte strings into a pointer. This is
// technically unsafe as the caller could leak these pointers out of our
// scope.
let mut ptrs = do tmps.map |tmp| {
tmp.with_ref(|buf| buf)
};
// Finally, make sure we add a null pointer.
ptrs.push(ptr::null());
ptrs.as_imm_buf(|buf, _| cb(buf))
}
#[cfg(unix)]
fn with_envp<T>(env: Option<~[(~str, ~str)]>, cb: &fn(*c_void) -> T) -> T {
use vec;
// 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.
match env {
Some(env) => {
let mut tmps = vec::with_capacity(env.len());
for pair in env.iter() {
let kv = format!("{}={}", pair.first(), pair.second());
tmps.push(kv.to_c_str());
}
// Once again, this is unsafe.
let mut ptrs = do tmps.map |tmp| {
tmp.with_ref(|buf| buf)
};
ptrs.push(ptr::null());
do ptrs.as_imm_buf |buf, _| {
unsafe { cb(cast::transmute(buf)) }
}
}
_ => cb(ptr::null())
}
}
#[cfg(windows)]
fn with_envp<T>(env: Option<~[(~str, ~str)]>, cb: &fn(*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.
match env {
Some(env) => {
let mut blk = ~[];
for pair in env.iter() {
let kv = format!("{}={}", pair.first(), pair.second());
blk.push_all(kv.as_bytes());
blk.push(0);
}
blk.push(0);
do blk.as_imm_buf |p, _len| {
unsafe { cb(cast::transmute(p)) }
}
}
_ => cb(ptr::mut_null())
}
}
fn with_dirp<T>(d: Option<&Path>, cb: &fn(*libc::c_char) -> T) -> T {
match d {
Some(dir) => dir.with_c_str(|buf| cb(buf)),
None => cb(ptr::null())
}
}
#[cfg(windows)]
fn free_handle(handle: *()) {
#[fixed_stack_segment]; #[inline(never)];
unsafe {
libc::funcs::extra::kernel32::CloseHandle(cast::transmute(handle));
}
}
#[cfg(unix)]
fn free_handle(_handle: *()) {
// unix has no process handle object, just a pid
}
/**
* Waits for a process to exit and returns the exit code, failing
* if there is no process with the specified id.
*
* Note that this is private to avoid race conditions on unix where if
* a user calls waitpid(some_process.get_id()) then some_process.finish()
* and some_process.destroy() and some_process.finalize() will then either
* operate on a none-existent process or, even worse, on a newer process
* with the same id.
*/
fn waitpid(pid: pid_t) -> int {
return waitpid_os(pid);
#[cfg(windows)]
fn waitpid_os(pid: pid_t) -> int {
#[fixed_stack_segment]; #[inline(never)];
use libc::types::os::arch::extra::DWORD;
use libc::consts::os::extra::{
SYNCHRONIZE,
PROCESS_QUERY_INFORMATION,
FALSE,
STILL_ACTIVE,
INFINITE,
WAIT_FAILED
};
use libc::funcs::extra::kernel32::{
OpenProcess,
GetExitCodeProcess,
CloseHandle,
WaitForSingleObject
};
unsafe {
let proc = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, FALSE, pid as DWORD);
if proc.is_null() {
fail2!("failure in OpenProcess: {}", os::last_os_error());
}
loop {
let mut status = 0;
if GetExitCodeProcess(proc, &mut status) == FALSE {
CloseHandle(proc);
fail2!("failure in GetExitCodeProcess: {}", os::last_os_error());
}
if status != STILL_ACTIVE {
CloseHandle(proc);
return status as int;
}
if WaitForSingleObject(proc, INFINITE) == WAIT_FAILED {
CloseHandle(proc);
fail2!("failure in WaitForSingleObject: {}", os::last_os_error());
}
}
}
}
#[cfg(unix)]
fn waitpid_os(pid: pid_t) -> int {
#[fixed_stack_segment]; #[inline(never)];
use libc::funcs::posix01::wait::*;
#[cfg(target_os = "linux")]
#[cfg(target_os = "android")]
fn WIFEXITED(status: i32) -> bool {
(status & 0xffi32) == 0i32
}
#[cfg(target_os = "macos")]
#[cfg(target_os = "freebsd")]
fn WIFEXITED(status: i32) -> bool {
(status & 0x7fi32) == 0i32
}
#[cfg(target_os = "linux")]
#[cfg(target_os = "android")]
fn WEXITSTATUS(status: i32) -> i32 {
(status >> 8i32) & 0xffi32
}
#[cfg(target_os = "macos")]
#[cfg(target_os = "freebsd")]
fn WEXITSTATUS(status: i32) -> i32 {
status >> 8i32
}
let mut status = 0 as c_int;
if unsafe { waitpid(pid, &mut status, 0) } == -1 {
fail2!("failure in waitpid: {}", os::last_os_error());
}
return if WIFEXITED(status) {
WEXITSTATUS(status) as int
} else {
1
};
}
}
#[cfg(test)]
mod tests {
#[test] #[cfg(windows)]
fn test_make_command_line() {
use super::make_command_line;
assert_eq!(
make_command_line("prog", [~"aaa", ~"bbb", ~"ccc"]),
~"prog aaa bbb ccc"
);
assert_eq!(
make_command_line("C:\\Program Files\\blah\\blah.exe", [~"aaa"]),
~"\"C:\\Program Files\\blah\\blah.exe\" aaa"
);
assert_eq!(
make_command_line("C:\\Program Files\\test", [~"aa\"bb"]),
~"\"C:\\Program Files\\test\" aa\\\"bb"
);
assert_eq!(
make_command_line("echo", [~"a b c"]),
~"echo \"a b c\""
);
}
// Currently most of the tests of this functionality live inside std::run,
// but they may move here eventually as a non-blocking backend is added to
// std::run
}

View file

@ -0,0 +1,67 @@
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use libc;
use option::Option;
use rt::io::{Reader, Writer};
use super::file;
/// Creates a new handle to the stdin of this process
pub fn stdin() -> StdIn { StdIn::new() }
/// Creates a new handle to the stdout of this process
pub fn stdout() -> StdOut { StdOut::new(libc::STDOUT_FILENO) }
/// Creates a new handle to the stderr of this process
pub fn stderr() -> StdOut { StdOut::new(libc::STDERR_FILENO) }
pub fn print(s: &str) {
stdout().write(s.as_bytes())
}
pub fn println(s: &str) {
let mut out = stdout();
out.write(s.as_bytes());
out.write(['\n' as u8]);
}
pub struct StdIn {
priv fd: file::FileDesc
}
impl StdIn {
/// Duplicates the stdin file descriptor, returning an io::Reader
#[fixed_stack_segment] #[inline(never)]
pub fn new() -> StdIn {
let fd = unsafe { libc::dup(libc::STDIN_FILENO) };
StdIn { fd: file::FileDesc::new(fd) }
}
}
impl Reader for StdIn {
fn read(&mut self, buf: &mut [u8]) -> Option<uint> { self.fd.read(buf) }
fn eof(&mut self) -> bool { self.fd.eof() }
}
pub struct StdOut {
priv fd: file::FileDesc
}
impl StdOut {
/// Duplicates the specified file descriptor, returning an io::Writer
#[fixed_stack_segment] #[inline(never)]
pub fn new(fd: file::fd_t) -> StdOut {
let fd = unsafe { libc::dup(fd) };
StdOut { fd: file::FileDesc::new(fd) }
}
}
impl Writer for StdOut {
fn write(&mut self, buf: &[u8]) { self.fd.write(buf) }
fn flush(&mut self) { self.fd.flush() }
}

View file

@ -18,6 +18,13 @@ use rt::io::io_error;
use rt::local::Local;
use rt::rtio::{RtioProcess, RtioProcessObject, IoFactoryObject, IoFactory};
// windows values don't matter as long as they're at least one of unix's
// TERM/KILL/INT signals
#[cfg(windows)] pub static PleaseExitSignal: int = 15;
#[cfg(windows)] pub static MustDieSignal: int = 9;
#[cfg(not(windows))] pub static PleaseExitSignal: int = libc::SIGTERM as int;
#[cfg(not(windows))] pub static MustDieSignal: int = libc::SIGKILL as int;
pub struct Process {
priv handle: ~RtioProcessObject,
io: ~[Option<io::PipeStream>],

View file

@ -8,45 +8,102 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use prelude::*;
use super::{Reader, Writer};
use libc;
use option::{Option, Some, None};
use result::{Ok, Err};
use rt::local::Local;
use rt::rtio::{RtioFileStream, IoFactoryObject, IoFactory};
use super::{Reader, Writer, io_error};
pub fn stdin() -> StdReader { fail2!() }
pub fn stdout() -> StdWriter { fail2!() }
pub fn stderr() -> StdReader { fail2!() }
pub fn print(_s: &str) { fail2!() }
pub fn println(_s: &str) { fail2!() }
pub enum StdStream {
StdIn,
StdOut,
StdErr
/// Creates a new non-blocking handle to the stdin of the current process.
///
/// See `stdout()` for notes about this function.
pub fn stdin() -> StdReader {
let stream = unsafe {
let io: *mut IoFactoryObject = Local::unsafe_borrow();
(*io).fs_from_raw_fd(libc::STDIN_FILENO, false)
};
StdReader { inner: stream }
}
pub struct StdReader;
/// Creates a new non-blocking handle to the stdout of the current process.
///
/// Note that this is a fairly expensive operation in that at least one memory
/// allocation is performed. Additionally, this must be called from a runtime
/// task context because the stream returned will be a non-blocking object using
/// the local scheduler to perform the I/O.
pub fn stdout() -> StdWriter {
let stream = unsafe {
let io: *mut IoFactoryObject = Local::unsafe_borrow();
(*io).fs_from_raw_fd(libc::STDOUT_FILENO, false)
};
StdWriter { inner: stream }
}
impl StdReader {
pub fn new(_stream: StdStream) -> StdReader { fail2!() }
/// Creates a new non-blocking handle to the stderr of the current process.
///
/// See `stdout()` for notes about this function.
pub fn stderr() -> StdWriter {
let stream = unsafe {
let io: *mut IoFactoryObject = Local::unsafe_borrow();
(*io).fs_from_raw_fd(libc::STDERR_FILENO, false)
};
StdWriter { inner: stream }
}
/// Prints a string to the stdout of the current process. No newline is emitted
/// after the string is printed.
pub fn print(s: &str) {
// XXX: need to see if not caching stdin() is the cause of performance
// issues, it should be possible to cache a stdout handle in each Task
// and then re-use that across calls to print/println
stdout().write(s.as_bytes());
}
/// Prints a string as a line. to the stdout of the current process. A literal
/// `\n` character is printed to the console after the string.
pub fn println(s: &str) {
let mut out = stdout();
out.write(s.as_bytes());
out.write(['\n' as u8]);
}
/// Representation of a reader of a standard input stream
pub struct StdReader {
priv inner: ~RtioFileStream
}
impl Reader for StdReader {
fn read(&mut self, _buf: &mut [u8]) -> Option<uint> { fail2!() }
fn read(&mut self, buf: &mut [u8]) -> Option<uint> {
match self.inner.read(buf) {
Ok(amt) => Some(amt as uint),
Err(e) => {
io_error::cond.raise(e);
None
}
}
}
fn eof(&mut self) -> bool { fail2!() }
fn eof(&mut self) -> bool { false }
}
pub struct StdWriter;
impl StdWriter {
pub fn new(_stream: StdStream) -> StdWriter { fail2!() }
/// Representation of a writer to a standard output stream
pub struct StdWriter {
priv inner: ~RtioFileStream
}
impl Writer for StdWriter {
fn write(&mut self, _buf: &[u8]) { fail2!() }
fn write(&mut self, buf: &[u8]) {
match self.inner.write(buf) {
Ok(()) => {}
Err(e) => io_error::cond.raise(e)
}
}
fn flush(&mut self) { fail2!() }
fn flush(&mut self) {
match self.inner.flush() {
Ok(()) => {}
Err(e) => io_error::cond.raise(e)
}
}
}

View file

@ -12,8 +12,6 @@ use fmt;
use from_str::from_str;
use libc::exit;
use option::{Some, None, Option};
use rt;
use rt::util::dumb_println;
use rt::crate_map::{ModEntry, CrateMap, iter_crate_map, get_crate_map};
use str::StrSlice;
use u32;
@ -88,16 +86,16 @@ fn parse_logging_spec(spec: ~str) -> ~[LogDirective]{
log_level = num;
},
_ => {
dumb_println(format!("warning: invalid logging spec \
'{}', ignoring it", parts[1]));
continue;
rterrln!("warning: invalid logging spec '{}', \
ignoring it", parts[1]);
continue
}
}
},
_ => {
dumb_println(format!("warning: invalid logging spec '{}',\
ignoring it", s));
continue;
rterrln!("warning: invalid logging spec '{}', \
ignoring it", s);
continue
}
}
let dir = LogDirective {name: name, level: log_level};
@ -141,9 +139,9 @@ fn update_log_settings(crate_map: &CrateMap, settings: ~str) {
let mut dirs = ~[];
if settings.len() > 0 {
if settings == ~"::help" || settings == ~"?" {
dumb_println("\nCrate log map:\n");
rterrln!("\nCrate log map:\n");
do iter_crate_map(crate_map) |entry| {
dumb_println(" "+entry.name);
rterrln!(" {}", entry.name);
}
unsafe { exit(1); }
}
@ -157,12 +155,10 @@ fn update_log_settings(crate_map: &CrateMap, settings: ~str) {
}
if n_matches < (dirs.len() as u32) {
dumb_println(format!("warning: got {} RUST_LOG specs but only matched\n\
{} of them. You may have mistyped a RUST_LOG \
spec. \n\
Use RUST_LOG=::help to see the list of crates \
and modules.\n",
dirs.len(), n_matches));
rterrln!("warning: got {} RUST_LOG specs but only matched\n\
{} of them. You may have mistyped a RUST_LOG spec. \n\
Use RUST_LOG=::help to see the list of crates and modules.\n",
dirs.len(), n_matches);
}
}
@ -174,24 +170,13 @@ pub struct StdErrLogger;
impl Logger for StdErrLogger {
fn log(&mut self, args: &fmt::Arguments) {
fmt::writeln(self as &mut rt::io::Writer, args);
// FIXME(#6846): this should not call the blocking version of println,
// or at least the default loggers for tasks shouldn't do
// that
::rt::util::dumb_println(args);
}
}
impl rt::io::Writer for StdErrLogger {
fn write(&mut self, buf: &[u8]) {
// Nothing like swapping between I/O implementations! In theory this
// could use the libuv bindings for writing to file descriptors, but
// that may not necessarily be desirable because logging should work
// outside of the uv loop. (modify with caution)
use io::Writer;
let dbg = ::libc::STDERR_FILENO as ::io::fd_t;
dbg.write(buf);
}
fn flush(&mut self) {}
}
/// Configure logging by traversing the crate map and setting the
/// per-module global logging flags based on the logging spec
pub fn init() {
@ -212,7 +197,7 @@ pub fn init() {
_ => {
match log_spec {
Some(_) => {
dumb_println("warning: RUST_LOG set, but no crate map found.");
rterrln!("warning: RUST_LOG set, but no crate map found.");
},
None => {}
}

View file

@ -9,6 +9,7 @@
// except according to those terms.
use container::Container;
use fmt;
use from_str::FromStr;
use libc;
use option::{Some, None, Option};
@ -74,10 +75,11 @@ pub fn default_sched_threads() -> uint {
}
}
pub fn dumb_println(s: &str) {
use io::WriterUtil;
let dbg = ::libc::STDERR_FILENO as ::io::fd_t;
dbg.write_str(s + "\n");
pub fn dumb_println(args: &fmt::Arguments) {
use rt::io::native::stdio::stderr;
use rt::io::Writer;
let mut out = stderr();
fmt::writeln(&mut out as &mut Writer, args);
}
pub fn abort(msg: &str) -> ! {

View file

@ -12,19 +12,14 @@
#[allow(missing_doc)];
use c_str::ToCStr;
use cast;
use clone::Clone;
use comm::{stream, SharedChan, GenericChan, GenericPort};
use io;
use libc::{pid_t, c_void, c_int};
use cell::Cell;
use comm::{stream, SharedChan};
use libc::{pid_t, c_int};
use libc;
use option::{Some, None};
use os;
use prelude::*;
use ptr;
use rt::io::native::process;
use rt::io;
use task;
use vec::ImmutableVector;
/**
* A value representing a child process.
@ -34,28 +29,7 @@ use vec::ImmutableVector;
* for the process to terminate.
*/
pub struct Process {
/// The unique id of the process (this should never be negative).
priv pid: pid_t,
/**
* A handle to the process - on unix this will always be NULL, but on
* windows it will be a HANDLE to the process, which will prevent the
* pid being re-used until the handle is closed.
*/
priv handle: *(),
/// Some(fd), or None when stdin is being redirected from a fd not created by Process::new.
priv input: Option<c_int>,
/// Some(file), or None when stdout is being redirected to a fd not created by Process::new.
priv output: Option<*libc::FILE>,
/// Some(file), or None when stderr is being redirected to a fd not created by Process::new.
priv error: Option<*libc::FILE>,
/// None until finish() is called.
priv exit_code: Option<int>,
priv inner: process::Process,
}
/// Options that can be given when starting a Process.
@ -147,178 +121,50 @@ impl Process {
* * options - Options to configure the environment of the process,
* the working directory and the standard IO streams.
*/
pub fn new(prog: &str, args: &[~str],
options: ProcessOptions)
-> Process {
#[fixed_stack_segment]; #[inline(never)];
let (in_pipe, in_fd) = match options.in_fd {
None => {
let pipe = os::pipe();
(Some(pipe), pipe.input)
},
Some(fd) => (None, fd)
};
let (out_pipe, out_fd) = match options.out_fd {
None => {
let pipe = os::pipe();
(Some(pipe), pipe.out)
},
Some(fd) => (None, fd)
};
let (err_pipe, err_fd) = match options.err_fd {
None => {
let pipe = os::pipe();
(Some(pipe), pipe.out)
},
Some(fd) => (None, fd)
};
let res = spawn_process_os(prog, args, options.env.clone(), options.dir,
in_fd, out_fd, err_fd);
unsafe {
for pipe in in_pipe.iter() { libc::close(pipe.input); }
for pipe in out_pipe.iter() { libc::close(pipe.out); }
for pipe in err_pipe.iter() { libc::close(pipe.out); }
}
Process {
pid: res.pid,
handle: res.handle,
input: in_pipe.map(|pipe| pipe.out),
output: out_pipe.map(|pipe| os::fdopen(pipe.input)),
error: err_pipe.map(|pipe| os::fdopen(pipe.input)),
exit_code: None,
}
pub fn new(prog: &str, args: &[~str], options: ProcessOptions) -> Process {
let ProcessOptions { env, dir, in_fd, out_fd, err_fd } = options;
let inner = process::Process::new(prog, args, env, dir,
in_fd, out_fd, err_fd);
Process { inner: inner }
}
/// Returns the unique id of the process
pub fn get_id(&self) -> pid_t { self.pid }
fn input_fd(&mut self) -> c_int {
match self.input {
Some(fd) => fd,
None => fail2!("This Process's stdin was redirected to an \
existing file descriptor.")
}
}
fn output_file(&mut self) -> *libc::FILE {
match self.output {
Some(file) => file,
None => fail2!("This Process's stdout was redirected to an \
existing file descriptor.")
}
}
fn error_file(&mut self) -> *libc::FILE {
match self.error {
Some(file) => file,
None => fail2!("This Process's stderr was redirected to an \
existing file descriptor.")
}
}
/**
* Returns whether this process is reading its stdin from an existing file
* descriptor rather than a pipe that was created specifically for this
* process.
*
* If this method returns true then self.input() will fail.
*/
pub fn input_redirected(&self) -> bool {
self.input.is_none()
}
/**
* Returns whether this process is writing its stdout to an existing file
* descriptor rather than a pipe that was created specifically for this
* process.
*
* If this method returns true then self.output() will fail.
*/
pub fn output_redirected(&self) -> bool {
self.output.is_none()
}
/**
* Returns whether this process is writing its stderr to an existing file
* descriptor rather than a pipe that was created specifically for this
* process.
*
* If this method returns true then self.error() will fail.
*/
pub fn error_redirected(&self) -> bool {
self.error.is_none()
}
pub fn get_id(&self) -> pid_t { self.inner.id() }
/**
* Returns an io::Writer that can be used to write to this Process's stdin.
*
* Fails if this Process's stdin was redirected to an existing file descriptor.
* Fails if there is no stdin available (it's already been removed by
* take_input)
*/
pub fn input(&mut self) -> @io::Writer {
// FIXME: the Writer can still be used after self is destroyed: #2625
io::fd_writer(self.input_fd(), false)
}
pub fn input<'a>(&'a mut self) -> &'a mut io::Writer { self.inner.input() }
/**
* Returns an io::Reader that can be used to read from this Process's stdout.
*
* Fails if this Process's stdout was redirected to an existing file descriptor.
* Fails if there is no stdout available (it's already been removed by
* take_output)
*/
pub fn output(&mut self) -> @io::Reader {
// FIXME: the Reader can still be used after self is destroyed: #2625
io::FILE_reader(self.output_file(), false)
}
pub fn output<'a>(&'a mut self) -> &'a mut io::Reader { self.inner.output() }
/**
* Returns an io::Reader that can be used to read from this Process's stderr.
*
* Fails if this Process's stderr was redirected to an existing file descriptor.
* Fails if there is no stderr available (it's already been removed by
* take_error)
*/
pub fn error(&mut self) -> @io::Reader {
// FIXME: the Reader can still be used after self is destroyed: #2625
io::FILE_reader(self.error_file(), false)
}
pub fn error<'a>(&'a mut self) -> &'a mut io::Reader { self.inner.error() }
/**
* Closes the handle to the child process's stdin.
*
* If this process is reading its stdin from an existing file descriptor, then this
* method does nothing.
*/
pub fn close_input(&mut self) {
#[fixed_stack_segment]; #[inline(never)];
match self.input {
Some(-1) | None => (),
Some(fd) => {
unsafe {
libc::close(fd);
}
self.input = Some(-1);
}
}
self.inner.take_input();
}
fn close_outputs(&mut self) {
#[fixed_stack_segment]; #[inline(never)];
fclose_and_null(&mut self.output);
fclose_and_null(&mut self.error);
fn fclose_and_null(f_opt: &mut Option<*libc::FILE>) {
#[allow(cstack)]; // fixed_stack_segment declared on enclosing fn
match *f_opt {
Some(f) if !f.is_null() => {
unsafe {
libc::fclose(f);
*f_opt = Some(0 as *libc::FILE);
}
},
_ => ()
}
}
self.inner.take_output();
self.inner.take_error();
}
/**
@ -327,29 +173,35 @@ impl Process {
*
* If the child has already been finished then the exit code is returned.
*/
pub fn finish(&mut self) -> int {
for &code in self.exit_code.iter() {
return code;
}
self.close_input();
let code = waitpid(self.pid);
self.exit_code = Some(code);
return code;
}
pub fn finish(&mut self) -> int { self.inner.wait() }
/**
* Closes the handle to stdin, waits for the child process to terminate, and reads
* and returns all remaining output of stdout and stderr, along with the exit code.
* Closes the handle to stdin, waits for the child process to terminate, and
* reads and returns all remaining output of stdout and stderr, along with
* the exit code.
*
* If the child has already been finished then the exit code and any remaining
* unread output of stdout and stderr will be returned.
* If the child has already been finished then the exit code and any
* remaining unread output of stdout and stderr will be returned.
*
* This method will fail if the child process's stdout or stderr streams were
* redirected to existing file descriptors.
* This method will fail if the child process's stdout or stderr streams
* were redirected to existing file descriptors.
*/
pub fn finish_with_output(&mut self) -> ProcessOutput {
let output_file = self.output_file();
let error_file = self.error_file();
self.inner.take_input(); // close stdin
let output = Cell::new(self.inner.take_output());
let error = Cell::new(self.inner.take_error());
fn read_everything(r: &mut io::Reader) -> ~[u8] {
let mut ret = ~[];
let mut buf = [0, ..1024];
loop {
match r.read(buf) {
Some(n) => { ret.push_all(buf.slice_to(n)); }
None => break
}
}
return ret;
}
// Spawn two entire schedulers to read both stdout and sterr
// in parallel so we don't deadlock while blocking on one
@ -359,12 +211,16 @@ impl Process {
let ch = SharedChan::new(ch);
let ch_clone = ch.clone();
do task::spawn_sched(task::SingleThreaded) {
let errput = io::FILE_reader(error_file, false);
ch.send((2, errput.read_whole_stream()));
match error.take() {
Some(ref mut e) => ch.send((2, read_everything(*e))),
None => ch.send((2, ~[]))
}
}
do task::spawn_sched(task::SingleThreaded) {
let output = io::FILE_reader(output_file, false);
ch_clone.send((1, output.read_whole_stream()));
match output.take() {
Some(ref mut e) => ch_clone.send((1, read_everything(*e))),
None => ch_clone.send((1, ~[]))
}
}
let status = self.finish();
@ -382,40 +238,6 @@ impl Process {
error: errs};
}
fn destroy_internal(&mut self, force: bool) {
// if the process has finished, and therefore had waitpid called,
// and we kill it, then on unix we might ending up killing a
// newer process that happens to have the same (re-used) id
if self.exit_code.is_none() {
killpid(self.pid, force);
self.finish();
}
#[cfg(windows)]
fn killpid(pid: pid_t, _force: bool) {
#[fixed_stack_segment]; #[inline(never)];
unsafe {
libc::funcs::extra::kernel32::TerminateProcess(
cast::transmute(pid), 1);
}
}
#[cfg(unix)]
fn killpid(pid: pid_t, force: bool) {
#[fixed_stack_segment]; #[inline(never)];
let signal = if force {
libc::consts::os::posix88::SIGKILL
} else {
libc::consts::os::posix88::SIGTERM
};
unsafe {
libc::funcs::posix88::signal::kill(pid, signal as c_int);
}
}
}
/**
* Terminates the process, giving it a chance to clean itself up if
* this is supported by the operating system.
@ -423,7 +245,10 @@ impl Process {
* On Posix OSs SIGTERM will be sent to the process. On Win32
* TerminateProcess(..) will be called.
*/
pub fn destroy(&mut self) { self.destroy_internal(false); }
pub fn destroy(&mut self) {
self.inner.signal(io::process::PleaseExitSignal);
self.finish();
}
/**
* Terminates the process as soon as possible without giving it a
@ -432,386 +257,12 @@ impl Process {
* On Posix OSs SIGKILL will be sent to the process. On Win32
* TerminateProcess(..) will be called.
*/
pub fn force_destroy(&mut self) { self.destroy_internal(true); }
}
impl Drop for Process {
fn drop(&mut self) {
pub fn force_destroy(&mut self) {
self.inner.signal(io::process::MustDieSignal);
self.finish();
self.close_outputs();
free_handle(self.handle);
}
}
struct SpawnProcessResult {
pid: pid_t,
handle: *(),
}
#[cfg(windows)]
fn spawn_process_os(prog: &str, args: &[~str],
env: Option<~[(~str, ~str)]>,
dir: Option<&Path>,
in_fd: c_int, out_fd: c_int, err_fd: c_int) -> SpawnProcessResult {
#[fixed_stack_segment]; #[inline(never)];
use libc::types::os::arch::extra::{DWORD, HANDLE, STARTUPINFO};
use libc::consts::os::extra::{
TRUE, FALSE,
STARTF_USESTDHANDLES,
INVALID_HANDLE_VALUE,
DUPLICATE_SAME_ACCESS
};
use libc::funcs::extra::kernel32::{
GetCurrentProcess,
DuplicateHandle,
CloseHandle,
CreateProcessA
};
use libc::funcs::extra::msvcrt::get_osfhandle;
use sys;
unsafe {
let mut si = zeroed_startupinfo();
si.cb = sys::size_of::<STARTUPINFO>() as DWORD;
si.dwFlags = STARTF_USESTDHANDLES;
let cur_proc = GetCurrentProcess();
let orig_std_in = get_osfhandle(in_fd) as HANDLE;
if orig_std_in == INVALID_HANDLE_VALUE as HANDLE {
fail2!("failure in get_osfhandle: {}", os::last_os_error());
}
if DuplicateHandle(cur_proc, orig_std_in, cur_proc, &mut si.hStdInput,
0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE {
fail2!("failure in DuplicateHandle: {}", os::last_os_error());
}
let orig_std_out = get_osfhandle(out_fd) as HANDLE;
if orig_std_out == INVALID_HANDLE_VALUE as HANDLE {
fail2!("failure in get_osfhandle: {}", os::last_os_error());
}
if DuplicateHandle(cur_proc, orig_std_out, cur_proc, &mut si.hStdOutput,
0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE {
fail2!("failure in DuplicateHandle: {}", os::last_os_error());
}
let orig_std_err = get_osfhandle(err_fd) as HANDLE;
if orig_std_err == INVALID_HANDLE_VALUE as HANDLE {
fail2!("failure in get_osfhandle: {}", os::last_os_error());
}
if DuplicateHandle(cur_proc, orig_std_err, cur_proc, &mut si.hStdError,
0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE {
fail2!("failure in DuplicateHandle: {}", os::last_os_error());
}
let cmd = make_command_line(prog, args);
let mut pi = zeroed_process_information();
let mut create_err = None;
do with_envp(env) |envp| {
do with_dirp(dir) |dirp| {
do cmd.with_c_str |cmdp| {
let created = CreateProcessA(ptr::null(), cast::transmute(cmdp),
ptr::mut_null(), ptr::mut_null(), TRUE,
0, envp, dirp, &mut si, &mut pi);
if created == FALSE {
create_err = Some(os::last_os_error());
}
}
}
}
CloseHandle(si.hStdInput);
CloseHandle(si.hStdOutput);
CloseHandle(si.hStdError);
for msg in create_err.iter() {
fail2!("failure in CreateProcess: {}", *msg);
}
// We close the thread handle because we don't care about keeping the thread id valid,
// and we aren't keeping the thread handle around to be able to close it later. We don't
// close the process handle however because we want the process id to stay valid at least
// until the calling code closes the process handle.
CloseHandle(pi.hThread);
SpawnProcessResult {
pid: pi.dwProcessId as pid_t,
handle: pi.hProcess as *()
}
}
}
#[cfg(windows)]
fn zeroed_startupinfo() -> libc::types::os::arch::extra::STARTUPINFO {
libc::types::os::arch::extra::STARTUPINFO {
cb: 0,
lpReserved: ptr::mut_null(),
lpDesktop: ptr::mut_null(),
lpTitle: ptr::mut_null(),
dwX: 0,
dwY: 0,
dwXSize: 0,
dwYSize: 0,
dwXCountChars: 0,
dwYCountCharts: 0,
dwFillAttribute: 0,
dwFlags: 0,
wShowWindow: 0,
cbReserved2: 0,
lpReserved2: ptr::mut_null(),
hStdInput: ptr::mut_null(),
hStdOutput: ptr::mut_null(),
hStdError: ptr::mut_null()
}
}
#[cfg(windows)]
fn zeroed_process_information() -> libc::types::os::arch::extra::PROCESS_INFORMATION {
libc::types::os::arch::extra::PROCESS_INFORMATION {
hProcess: ptr::mut_null(),
hThread: ptr::mut_null(),
dwProcessId: 0,
dwThreadId: 0
}
}
// FIXME: this is only pub so it can be tested (see issue #4536)
#[cfg(windows)]
pub fn make_command_line(prog: &str, args: &[~str]) -> ~str {
let mut cmd = ~"";
append_arg(&mut cmd, prog);
for arg in args.iter() {
cmd.push_char(' ');
append_arg(&mut cmd, *arg);
}
return cmd;
fn append_arg(cmd: &mut ~str, arg: &str) {
let quote = arg.iter().any(|c| c == ' ' || c == '\t');
if quote {
cmd.push_char('"');
}
for i in range(0u, arg.len()) {
append_char_at(cmd, arg, i);
}
if quote {
cmd.push_char('"');
}
}
fn append_char_at(cmd: &mut ~str, arg: &str, i: uint) {
match arg[i] as char {
'"' => {
// Escape quotes.
cmd.push_str("\\\"");
}
'\\' => {
if backslash_run_ends_in_quote(arg, i) {
// Double all backslashes that are in runs before quotes.
cmd.push_str("\\\\");
} else {
// Pass other backslashes through unescaped.
cmd.push_char('\\');
}
}
c => {
cmd.push_char(c);
}
}
}
fn backslash_run_ends_in_quote(s: &str, mut i: uint) -> bool {
while i < s.len() && s[i] as char == '\\' {
i += 1;
}
return i < s.len() && s[i] as char == '"';
}
}
#[cfg(unix)]
fn spawn_process_os(prog: &str, args: &[~str],
env: Option<~[(~str, ~str)]>,
dir: Option<&Path>,
in_fd: c_int, out_fd: c_int, err_fd: c_int) -> SpawnProcessResult {
#[fixed_stack_segment]; #[inline(never)];
use libc::funcs::posix88::unistd::{fork, dup2, close, chdir, execvp};
use libc::funcs::bsd44::getdtablesize;
mod rustrt {
#[abi = "cdecl"]
extern {
pub fn rust_unset_sigprocmask();
}
}
#[cfg(windows)]
unsafe fn set_environ(_envp: *c_void) {}
#[cfg(target_os = "macos")]
unsafe fn set_environ(envp: *c_void) {
externfn!(fn _NSGetEnviron() -> *mut *c_void);
*_NSGetEnviron() = envp;
}
#[cfg(not(target_os = "macos"), not(windows))]
unsafe fn set_environ(envp: *c_void) {
extern {
static mut environ: *c_void;
}
environ = envp;
}
unsafe {
let pid = fork();
if pid < 0 {
fail2!("failure in fork: {}", os::last_os_error());
} else if pid > 0 {
return SpawnProcessResult {pid: pid, handle: ptr::null()};
}
rustrt::rust_unset_sigprocmask();
if dup2(in_fd, 0) == -1 {
fail2!("failure in dup2(in_fd, 0): {}", os::last_os_error());
}
if dup2(out_fd, 1) == -1 {
fail2!("failure in dup2(out_fd, 1): {}", os::last_os_error());
}
if dup2(err_fd, 2) == -1 {
fail2!("failure in dup3(err_fd, 2): {}", os::last_os_error());
}
// close all other fds
for fd in range(3, getdtablesize()).invert() {
close(fd as c_int);
}
do with_dirp(dir) |dirp| {
if !dirp.is_null() && chdir(dirp) == -1 {
fail2!("failure in chdir: {}", os::last_os_error());
}
}
do with_envp(env) |envp| {
if !envp.is_null() {
set_environ(envp);
}
do with_argv(prog, args) |argv| {
execvp(*argv, argv);
// execvp only returns if an error occurred
fail2!("failure in execvp: {}", os::last_os_error());
}
}
}
}
#[cfg(unix)]
fn with_argv<T>(prog: &str, args: &[~str], cb: &fn(**libc::c_char) -> T) -> T {
use vec;
// We can't directly convert `str`s into `*char`s, as someone needs to hold
// a reference to the intermediary byte buffers. So first build an array to
// hold all the ~[u8] byte strings.
let mut tmps = vec::with_capacity(args.len() + 1);
tmps.push(prog.to_c_str());
for arg in args.iter() {
tmps.push(arg.to_c_str());
}
// Next, convert each of the byte strings into a pointer. This is
// technically unsafe as the caller could leak these pointers out of our
// scope.
let mut ptrs = do tmps.map |tmp| {
tmp.with_ref(|buf| buf)
};
// Finally, make sure we add a null pointer.
ptrs.push(ptr::null());
ptrs.as_imm_buf(|buf, _| cb(buf))
}
#[cfg(unix)]
fn with_envp<T>(env: Option<~[(~str, ~str)]>, cb: &fn(*c_void) -> T) -> T {
use vec;
// 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.
match env {
Some(env) => {
let mut tmps = vec::with_capacity(env.len());
for pair in env.iter() {
let kv = format!("{}={}", pair.first(), pair.second());
tmps.push(kv.to_c_str());
}
// Once again, this is unsafe.
let mut ptrs = do tmps.map |tmp| {
tmp.with_ref(|buf| buf)
};
ptrs.push(ptr::null());
do ptrs.as_imm_buf |buf, _| {
unsafe { cb(cast::transmute(buf)) }
}
}
_ => cb(ptr::null())
}
}
#[cfg(windows)]
fn with_envp<T>(env: Option<~[(~str, ~str)]>, cb: &fn(*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.
match env {
Some(env) => {
let mut blk = ~[];
for pair in env.iter() {
let kv = format!("{}={}", pair.first(), pair.second());
blk.push_all(kv.as_bytes());
blk.push(0);
}
blk.push(0);
do blk.as_imm_buf |p, _len| {
unsafe { cb(cast::transmute(p)) }
}
}
_ => cb(ptr::mut_null())
}
}
fn with_dirp<T>(d: Option<&Path>, cb: &fn(*libc::c_char) -> T) -> T {
match d {
Some(dir) => dir.with_c_str(|buf| cb(buf)),
None => cb(ptr::null())
}
}
#[cfg(windows)]
fn free_handle(handle: *()) {
#[fixed_stack_segment]; #[inline(never)];
unsafe {
libc::funcs::extra::kernel32::CloseHandle(cast::transmute(handle));
}
}
#[cfg(unix)]
fn free_handle(_handle: *()) {
// unix has no process handle object, just a pid
}
/**
* Spawns a process and waits for it to terminate. The process will
* inherit the current stdin/stdout/stderr file descriptors.
@ -825,13 +276,14 @@ fn free_handle(_handle: *()) {
*
* The process's exit code
*/
#[fixed_stack_segment] #[inline(never)]
pub fn process_status(prog: &str, args: &[~str]) -> int {
let mut prog = Process::new(prog, args, ProcessOptions {
env: None,
dir: None,
in_fd: Some(0),
out_fd: Some(1),
err_fd: Some(2)
in_fd: Some(unsafe { libc::dup(libc::STDIN_FILENO) }),
out_fd: Some(unsafe { libc::dup(libc::STDOUT_FILENO) }),
err_fd: Some(unsafe { libc::dup(libc::STDERR_FILENO) })
});
prog.finish()
}
@ -853,110 +305,8 @@ pub fn process_output(prog: &str, args: &[~str]) -> ProcessOutput {
prog.finish_with_output()
}
/**
* Waits for a process to exit and returns the exit code, failing
* if there is no process with the specified id.
*
* Note that this is private to avoid race conditions on unix where if
* a user calls waitpid(some_process.get_id()) then some_process.finish()
* and some_process.destroy() and some_process.finalize() will then either
* operate on a none-existent process or, even worse, on a newer process
* with the same id.
*/
fn waitpid(pid: pid_t) -> int {
return waitpid_os(pid);
#[cfg(windows)]
fn waitpid_os(pid: pid_t) -> int {
#[fixed_stack_segment]; #[inline(never)];
use libc::types::os::arch::extra::DWORD;
use libc::consts::os::extra::{
SYNCHRONIZE,
PROCESS_QUERY_INFORMATION,
FALSE,
STILL_ACTIVE,
INFINITE,
WAIT_FAILED
};
use libc::funcs::extra::kernel32::{
OpenProcess,
GetExitCodeProcess,
CloseHandle,
WaitForSingleObject
};
unsafe {
let proc = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, FALSE, pid as DWORD);
if proc.is_null() {
fail2!("failure in OpenProcess: {}", os::last_os_error());
}
loop {
let mut status = 0;
if GetExitCodeProcess(proc, &mut status) == FALSE {
CloseHandle(proc);
fail2!("failure in GetExitCodeProcess: {}", os::last_os_error());
}
if status != STILL_ACTIVE {
CloseHandle(proc);
return status as int;
}
if WaitForSingleObject(proc, INFINITE) == WAIT_FAILED {
CloseHandle(proc);
fail2!("failure in WaitForSingleObject: {}", os::last_os_error());
}
}
}
}
#[cfg(unix)]
fn waitpid_os(pid: pid_t) -> int {
#[fixed_stack_segment]; #[inline(never)];
use libc::funcs::posix01::wait::*;
#[cfg(target_os = "linux")]
#[cfg(target_os = "android")]
fn WIFEXITED(status: i32) -> bool {
(status & 0xffi32) == 0i32
}
#[cfg(target_os = "macos")]
#[cfg(target_os = "freebsd")]
fn WIFEXITED(status: i32) -> bool {
(status & 0x7fi32) == 0i32
}
#[cfg(target_os = "linux")]
#[cfg(target_os = "android")]
fn WEXITSTATUS(status: i32) -> i32 {
(status >> 8i32) & 0xffi32
}
#[cfg(target_os = "macos")]
#[cfg(target_os = "freebsd")]
fn WEXITSTATUS(status: i32) -> i32 {
status >> 8i32
}
let mut status = 0 as c_int;
if unsafe { waitpid(pid, &mut status, 0) } == -1 {
fail2!("failure in waitpid: {}", os::last_os_error());
}
return if WIFEXITED(status) {
WEXITSTATUS(status) as int
} else {
1
};
}
}
#[cfg(test)]
mod tests {
use io;
use libc::c_int;
use option::{Option, None, Some};
use os;
@ -964,27 +314,8 @@ mod tests {
use run;
use str;
use unstable::running_on_valgrind;
#[test]
#[cfg(windows)]
fn test_make_command_line() {
assert_eq!(
run::make_command_line("prog", [~"aaa", ~"bbb", ~"ccc"]),
~"prog aaa bbb ccc"
);
assert_eq!(
run::make_command_line("C:\\Program Files\\blah\\blah.exe", [~"aaa"]),
~"\"C:\\Program Files\\blah\\blah.exe\" aaa"
);
assert_eq!(
run::make_command_line("C:\\Program Files\\test", [~"aa\"bb"]),
~"\"C:\\Program Files\\test\" aa\\\"bb"
);
assert_eq!(
run::make_command_line("echo", [~"a b c"]),
~"echo \"a b c\""
);
}
use rt::io::native::file;
use rt::io::{Writer, Reader};
#[test]
#[cfg(not(target_os="android"))]
@ -1068,10 +399,6 @@ mod tests {
err_fd: Some(pipe_err.out)
});
assert!(proc.input_redirected());
assert!(proc.output_redirected());
assert!(proc.error_redirected());
os::close(pipe_in.input);
os::close(pipe_out.out);
os::close(pipe_err.out);
@ -1086,21 +413,21 @@ mod tests {
}
fn writeclose(fd: c_int, s: &str) {
let writer = io::fd_writer(fd, false);
writer.write_str(s);
os::close(fd);
let mut writer = file::FileDesc::new(fd);
writer.write(s.as_bytes());
}
fn readclose(fd: c_int) -> ~str {
#[fixed_stack_segment]; #[inline(never)];
unsafe {
let file = os::fdopen(fd);
let reader = io::FILE_reader(file, false);
let buf = reader.read_whole_stream();
os::fclose(file);
str::from_utf8(buf)
let mut res = ~[];
let mut reader = file::FileDesc::new(fd);
let mut buf = [0, ..1024];
loop {
match reader.read(buf) {
Some(n) => { res.push_all(buf.slice_to(n)); }
None => break
}
}
str::from_utf8_owned(res)
}
#[test]
@ -1223,36 +550,6 @@ mod tests {
}
}
#[test]
#[should_fail]
#[cfg(not(windows),not(target_os="android"))]
fn test_finish_with_output_redirected() {
let mut prog = run::Process::new("echo", [~"hello"], run::ProcessOptions {
env: None,
dir: None,
in_fd: Some(0),
out_fd: Some(1),
err_fd: Some(2)
});
// this should fail because it is not valid to read the output when it was redirected
prog.finish_with_output();
}
#[test]
#[should_fail]
#[cfg(not(windows),target_os="android")]
fn test_finish_with_output_redirected() {
let mut prog = run::Process::new("/system/bin/sh", [~"-c",~"echo hello"],
run::ProcessOptions {
env: None,
dir: None,
in_fd: Some(0),
out_fd: Some(1),
err_fd: Some(2)
});
// this should fail because it is not valid to read the output when it was redirected
prog.finish_with_output();
}
#[cfg(unix,not(target_os="android"))]
fn run_pwd(dir: Option<&Path>) -> run::Process {
run::Process::new("pwd", [], run::ProcessOptions {

View file

@ -15,7 +15,6 @@
extern mod extra;
use extra::time::precise_time_s;
use std::io;
use std::os;
use std::rand::Rng;
use std::rand;
@ -70,11 +69,15 @@ fn shift_push() {
}
fn read_line() {
use std::rt::io::{Reader, Open};
use std::rt::io::file::FileInfo;
use std::rt::io::buffered::BufferedReader;
let path = Path(env!("CFG_SRC_DIR"))
.push_rel(&Path("src/test/bench/shootout-k-nucleotide.data"));
for _ in range(0, 3) {
let reader = io::file_reader(&path).unwrap();
let mut reader = BufferedReader::new(path.open_reader(Open).unwrap());
while !reader.eof() {
reader.read_line();
}

View file

@ -156,17 +156,21 @@ fn make_sequence_processor(sz: uint,
// given a FASTA file on stdin, process sequence THREE
fn main() {
use std::rt::io::{Reader, Open};
use std::rt::io::file::FileInfo;
use std::rt::io::native::stdio;
use std::rt::io::buffered::BufferedReader;
let rdr = if os::getenv("RUST_BENCH").is_some() {
// FIXME: Using this compile-time env variable is a crummy way to
// get to this massive data set, but include_bin! chokes on it (#2598)
let path = Path(env!("CFG_SRC_DIR"))
.push_rel(&Path("src/test/bench/shootout-k-nucleotide.data"));
io::file_reader(&path).unwrap()
} else {
io::stdin()
};
// FIXME: Using this compile-time env variable is a crummy way to
// get to this massive data set, but include_bin! chokes on it (#2598)
let path = Path(env!("CFG_SRC_DIR"))
.push_rel(&Path("src/test/bench/shootout-k-nucleotide.data"));
~path.open_reader(Open).unwrap() as ~Reader
} else {
~stdio::stdin() as ~Reader
};
let mut rdr = BufferedReader::new(rdr);
// initialize each sequence sorter
let sizes = ~[1u,2,3,4,6,12,18];
@ -193,8 +197,11 @@ fn main() {
// reading the sequence of interest
let mut proc_mode = false;
while !rdr.eof() {
let line: ~str = rdr.read_line();
loop {
let line = match rdr.read_line() {
Some(ln) => ln, None => break,
};
let line = line.trim().to_owned();
if line.len() == 0u { continue; }