about summary refs log tree commit diff
path: root/src/libstd/rt/io
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2013-10-10 04:31:24 -0700
committerbors <bors@rust-lang.org>2013-10-10 04:31:24 -0700
commit0ede2ea4e2e9384ac5bd614012d85ed213873dab (patch)
tree1c1273aa2aabe17e0557c01b41d4d438c5dd130e /src/libstd/rt/io
parent34d123db4eb03c1b2378b6248ebea5f0f40f2a4f (diff)
parent413747176c9ce52a87775175e096b3eca88e6b64 (diff)
downloadrust-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.rs72
-rw-r--r--src/libstd/rt/io/file.rs2
-rw-r--r--src/libstd/rt/io/mod.rs27
-rw-r--r--src/libstd/rt/io/native/file.rs256
-rw-r--r--src/libstd/rt/io/native/process.rs745
-rw-r--r--src/libstd/rt/io/native/stdio.rs67
-rw-r--r--src/libstd/rt/io/process.rs7
-rw-r--r--src/libstd/rt/io/stdio.rs105
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)
+        }
+    }
 }