diff options
| author | bors <bors@rust-lang.org> | 2013-10-10 04:31:24 -0700 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2013-10-10 04:31:24 -0700 |
| commit | 0ede2ea4e2e9384ac5bd614012d85ed213873dab (patch) | |
| tree | 1c1273aa2aabe17e0557c01b41d4d438c5dd130e /src/libstd | |
| parent | 34d123db4eb03c1b2378b6248ebea5f0f40f2a4f (diff) | |
| parent | 413747176c9ce52a87775175e096b3eca88e6b64 (diff) | |
| download | rust-0ede2ea4e2e9384ac5bd614012d85ed213873dab.tar.gz rust-0ede2ea4e2e9384ac5bd614012d85ed213873dab.zip | |
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?).
Diffstat (limited to 'src/libstd')
| -rw-r--r-- | src/libstd/cleanup.rs | 17 | ||||
| -rw-r--r-- | src/libstd/io.rs | 37 | ||||
| -rw-r--r-- | src/libstd/macros.rs | 2 | ||||
| -rw-r--r-- | src/libstd/prelude.rs | 2 | ||||
| -rw-r--r-- | src/libstd/rt/borrowck.rs | 56 | ||||
| -rw-r--r-- | src/libstd/rt/io/buffered.rs | 72 | ||||
| -rw-r--r-- | src/libstd/rt/io/file.rs | 2 | ||||
| -rw-r--r-- | src/libstd/rt/io/mod.rs | 27 | ||||
| -rw-r--r-- | src/libstd/rt/io/native/file.rs | 256 | ||||
| -rw-r--r-- | src/libstd/rt/io/native/process.rs | 745 | ||||
| -rw-r--r-- | src/libstd/rt/io/native/stdio.rs | 67 | ||||
| -rw-r--r-- | src/libstd/rt/io/process.rs | 7 | ||||
| -rw-r--r-- | src/libstd/rt/io/stdio.rs | 105 | ||||
| -rw-r--r-- | src/libstd/rt/logging.rs | 49 | ||||
| -rw-r--r-- | src/libstd/rt/util.rs | 10 | ||||
| -rw-r--r-- | src/libstd/run.rs | 859 |
16 files changed, 1336 insertions, 977 deletions
diff --git a/src/libstd/cleanup.rs b/src/libstd/cleanup.rs index 6b982ec75da..1c9944664ee 100644 --- a/src/libstd/cleanup.rs +++ b/src/libstd/cleanup.rs @@ -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); } } diff --git a/src/libstd/io.rs b/src/libstd/io.rs index 2dfd41a4435..791616d330e 100644 --- a/src/libstd/io.rs +++ b/src/libstd/io.rs @@ -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? @@ -2087,16 +2060,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(); wr.write([0u8, 1u8, 2u8, 3u8]); diff --git a/src/libstd/macros.rs b/src/libstd/macros.rs index 2ef25548535..17dc03d0098 100644 --- a/src/libstd/macros.rs +++ b/src/libstd/macros.rs @@ -13,7 +13,7 @@ macro_rules! rterrln ( ($($arg:tt)*) => ( { - ::rt::util::dumb_println(format!($($arg)*)); + format_args!(::rt::util::dumb_println, $($arg)*) } ) ) diff --git a/src/libstd/prelude.rs b/src/libstd/prelude.rs index 273a01c1811..3da337add94 100644 --- a/src/libstd/prelude.rs +++ b/src/libstd/prelude.rs @@ -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; diff --git a/src/libstd/rt/borrowck.rs b/src/libstd/rt/borrowck.rs index d703272420c..6be23a983ab 100644 --- a/src/libstd/rt/borrowck.rs +++ b/src/libstd/rt/borrowck.rs @@ -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); } } diff --git a/src/libstd/rt/io/buffered.rs b/src/libstd/rt/io/buffered.rs index 2269469ee23..9dcb35c806f 100644 --- a/src/libstd/rt/io/buffered.rs +++ b/src/libstd/rt/io/buffered.rs @@ -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); + } } diff --git a/src/libstd/rt/io/file.rs b/src/libstd/rt/io/file.rs index a18eec8773e..3258c350cd0 100644 --- a/src/libstd/rt/io/file.rs +++ b/src/libstd/rt/io/file.rs @@ -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 diff --git a/src/libstd/rt/io/mod.rs b/src/libstd/rt/io/mod.rs index f14f8f28d12..f9542cbf5f9 100644 --- a/src/libstd/rt/io/mod.rs +++ b/src/libstd/rt/io/mod.rs @@ -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 {} diff --git a/src/libstd/rt/io/native/file.rs b/src/libstd/rt/io/native/file.rs index 47ae89ccf9f..dc8d34d1b11 100644 --- a/src/libstd/rt/io/native/file.rs +++ b/src/libstd/rt/io/native/file.rs @@ -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) + } + } + } } diff --git a/src/libstd/rt/io/native/process.rs b/src/libstd/rt/io/native/process.rs new file mode 100644 index 00000000000..d338192c664 --- /dev/null +++ b/src/libstd/rt/io/native/process.rs @@ -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 +} diff --git a/src/libstd/rt/io/native/stdio.rs b/src/libstd/rt/io/native/stdio.rs new file mode 100644 index 00000000000..5661725d77b --- /dev/null +++ b/src/libstd/rt/io/native/stdio.rs @@ -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() } +} diff --git a/src/libstd/rt/io/process.rs b/src/libstd/rt/io/process.rs index c190547889d..5f2453852ee 100644 --- a/src/libstd/rt/io/process.rs +++ b/src/libstd/rt/io/process.rs @@ -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>], diff --git a/src/libstd/rt/io/stdio.rs b/src/libstd/rt/io/stdio.rs index 734a40429a6..e3ca148862f 100644 --- a/src/libstd/rt/io/stdio.rs +++ b/src/libstd/rt/io/stdio.rs @@ -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!() } +/// 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 fn print(_s: &str) { fail2!() } +/// 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 } +} -pub fn println(_s: &str) { 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 } +} -pub enum StdStream { - StdIn, - StdOut, - StdErr +/// 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()); } -pub struct StdReader; +/// 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]); +} -impl StdReader { - pub fn new(_stream: StdStream) -> StdReader { fail2!() } +/// 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) + } + } } diff --git a/src/libstd/rt/logging.rs b/src/libstd/rt/logging.rs index b08e76921d8..660d1cd4359 100644 --- a/src/libstd/rt/logging.rs +++ b/src/libstd/rt/logging.rs @@ -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 => {} } diff --git a/src/libstd/rt/util.rs b/src/libstd/rt/util.rs index 68996a3a2a5..727bdb782d2 100644 --- a/src/libstd/rt/util.rs +++ b/src/libstd/rt/util.rs @@ -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) -> ! { diff --git a/src/libstd/run.rs b/src/libstd/run.rs index 074c232e149..8712d01aae9 100644 --- a/src/libstd/run.rs +++ b/src/libstd/run.rs @@ -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 { |
