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/rt/io | |
| 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/rt/io')
| -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 |
8 files changed, 1222 insertions, 59 deletions
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) + } + } } |
