about summary refs log tree commit diff
path: root/src/libstd/sys
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstd/sys')
-rw-r--r--src/libstd/sys/common/helper_thread.rs11
-rw-r--r--src/libstd/sys/unix/fs.rs411
-rw-r--r--src/libstd/sys/unix/mod.rs2
-rw-r--r--src/libstd/sys/unix/process.rs587
-rw-r--r--src/libstd/sys/windows/fs.rs460
-rw-r--r--src/libstd/sys/windows/mod.rs1
-rw-r--r--src/libstd/sys/windows/process.rs511
7 files changed, 1971 insertions, 12 deletions
diff --git a/src/libstd/sys/common/helper_thread.rs b/src/libstd/sys/common/helper_thread.rs
index 8c8ec4466a7..87907fde277 100644
--- a/src/libstd/sys/common/helper_thread.rs
+++ b/src/libstd/sys/common/helper_thread.rs
@@ -20,8 +20,6 @@
 //! can be created in the future and there must be no active timers at that
 //! time.
 
-#![macro_escape]
-
 use mem;
 use rt::bookkeeping;
 use rt::mutex::StaticNativeMutex;
@@ -57,15 +55,6 @@ pub struct Helper<M> {
     pub initialized: UnsafeCell<bool>,
 }
 
-macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => (
-    static $name: Helper<$m> = Helper {
-        lock: ::std::rt::mutex::NATIVE_MUTEX_INIT,
-        chan: ::std::cell::UnsafeCell { value: 0 as *mut Sender<$m> },
-        signal: ::std::cell::UnsafeCell { value: 0 },
-        initialized: ::std::cell::UnsafeCell { value: false },
-    };
-) )
-
 impl<M: Send> Helper<M> {
     /// Lazily boots a helper thread, becoming a no-op if the helper has already
     /// been spawned.
diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs
new file mode 100644
index 00000000000..3dcd99859e8
--- /dev/null
+++ b/src/libstd/sys/unix/fs.rs
@@ -0,0 +1,411 @@
+// Copyright 2013-2014 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.
+
+//! Blocking posix-based file I/O
+
+use libc::{mod, c_int, c_void};
+use c_str::CString;
+use mem;
+use io;
+
+use prelude::*;
+
+use io::{FilePermission, Write, UnstableFileStat, Open, FileAccess, FileMode};
+use io::{IoResult, FileStat, SeekStyle, Reader};
+use io::{Read, Truncate, SeekCur, SeekSet, ReadWrite, SeekEnd, Append};
+use result::{Ok, Err};
+use sys::retry;
+use sys_common::{keep_going, eof, mkerr_libc};
+
+pub use path::PosixPath as Path;
+
+pub type fd_t = libc::c_int;
+
+pub struct FileDesc {
+    /// The underlying C file descriptor.
+    fd: fd_t,
+
+    /// Whether to close the file descriptor on drop.
+    close_on_drop: bool,
+}
+
+impl FileDesc {
+    pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc {
+        FileDesc { fd: fd, close_on_drop: close_on_drop }
+    }
+
+    pub fn read(&self, buf: &mut [u8]) -> IoResult<uint> {
+        let ret = retry(|| unsafe {
+            libc::read(self.fd(),
+                       buf.as_mut_ptr() as *mut libc::c_void,
+                       buf.len() as libc::size_t)
+        });
+        if ret == 0 {
+            Err(eof())
+        } else if ret < 0 {
+            Err(super::last_error())
+        } else {
+            Ok(ret as uint)
+        }
+    }
+    pub fn write(&self, buf: &[u8]) -> IoResult<()> {
+        let ret = keep_going(buf, |buf, len| {
+            unsafe {
+                libc::write(self.fd(), buf as *const libc::c_void,
+                            len as libc::size_t) as i64
+            }
+        });
+        if ret < 0 {
+            Err(super::last_error())
+        } else {
+            Ok(())
+        }
+    }
+
+    pub fn fd(&self) -> fd_t { self.fd }
+
+    pub fn seek(&self, pos: i64, whence: SeekStyle) -> IoResult<u64> {
+        let whence = match whence {
+            SeekSet => libc::SEEK_SET,
+            SeekEnd => libc::SEEK_END,
+            SeekCur => libc::SEEK_CUR,
+        };
+        let n = unsafe { libc::lseek(self.fd(), pos as libc::off_t, whence) };
+        if n < 0 {
+            Err(super::last_error())
+        } else {
+            Ok(n as u64)
+        }
+    }
+
+    pub fn tell(&self) -> IoResult<u64> {
+        let n = unsafe { libc::lseek(self.fd(), 0, libc::SEEK_CUR) };
+        if n < 0 {
+            Err(super::last_error())
+        } else {
+            Ok(n as u64)
+        }
+    }
+
+    pub fn fsync(&self) -> IoResult<()> {
+        mkerr_libc(retry(|| unsafe { libc::fsync(self.fd()) }))
+    }
+
+    pub fn datasync(&self) -> IoResult<()> {
+        return mkerr_libc(os_datasync(self.fd()));
+
+        #[cfg(any(target_os = "macos", target_os = "ios"))]
+        fn os_datasync(fd: c_int) -> c_int {
+            unsafe { libc::fcntl(fd, libc::F_FULLFSYNC) }
+        }
+        #[cfg(target_os = "linux")]
+        fn os_datasync(fd: c_int) -> c_int {
+            retry(|| unsafe { libc::fdatasync(fd) })
+        }
+        #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "linux")))]
+        fn os_datasync(fd: c_int) -> c_int {
+            retry(|| unsafe { libc::fsync(fd) })
+        }
+    }
+
+    pub fn truncate(&self, offset: i64) -> IoResult<()> {
+        mkerr_libc(retry(|| unsafe {
+            libc::ftruncate(self.fd(), offset as libc::off_t)
+        }))
+    }
+
+    pub fn fstat(&self) -> IoResult<FileStat> {
+        let mut stat: libc::stat = unsafe { mem::zeroed() };
+        match unsafe { libc::fstat(self.fd(), &mut stat) } {
+            0 => Ok(mkstat(&stat)),
+            _ => Err(super::last_error()),
+        }
+    }
+
+    /// Extract the actual filedescriptor without closing it.
+    pub fn unwrap(self) -> fd_t {
+        let fd = self.fd;
+        unsafe { mem::forget(self) };
+        fd
+    }
+}
+
+/*
+
+impl RtioTTY for FileDesc {
+    fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
+        self.inner_read(buf)
+    }
+    fn write(&mut self, buf: &[u8]) -> IoResult<()> {
+        self.inner_write(buf)
+    }
+    fn set_raw(&mut self, _raw: bool) -> IoResult<()> {
+        Err(super::unimpl())
+    }
+    fn get_winsize(&mut self) -> IoResult<(int, int)> {
+        Err(super::unimpl())
+    }
+    fn isatty(&self) -> bool { false }
+}
+*/
+
+impl Drop for FileDesc {
+    fn drop(&mut self) {
+        // closing stdio file handles makes no sense, so never do it. Also, note
+        // that errors are ignored when closing a file descriptor. The reason
+        // for this is that if an error occurs we don't actually know if the
+        // file descriptor was closed or not, and if we retried (for something
+        // like EINTR), we might close another valid file descriptor (opened
+        // after we closed ours.
+        if self.close_on_drop && self.fd > libc::STDERR_FILENO {
+            let n = unsafe { libc::close(self.fd) };
+            if n != 0 {
+                println!("error {} when closing file descriptor {}", n, self.fd);
+            }
+        }
+    }
+}
+
+pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult<FileDesc> {
+    let flags = match fm {
+        Open => 0,
+        Append => libc::O_APPEND,
+        Truncate => libc::O_TRUNC,
+    };
+    // Opening with a write permission must silently create the file.
+    let (flags, mode) = match fa {
+        Read => (flags | libc::O_RDONLY, 0),
+        Write => (flags | libc::O_WRONLY | libc::O_CREAT,
+                        libc::S_IRUSR | libc::S_IWUSR),
+        ReadWrite => (flags | libc::O_RDWR | libc::O_CREAT,
+                            libc::S_IRUSR | libc::S_IWUSR),
+    };
+
+    let path = path.to_c_str();
+    match retry(|| unsafe { libc::open(path.as_ptr(), flags, mode) }) {
+        -1 => Err(super::last_error()),
+        fd => Ok(FileDesc::new(fd, true)),
+    }
+}
+
+pub fn mkdir(p: &Path, mode: uint) -> IoResult<()> {
+    let p = p.to_c_str();
+    mkerr_libc(unsafe { libc::mkdir(p.as_ptr(), mode as libc::mode_t) })
+}
+
+pub fn readdir(p: &Path) -> IoResult<Vec<Path>> {
+    use libc::{dirent_t};
+    use libc::{opendir, readdir_r, closedir};
+
+    fn prune(root: &CString, dirs: Vec<Path>) -> Vec<Path> {
+        let root = unsafe { CString::new(root.as_ptr(), false) };
+        let root = Path::new(root);
+
+        dirs.into_iter().filter(|path| {
+            path.as_vec() != b"." && path.as_vec() != b".."
+        }).map(|path| root.join(path)).collect()
+    }
+
+    extern {
+        fn rust_dirent_t_size() -> libc::c_int;
+        fn rust_list_dir_val(ptr: *mut dirent_t) -> *const libc::c_char;
+    }
+
+    let size = unsafe { rust_dirent_t_size() };
+    let mut buf = Vec::<u8>::with_capacity(size as uint);
+    let ptr = buf.as_mut_slice().as_mut_ptr() as *mut dirent_t;
+
+    let p = p.to_c_str();
+    let dir_ptr = unsafe {opendir(p.as_ptr())};
+
+    if dir_ptr as uint != 0 {
+        let mut paths = vec!();
+        let mut entry_ptr = 0 as *mut dirent_t;
+        while unsafe { readdir_r(dir_ptr, ptr, &mut entry_ptr) == 0 } {
+            if entry_ptr.is_null() { break }
+            let cstr = unsafe {
+                CString::new(rust_list_dir_val(entry_ptr), false)
+            };
+            paths.push(Path::new(cstr));
+        }
+        assert_eq!(unsafe { closedir(dir_ptr) }, 0);
+        Ok(prune(&p, paths))
+    } else {
+        Err(super::last_error())
+    }
+}
+
+pub fn unlink(p: &Path) -> IoResult<()> {
+    let p = p.to_c_str();
+    mkerr_libc(unsafe { libc::unlink(p.as_ptr()) })
+}
+
+pub fn rename(old: &Path, new: &Path) -> IoResult<()> {
+    let old = old.to_c_str();
+    let new = new.to_c_str();
+    mkerr_libc(unsafe {
+        libc::rename(old.as_ptr(), new.as_ptr())
+    })
+}
+
+pub fn chmod(p: &Path, mode: uint) -> IoResult<()> {
+    let p = p.to_c_str();
+    mkerr_libc(retry(|| unsafe {
+        libc::chmod(p.as_ptr(), mode as libc::mode_t)
+    }))
+}
+
+pub fn rmdir(p: &Path) -> IoResult<()> {
+    let p = p.to_c_str();
+    mkerr_libc(unsafe { libc::rmdir(p.as_ptr()) })
+}
+
+pub fn chown(p: &Path, uid: int, gid: int) -> IoResult<()> {
+    let p = p.to_c_str();
+    mkerr_libc(retry(|| unsafe {
+        libc::chown(p.as_ptr(), uid as libc::uid_t, gid as libc::gid_t)
+    }))
+}
+
+pub fn readlink(p: &Path) -> IoResult<Path> {
+    let c_path = p.to_c_str();
+    let p = c_path.as_ptr();
+    let mut len = unsafe { libc::pathconf(p as *mut _, libc::_PC_NAME_MAX) };
+    if len == -1 {
+        len = 1024; // FIXME: read PATH_MAX from C ffi?
+    }
+    let mut buf: Vec<u8> = Vec::with_capacity(len as uint);
+    match unsafe {
+        libc::readlink(p, buf.as_ptr() as *mut libc::c_char,
+                       len as libc::size_t) as libc::c_int
+    } {
+        -1 => Err(super::last_error()),
+        n => {
+            assert!(n > 0);
+            unsafe { buf.set_len(n as uint); }
+            Ok(Path::new(buf))
+        }
+    }
+}
+
+pub fn symlink(src: &Path, dst: &Path) -> IoResult<()> {
+    let src = src.to_c_str();
+    let dst = dst.to_c_str();
+    mkerr_libc(unsafe { libc::symlink(src.as_ptr(), dst.as_ptr()) })
+}
+
+pub fn link(src: &Path, dst: &Path) -> IoResult<()> {
+    let src = src.to_c_str();
+    let dst = dst.to_c_str();
+    mkerr_libc(unsafe { libc::link(src.as_ptr(), dst.as_ptr()) })
+}
+
+fn mkstat(stat: &libc::stat) -> FileStat {
+    // FileStat times are in milliseconds
+    fn mktime(secs: u64, nsecs: u64) -> u64 { secs * 1000 + nsecs / 1000000 }
+
+    #[cfg(not(any(target_os = "linux", target_os = "android")))]
+    fn flags(stat: &libc::stat) -> u64 { stat.st_flags as u64 }
+    #[cfg(any(target_os = "linux", target_os = "android"))]
+    fn flags(_stat: &libc::stat) -> u64 { 0 }
+
+    #[cfg(not(any(target_os = "linux", target_os = "android")))]
+    fn gen(stat: &libc::stat) -> u64 { stat.st_gen as u64 }
+    #[cfg(any(target_os = "linux", target_os = "android"))]
+    fn gen(_stat: &libc::stat) -> u64 { 0 }
+
+    FileStat {
+        size: stat.st_size as u64,
+        kind: match (stat.st_mode as libc::mode_t) & libc::S_IFMT {
+            libc::S_IFREG => io::TypeFile,
+            libc::S_IFDIR => io::TypeDirectory,
+            libc::S_IFIFO => io::TypeNamedPipe,
+            libc::S_IFBLK => io::TypeBlockSpecial,
+            libc::S_IFLNK => io::TypeSymlink,
+            _ => io::TypeUnknown,
+        },
+        perm: FilePermission::from_bits_truncate(stat.st_mode as u32),
+        created: mktime(stat.st_ctime as u64, stat.st_ctime_nsec as u64),
+        modified: mktime(stat.st_mtime as u64, stat.st_mtime_nsec as u64),
+        accessed: mktime(stat.st_atime as u64, stat.st_atime_nsec as u64),
+        unstable: UnstableFileStat {
+            device: stat.st_dev as u64,
+            inode: stat.st_ino as u64,
+            rdev: stat.st_rdev as u64,
+            nlink: stat.st_nlink as u64,
+            uid: stat.st_uid as u64,
+            gid: stat.st_gid as u64,
+            blksize: stat.st_blksize as u64,
+            blocks: stat.st_blocks as u64,
+            flags: flags(stat),
+            gen: gen(stat),
+        },
+    }
+}
+
+pub fn stat(p: &Path) -> IoResult<FileStat> {
+    let p = p.to_c_str();
+    let mut stat: libc::stat = unsafe { mem::zeroed() };
+    match unsafe { libc::stat(p.as_ptr(), &mut stat) } {
+        0 => Ok(mkstat(&stat)),
+        _ => Err(super::last_error()),
+    }
+}
+
+pub fn lstat(p: &Path) -> IoResult<FileStat> {
+    let p = p.to_c_str();
+    let mut stat: libc::stat = unsafe { mem::zeroed() };
+    match unsafe { libc::lstat(p.as_ptr(), &mut stat) } {
+        0 => Ok(mkstat(&stat)),
+        _ => Err(super::last_error()),
+    }
+}
+
+pub fn utime(p: &Path, atime: u64, mtime: u64) -> IoResult<()> {
+    let p = p.to_c_str();
+    let buf = libc::utimbuf {
+        actime: (atime / 1000) as libc::time_t,
+        modtime: (mtime / 1000) as libc::time_t,
+    };
+    mkerr_libc(unsafe { libc::utime(p.as_ptr(), &buf) })
+}
+
+#[cfg(test)]
+mod tests {
+    use super::FileDesc;
+    use libc;
+    use os;
+    use prelude::*;
+
+    #[cfg_attr(target_os = "freebsd", ignore)] // hmm, maybe pipes have a tiny buffer
+    #[test]
+    fn test_file_desc() {
+        // Run this test with some pipes so we don't have to mess around with
+        // opening or closing files.
+        let os::Pipe { reader, writer } = unsafe { os::pipe().unwrap() };
+        let mut reader = FileDesc::new(reader, true);
+        let mut writer = FileDesc::new(writer, true);
+
+        writer.write(b"test").ok().unwrap();
+        let mut buf = [0u8, ..4];
+        match reader.read(buf) {
+            Ok(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 => panic!("invalid read: {}", r),
+        }
+
+        assert!(writer.read(buf).is_err());
+        assert!(reader.write(buf).is_err());
+    }
+}
diff --git a/src/libstd/sys/unix/mod.rs b/src/libstd/sys/unix/mod.rs
index 6295864e0e1..b404dc7fdbd 100644
--- a/src/libstd/sys/unix/mod.rs
+++ b/src/libstd/sys/unix/mod.rs
@@ -17,7 +17,6 @@ use prelude::*;
 use io::{mod, IoResult, IoError};
 use sys_common::mkerr_libc;
 
-
 macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => (
     static $name: Helper<$m> = Helper {
         lock: ::rt::mutex::NATIVE_MUTEX_INIT,
@@ -34,6 +33,7 @@ pub mod tcp;
 pub mod udp;
 pub mod pipe;
 pub mod helper_signal;
+pub mod process;
 
 pub mod addrinfo {
     pub use sys_common::net::get_host_addresses;
diff --git a/src/libstd/sys/unix/process.rs b/src/libstd/sys/unix/process.rs
new file mode 100644
index 00000000000..0965d98d9b0
--- /dev/null
+++ b/src/libstd/sys/unix/process.rs
@@ -0,0 +1,587 @@
+// Copyright 2014 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::{mod, pid_t, c_void, c_int};
+use c_str::CString;
+use io::{mod, IoResult, IoError};
+use mem;
+use os;
+use ptr;
+use prelude::*;
+use io::process::{ProcessExit, ExitStatus, ExitSignal};
+use collections;
+use path::BytesContainer;
+use hash::Hash;
+
+use sys::{mod, retry, c, wouldblock, set_nonblocking, ms_to_timeval};
+use sys::fs::FileDesc;
+use sys_common::helper_thread::Helper;
+use sys_common::{AsFileDesc, mkerr_libc, timeout};
+
+pub use sys_common::ProcessConfig;
+
+helper_init!(static HELPER: Helper<Req>)
+
+/// The unique id of the process (this should never be negative).
+pub struct Process {
+    pub pid: pid_t
+}
+
+enum Req {
+    NewChild(libc::pid_t, Sender<ProcessExit>, u64),
+}
+
+impl Process {
+    pub fn id(&self) -> pid_t {
+        self.pid
+    }
+
+    pub unsafe fn kill(&self, signal: int) -> IoResult<()> {
+        Process::killpid(self.pid, signal)
+    }
+
+    pub unsafe fn killpid(pid: pid_t, signal: int) -> IoResult<()> {
+        let r = libc::funcs::posix88::signal::kill(pid, signal as c_int);
+        mkerr_libc(r)
+    }
+
+    pub fn spawn<K, V, C, P>(cfg: &C, in_fd: Option<P>,
+                              out_fd: Option<P>, err_fd: Option<P>)
+                              -> IoResult<Process>
+        where C: ProcessConfig<K, V>, P: AsFileDesc,
+              K: BytesContainer + Eq + Hash, V: BytesContainer
+    {
+        use libc::funcs::posix88::unistd::{fork, dup2, close, chdir, execvp};
+        use libc::funcs::bsd44::getdtablesize;
+
+        mod rustrt {
+            extern {
+                pub fn rust_unset_sigprocmask();
+            }
+        }
+
+        #[cfg(target_os = "macos")]
+        unsafe fn set_environ(envp: *const c_void) {
+            extern { fn _NSGetEnviron() -> *mut *const c_void; }
+
+            *_NSGetEnviron() = envp;
+        }
+        #[cfg(not(target_os = "macos"))]
+        unsafe fn set_environ(envp: *const c_void) {
+            extern { static mut environ: *const c_void; }
+            environ = envp;
+        }
+
+        unsafe fn set_cloexec(fd: c_int) {
+            let ret = c::ioctl(fd, c::FIOCLEX);
+            assert_eq!(ret, 0);
+        }
+
+        let dirp = cfg.cwd().map(|c| c.as_ptr()).unwrap_or(ptr::null());
+
+        // temporary until unboxed closures land
+        let cfg = unsafe {
+            mem::transmute::<&ProcessConfig<K,V>,&'static ProcessConfig<K,V>>(cfg)
+        };
+
+        with_envp(cfg.env(), proc(envp) {
+            with_argv(cfg.program(), cfg.args(), proc(argv) unsafe {
+                let (input, mut output) = try!(sys::os::pipe());
+
+                // We may use this in the child, so perform allocations before the
+                // fork
+                let devnull = "/dev/null".to_c_str();
+
+                set_cloexec(output.fd());
+
+                let pid = fork();
+                if pid < 0 {
+                    return Err(super::last_error())
+                } else if pid > 0 {
+                    drop(output);
+                    let mut bytes = [0, ..4];
+                    return match input.read(bytes) {
+                        Ok(4) => {
+                            let errno = (bytes[0] as i32 << 24) |
+                                        (bytes[1] as i32 << 16) |
+                                        (bytes[2] as i32 <<  8) |
+                                        (bytes[3] as i32 <<  0);
+                            Err(super::decode_error(errno))
+                        }
+                        Err(..) => Ok(Process { pid: pid }),
+                        Ok(..) => panic!("short read on the cloexec pipe"),
+                    };
+                }
+
+                // And at this point we've reached a special time in the life of the
+                // child. The child must now be considered hamstrung and unable to
+                // do anything other than syscalls really. Consider the following
+                // scenario:
+                //
+                //      1. Thread A of process 1 grabs the malloc() mutex
+                //      2. Thread B of process 1 forks(), creating thread C
+                //      3. Thread C of process 2 then attempts to malloc()
+                //      4. The memory of process 2 is the same as the memory of
+                //         process 1, so the mutex is locked.
+                //
+                // This situation looks a lot like deadlock, right? It turns out
+                // that this is what pthread_atfork() takes care of, which is
+                // presumably implemented across platforms. The first thing that
+                // threads to *before* forking is to do things like grab the malloc
+                // mutex, and then after the fork they unlock it.
+                //
+                // Despite this information, libnative's spawn has been witnessed to
+                // deadlock on both OSX and FreeBSD. I'm not entirely sure why, but
+                // all collected backtraces point at malloc/free traffic in the
+                // child spawned process.
+                //
+                // For this reason, the block of code below should contain 0
+                // invocations of either malloc of free (or their related friends).
+                //
+                // As an example of not having malloc/free traffic, we don't close
+                // this file descriptor by dropping the FileDesc (which contains an
+                // allocation). Instead we just close it manually. This will never
+                // have the drop glue anyway because this code never returns (the
+                // child will either exec() or invoke libc::exit)
+                let _ = libc::close(input.fd());
+
+                fn fail(output: &mut FileDesc) -> ! {
+                    let errno = sys::os::errno();
+                    let bytes = [
+                        (errno >> 24) as u8,
+                        (errno >> 16) as u8,
+                        (errno >>  8) as u8,
+                        (errno >>  0) as u8,
+                    ];
+                    assert!(output.write(bytes).is_ok());
+                    unsafe { libc::_exit(1) }
+                }
+
+                rustrt::rust_unset_sigprocmask();
+
+                // If a stdio file descriptor is set to be ignored (via a -1 file
+                // descriptor), then we don't actually close it, but rather open
+                // up /dev/null into that file descriptor. Otherwise, the first file
+                // descriptor opened up in the child would be numbered as one of the
+                // stdio file descriptors, which is likely to wreak havoc.
+                let setup = |src: Option<P>, dst: c_int| {
+                    let src = match src {
+                        None => {
+                            let flags = if dst == libc::STDIN_FILENO {
+                                libc::O_RDONLY
+                            } else {
+                                libc::O_RDWR
+                            };
+                            libc::open(devnull.as_ptr(), flags, 0)
+                        }
+                        Some(obj) => {
+                            let fd = obj.as_fd().fd();
+                            // Leak the memory and the file descriptor. We're in the
+                            // child now an all our resources are going to be
+                            // cleaned up very soon
+                            mem::forget(obj);
+                            fd
+                        }
+                    };
+                    src != -1 && retry(|| dup2(src, dst)) != -1
+                };
+
+                if !setup(in_fd, libc::STDIN_FILENO) { fail(&mut output) }
+                if !setup(out_fd, libc::STDOUT_FILENO) { fail(&mut output) }
+                if !setup(err_fd, libc::STDERR_FILENO) { fail(&mut output) }
+
+                // close all other fds
+                for fd in range(3, getdtablesize()).rev() {
+                    if fd != output.fd() {
+                        let _ = close(fd as c_int);
+                    }
+                }
+
+                match cfg.gid() {
+                    Some(u) => {
+                        if libc::setgid(u as libc::gid_t) != 0 {
+                            fail(&mut output);
+                        }
+                    }
+                    None => {}
+                }
+                match cfg.uid() {
+                    Some(u) => {
+                        // When dropping privileges from root, the `setgroups` call
+                        // will remove any extraneous groups. If we don't call this,
+                        // then even though our uid has dropped, we may still have
+                        // groups that enable us to do super-user things. This will
+                        // fail if we aren't root, so don't bother checking the
+                        // return value, this is just done as an optimistic
+                        // privilege dropping function.
+                        extern {
+                            fn setgroups(ngroups: libc::c_int,
+                                         ptr: *const libc::c_void) -> libc::c_int;
+                        }
+                        let _ = setgroups(0, 0 as *const libc::c_void);
+
+                        if libc::setuid(u as libc::uid_t) != 0 {
+                            fail(&mut output);
+                        }
+                    }
+                    None => {}
+                }
+                if cfg.detach() {
+                    // Don't check the error of setsid because it fails if we're the
+                    // process leader already. We just forked so it shouldn't return
+                    // error, but ignore it anyway.
+                    let _ = libc::setsid();
+                }
+                if !dirp.is_null() && chdir(dirp) == -1 {
+                    fail(&mut output);
+                }
+                if !envp.is_null() {
+                    set_environ(envp);
+                }
+                let _ = execvp(*argv, argv as *mut _);
+                fail(&mut output);
+            })
+        })
+    }
+
+    pub fn wait(&self, deadline: u64) -> IoResult<ProcessExit> {
+        use std::cmp;
+        use std::comm;
+
+        static mut WRITE_FD: libc::c_int = 0;
+
+        let mut status = 0 as c_int;
+        if deadline == 0 {
+            return match retry(|| unsafe { c::waitpid(self.pid, &mut status, 0) }) {
+                -1 => panic!("unknown waitpid error: {}", super::last_error()),
+                _ => Ok(translate_status(status)),
+            }
+        }
+
+        // On unix, wait() and its friends have no timeout parameters, so there is
+        // no way to time out a thread in wait(). From some googling and some
+        // thinking, it appears that there are a few ways to handle timeouts in
+        // wait(), but the only real reasonable one for a multi-threaded program is
+        // to listen for SIGCHLD.
+        //
+        // With this in mind, the waiting mechanism with a timeout barely uses
+        // waitpid() at all. There are a few times that waitpid() is invoked with
+        // WNOHANG, but otherwise all the necessary blocking is done by waiting for
+        // a SIGCHLD to arrive (and that blocking has a timeout). Note, however,
+        // that waitpid() is still used to actually reap the child.
+        //
+        // Signal handling is super tricky in general, and this is no exception. Due
+        // to the async nature of SIGCHLD, we use the self-pipe trick to transmit
+        // data out of the signal handler to the rest of the application. The first
+        // idea would be to have each thread waiting with a timeout to read this
+        // output file descriptor, but a write() is akin to a signal(), not a
+        // broadcast(), so it would only wake up one thread, and possibly the wrong
+        // thread. Hence a helper thread is used.
+        //
+        // The helper thread here is responsible for farming requests for a
+        // waitpid() with a timeout, and then processing all of the wait requests.
+        // By guaranteeing that only this helper thread is reading half of the
+        // self-pipe, we're sure that we'll never lose a SIGCHLD. This helper thread
+        // is also responsible for select() to wait for incoming messages or
+        // incoming SIGCHLD messages, along with passing an appropriate timeout to
+        // select() to wake things up as necessary.
+        //
+        // The ordering of the following statements is also very purposeful. First,
+        // we must be guaranteed that the helper thread is booted and available to
+        // receive SIGCHLD signals, and then we must also ensure that we do a
+        // nonblocking waitpid() at least once before we go ask the sigchld helper.
+        // This prevents the race where the child exits, we boot the helper, and
+        // then we ask for the child's exit status (never seeing a sigchld).
+        //
+        // The actual communication between the helper thread and this thread is
+        // quite simple, just a channel moving data around.
+
+        unsafe { HELPER.boot(register_sigchld, waitpid_helper) }
+
+        match self.try_wait() {
+            Some(ret) => return Ok(ret),
+            None => {}
+        }
+
+        let (tx, rx) = channel();
+        unsafe { HELPER.send(NewChild(self.pid, tx, deadline)); }
+        return match rx.recv_opt() {
+            Ok(e) => Ok(e),
+            Err(()) => Err(timeout("wait timed out")),
+        };
+
+        // Register a new SIGCHLD handler, returning the reading half of the
+        // self-pipe plus the old handler registered (return value of sigaction).
+        //
+        // Be sure to set up the self-pipe first because as soon as we register a
+        // handler we're going to start receiving signals.
+        fn register_sigchld() -> (libc::c_int, c::sigaction) {
+            unsafe {
+                let mut pipes = [0, ..2];
+                assert_eq!(libc::pipe(pipes.as_mut_ptr()), 0);
+                set_nonblocking(pipes[0], true).ok().unwrap();
+                set_nonblocking(pipes[1], true).ok().unwrap();
+                WRITE_FD = pipes[1];
+
+                let mut old: c::sigaction = mem::zeroed();
+                let mut new: c::sigaction = mem::zeroed();
+                new.sa_handler = sigchld_handler;
+                new.sa_flags = c::SA_NOCLDSTOP;
+                assert_eq!(c::sigaction(c::SIGCHLD, &new, &mut old), 0);
+                (pipes[0], old)
+            }
+        }
+
+        // Helper thread for processing SIGCHLD messages
+        fn waitpid_helper(input: libc::c_int,
+                          messages: Receiver<Req>,
+                          (read_fd, old): (libc::c_int, c::sigaction)) {
+            set_nonblocking(input, true).ok().unwrap();
+            let mut set: c::fd_set = unsafe { mem::zeroed() };
+            let mut tv: libc::timeval;
+            let mut active = Vec::<(libc::pid_t, Sender<ProcessExit>, u64)>::new();
+            let max = cmp::max(input, read_fd) + 1;
+
+            'outer: loop {
+                // Figure out the timeout of our syscall-to-happen. If we're waiting
+                // for some processes, then they'll have a timeout, otherwise we
+                // wait indefinitely for a message to arrive.
+                //
+                // FIXME: sure would be nice to not have to scan the entire array
+                let min = active.iter().map(|a| *a.ref2()).enumerate().min_by(|p| {
+                    p.val1()
+                });
+                let (p, idx) = match min {
+                    Some((idx, deadline)) => {
+                        let now = sys::timer::now();
+                        let ms = if now < deadline {deadline - now} else {0};
+                        tv = ms_to_timeval(ms);
+                        (&mut tv as *mut _, idx)
+                    }
+                    None => (ptr::null_mut(), -1),
+                };
+
+                // Wait for something to happen
+                c::fd_set(&mut set, input);
+                c::fd_set(&mut set, read_fd);
+                match unsafe { c::select(max, &mut set, ptr::null_mut(),
+                                         ptr::null_mut(), p) } {
+                    // interrupted, retry
+                    -1 if os::errno() == libc::EINTR as uint => continue,
+
+                    // We read something, break out and process
+                    1 | 2 => {}
+
+                    // Timeout, the pending request is removed
+                    0 => {
+                        drop(active.remove(idx));
+                        continue
+                    }
+
+                    n => panic!("error in select {} ({})", os::errno(), n),
+                }
+
+                // Process any pending messages
+                if drain(input) {
+                    loop {
+                        match messages.try_recv() {
+                            Ok(NewChild(pid, tx, deadline)) => {
+                                active.push((pid, tx, deadline));
+                            }
+                            Err(comm::Disconnected) => {
+                                assert!(active.len() == 0);
+                                break 'outer;
+                            }
+                            Err(comm::Empty) => break,
+                        }
+                    }
+                }
+
+                // If a child exited (somehow received SIGCHLD), then poll all
+                // children to see if any of them exited.
+                //
+                // We also attempt to be responsible netizens when dealing with
+                // SIGCHLD by invoking any previous SIGCHLD handler instead of just
+                // ignoring any previous SIGCHLD handler. Note that we don't provide
+                // a 1:1 mapping of our handler invocations to the previous handler
+                // invocations because we drain the `read_fd` entirely. This is
+                // probably OK because the kernel is already allowed to coalesce
+                // simultaneous signals, we're just doing some extra coalescing.
+                //
+                // Another point of note is that this likely runs the signal handler
+                // on a different thread than the one that received the signal. I
+                // *think* this is ok at this time.
+                //
+                // The main reason for doing this is to allow stdtest to run native
+                // tests as well. Both libgreen and libnative are running around
+                // with process timeouts, but libgreen should get there first
+                // (currently libuv doesn't handle old signal handlers).
+                if drain(read_fd) {
+                    let i: uint = unsafe { mem::transmute(old.sa_handler) };
+                    if i != 0 {
+                        assert!(old.sa_flags & c::SA_SIGINFO == 0);
+                        (old.sa_handler)(c::SIGCHLD);
+                    }
+
+                    // FIXME: sure would be nice to not have to scan the entire
+                    //        array...
+                    active.retain(|&(pid, ref tx, _)| {
+                        let pr = Process { pid: pid };
+                        match pr.try_wait() {
+                            Some(msg) => { tx.send(msg); false }
+                            None => true,
+                        }
+                    });
+                }
+            }
+
+            // Once this helper thread is done, we re-register the old sigchld
+            // handler and close our intermediate file descriptors.
+            unsafe {
+                assert_eq!(c::sigaction(c::SIGCHLD, &old, ptr::null_mut()), 0);
+                let _ = libc::close(read_fd);
+                let _ = libc::close(WRITE_FD);
+                WRITE_FD = -1;
+            }
+        }
+
+        // Drain all pending data from the file descriptor, returning if any data
+        // could be drained. This requires that the file descriptor is in
+        // nonblocking mode.
+        fn drain(fd: libc::c_int) -> bool {
+            let mut ret = false;
+            loop {
+                let mut buf = [0u8, ..1];
+                match unsafe {
+                    libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void,
+                               buf.len() as libc::size_t)
+                } {
+                    n if n > 0 => { ret = true; }
+                    0 => return true,
+                    -1 if wouldblock() => return ret,
+                    n => panic!("bad read {} ({})", os::last_os_error(), n),
+                }
+            }
+        }
+
+        // Signal handler for SIGCHLD signals, must be async-signal-safe!
+        //
+        // This function will write to the writing half of the "self pipe" to wake
+        // up the helper thread if it's waiting. Note that this write must be
+        // nonblocking because if it blocks and the reader is the thread we
+        // interrupted, then we'll deadlock.
+        //
+        // When writing, if the write returns EWOULDBLOCK then we choose to ignore
+        // it. At that point we're guaranteed that there's something in the pipe
+        // which will wake up the other end at some point, so we just allow this
+        // signal to be coalesced with the pending signals on the pipe.
+        extern fn sigchld_handler(_signum: libc::c_int) {
+            let msg = 1i;
+            match unsafe {
+                libc::write(WRITE_FD, &msg as *const _ as *const libc::c_void, 1)
+            } {
+                1 => {}
+                -1 if wouldblock() => {} // see above comments
+                n => panic!("bad error on write fd: {} {}", n, os::errno()),
+            }
+        }
+    }
+
+    pub fn try_wait(&self) -> Option<ProcessExit> {
+        let mut status = 0 as c_int;
+        match retry(|| unsafe {
+            c::waitpid(self.pid, &mut status, c::WNOHANG)
+        }) {
+            n if n == self.pid => Some(translate_status(status)),
+            0 => None,
+            n => panic!("unknown waitpid error `{}`: {}", n,
+                       super::last_error()),
+        }
+    }
+}
+
+fn with_argv<T>(prog: &CString, args: &[CString],
+                cb: proc(*const *const libc::c_char) -> T) -> T {
+    let mut ptrs: Vec<*const libc::c_char> = Vec::with_capacity(args.len()+1);
+
+    // Convert the CStrings into an array of pointers. Note: the
+    // lifetime of the various CStrings involved is guaranteed to be
+    // larger than the lifetime of our invocation of cb, but this is
+    // technically unsafe as the callback could leak these pointers
+    // out of our scope.
+    ptrs.push(prog.as_ptr());
+    ptrs.extend(args.iter().map(|tmp| tmp.as_ptr()));
+
+    // Add a terminating null pointer (required by libc).
+    ptrs.push(ptr::null());
+
+    cb(ptrs.as_ptr())
+}
+
+fn with_envp<K, V, T>(env: Option<&collections::HashMap<K, V>>,
+                      cb: proc(*const c_void) -> T) -> T
+    where K: BytesContainer + Eq + Hash, V: BytesContainer
+{
+    // On posixy systems we can pass a char** for envp, which is a
+    // null-terminated array of "k=v\0" strings. Since we must create
+    // these strings locally, yet expose a raw pointer to them, we
+    // create a temporary vector to own the CStrings that outlives the
+    // call to cb.
+    match env {
+        Some(env) => {
+            let mut tmps = Vec::with_capacity(env.len());
+
+            for pair in env.iter() {
+                let mut kv = Vec::new();
+                kv.push_all(pair.ref0().container_as_bytes());
+                kv.push('=' as u8);
+                kv.push_all(pair.ref1().container_as_bytes());
+                kv.push(0); // terminating null
+                tmps.push(kv);
+            }
+
+            // As with `with_argv`, this is unsafe, since cb could leak the pointers.
+            let mut ptrs: Vec<*const libc::c_char> =
+                tmps.iter()
+                    .map(|tmp| tmp.as_ptr() as *const libc::c_char)
+                    .collect();
+            ptrs.push(ptr::null());
+
+            cb(ptrs.as_ptr() as *const c_void)
+        }
+        _ => cb(ptr::null())
+    }
+}
+
+fn translate_status(status: c_int) -> ProcessExit {
+    #![allow(non_snake_case)]
+    #[cfg(any(target_os = "linux", target_os = "android"))]
+    mod imp {
+        pub fn WIFEXITED(status: i32) -> bool { (status & 0xff) == 0 }
+        pub fn WEXITSTATUS(status: i32) -> i32 { (status >> 8) & 0xff }
+        pub fn WTERMSIG(status: i32) -> i32 { status & 0x7f }
+    }
+
+    #[cfg(any(target_os = "macos",
+              target_os = "ios",
+              target_os = "freebsd",
+              target_os = "dragonfly"))]
+    mod imp {
+        pub fn WIFEXITED(status: i32) -> bool { (status & 0x7f) == 0 }
+        pub fn WEXITSTATUS(status: i32) -> i32 { status >> 8 }
+        pub fn WTERMSIG(status: i32) -> i32 { status & 0o177 }
+    }
+
+    if imp::WIFEXITED(status) {
+        ExitStatus(imp::WEXITSTATUS(status) as int)
+    } else {
+        ExitSignal(imp::WTERMSIG(status) as int)
+    }
+}
diff --git a/src/libstd/sys/windows/fs.rs b/src/libstd/sys/windows/fs.rs
new file mode 100644
index 00000000000..a07688b2fed
--- /dev/null
+++ b/src/libstd/sys/windows/fs.rs
@@ -0,0 +1,460 @@
+// Copyright 2014 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.
+
+//! Blocking Windows-based file I/O
+
+use alloc::arc::Arc;
+use libc::{mod, c_int};
+
+use c_str::CString;
+use mem;
+use os::windows::fill_utf16_buf_and_decode;
+use path;
+use ptr;
+use str;
+use io;
+
+use prelude::*;
+use sys;
+use sys_common::{keep_going, eof, mkerr_libc};
+
+use io::{FilePermission, Write, UnstableFileStat, Open, FileAccess, FileMode};
+use io::{IoResult, IoError, FileStat, SeekStyle, Seek, Writer, Reader};
+use io::{Read, Truncate, SeekCur, SeekSet, ReadWrite, SeekEnd, Append};
+
+pub use path::WindowsPath as Path;
+pub type fd_t = libc::c_int;
+
+pub struct FileDesc {
+    /// The underlying C file descriptor.
+    pub fd: fd_t,
+
+    /// Whether to close the file descriptor on drop.
+    close_on_drop: bool,
+}
+
+impl FileDesc {
+    pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc {
+        FileDesc { fd: fd, close_on_drop: close_on_drop }
+    }
+
+    pub fn read(&self, buf: &mut [u8]) -> IoResult<uint> {
+        let mut read = 0;
+        let ret = unsafe {
+            libc::ReadFile(self.handle(), buf.as_ptr() as libc::LPVOID,
+                           buf.len() as libc::DWORD, &mut read,
+                           ptr::null_mut())
+        };
+        if ret != 0 {
+            Ok(read as uint)
+        } else {
+            Err(super::last_error())
+        }
+    }
+
+    pub fn write(&self, buf: &[u8]) -> IoResult<()> {
+        let mut cur = buf.as_ptr();
+        let mut remaining = buf.len();
+        while remaining > 0 {
+            let mut amt = 0;
+            let ret = unsafe {
+                libc::WriteFile(self.handle(), cur as libc::LPVOID,
+                                remaining as libc::DWORD, &mut amt,
+                                ptr::null_mut())
+            };
+            if ret != 0 {
+                remaining -= amt as uint;
+                cur = unsafe { cur.offset(amt as int) };
+            } else {
+                return Err(super::last_error())
+            }
+        }
+        Ok(())
+    }
+
+    pub fn fd(&self) -> fd_t { self.fd }
+
+    pub fn handle(&self) -> libc::HANDLE {
+        unsafe { libc::get_osfhandle(self.fd()) as libc::HANDLE }
+    }
+
+    // A version of seek that takes &self so that tell can call it
+    //   - the private seek should of course take &mut self.
+    fn seek_common(&self, pos: i64, style: SeekStyle) -> IoResult<u64> {
+        let whence = match style {
+            SeekSet => libc::FILE_BEGIN,
+            SeekEnd => libc::FILE_END,
+            SeekCur => libc::FILE_CURRENT,
+        };
+        unsafe {
+            let mut newpos = 0;
+            match libc::SetFilePointerEx(self.handle(), pos, &mut newpos, whence) {
+                0 => Err(super::last_error()),
+                _ => Ok(newpos as u64),
+            }
+        }
+    }
+
+    pub fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<u64> {
+        self.seek_common(pos, style)
+    }
+
+    pub fn tell(&self) -> IoResult<u64> {
+        self.seek_common(0, SeekCur)
+    }
+
+    pub fn fsync(&mut self) -> IoResult<()> {
+        super::mkerr_winbool(unsafe {
+            libc::FlushFileBuffers(self.handle())
+        })
+    }
+
+    pub fn datasync(&mut self) -> IoResult<()> { return self.fsync(); }
+
+    pub fn truncate(&mut self, offset: i64) -> IoResult<()> {
+        let orig_pos = try!(self.tell());
+        let _ = try!(self.seek(offset, SeekSet));
+        let ret = unsafe {
+            match libc::SetEndOfFile(self.handle()) {
+                0 => Err(super::last_error()),
+                _ => Ok(())
+            }
+        };
+        let _ = self.seek(orig_pos as i64, SeekSet);
+        return ret;
+    }
+
+    pub fn fstat(&mut self) -> IoResult<io::FileStat> {
+        let mut stat: libc::stat = unsafe { mem::zeroed() };
+        match unsafe { libc::fstat(self.fd(), &mut stat) } {
+            0 => Ok(mkstat(&stat)),
+            _ => Err(super::last_error()),
+        }
+    }
+
+    /// Extract the actual filedescriptor without closing it.
+    pub fn unwrap(self) -> fd_t {
+        let fd = self.fd;
+        unsafe { mem::forget(self) };
+        fd
+    }
+}
+
+impl Drop for FileDesc {
+    fn drop(&mut self) {
+        // closing stdio file handles makes no sense, so never do it. Also, note
+        // that errors are ignored when closing a file descriptor. The reason
+        // for this is that if an error occurs we don't actually know if the
+        // file descriptor was closed or not, and if we retried (for something
+        // like EINTR), we might close another valid file descriptor (opened
+        // after we closed ours.
+        if self.close_on_drop && self.fd > libc::STDERR_FILENO {
+            let n = unsafe { libc::close(self.fd) };
+            if n != 0 {
+                println!("error {} when closing file descriptor {}", n, self.fd);
+            }
+        }
+    }
+}
+
+pub fn to_utf16(s: &Path) -> IoResult<Vec<u16>> {
+    sys::to_utf16(s.as_str())
+}
+
+pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult<FileDesc> {
+    // Flags passed to open_osfhandle
+    let flags = match fm {
+        Open => 0,
+        Append => libc::O_APPEND,
+        Truncate => libc::O_TRUNC,
+    };
+    let flags = match fa {
+        Read => flags | libc::O_RDONLY,
+        Write => flags | libc::O_WRONLY | libc::O_CREAT,
+        ReadWrite => flags | libc::O_RDWR | libc::O_CREAT,
+    };
+    let mut dwDesiredAccess = match fa {
+        Read => libc::FILE_GENERIC_READ,
+        Write => libc::FILE_GENERIC_WRITE,
+        ReadWrite => libc::FILE_GENERIC_READ | libc::FILE_GENERIC_WRITE
+    };
+
+    // libuv has a good comment about this, but the basic idea is what we try to
+    // emulate unix semantics by enabling all sharing by allowing things such as
+    // deleting a file while it's still open.
+    let dwShareMode = libc::FILE_SHARE_READ | libc::FILE_SHARE_WRITE |
+                      libc::FILE_SHARE_DELETE;
+
+    let dwCreationDisposition = match (fm, fa) {
+        (Truncate, Read) => libc::TRUNCATE_EXISTING,
+        (Truncate, _) => libc::CREATE_ALWAYS,
+        (Open, Read) => libc::OPEN_EXISTING,
+        (Open, _) => libc::OPEN_ALWAYS,
+        (Append, Read) => {
+            dwDesiredAccess |= libc::FILE_APPEND_DATA;
+            libc::OPEN_EXISTING
+        }
+        (Append, _) => {
+            dwDesiredAccess &= !libc::FILE_WRITE_DATA;
+            dwDesiredAccess |= libc::FILE_APPEND_DATA;
+            libc::OPEN_ALWAYS
+        }
+    };
+
+    let mut dwFlagsAndAttributes = libc::FILE_ATTRIBUTE_NORMAL;
+    // Compat with unix, this allows opening directories (see libuv)
+    dwFlagsAndAttributes |= libc::FILE_FLAG_BACKUP_SEMANTICS;
+
+    let path = try!(to_utf16(path));
+    let handle = unsafe {
+        libc::CreateFileW(path.as_ptr(),
+                          dwDesiredAccess,
+                          dwShareMode,
+                          ptr::null_mut(),
+                          dwCreationDisposition,
+                          dwFlagsAndAttributes,
+                          ptr::null_mut())
+    };
+    if handle == libc::INVALID_HANDLE_VALUE {
+        Err(super::last_error())
+    } else {
+        let fd = unsafe {
+            libc::open_osfhandle(handle as libc::intptr_t, flags)
+        };
+        if fd < 0 {
+            let _ = unsafe { libc::CloseHandle(handle) };
+            Err(super::last_error())
+        } else {
+            Ok(FileDesc::new(fd, true))
+        }
+    }
+}
+
+pub fn mkdir(p: &Path, _mode: uint) -> IoResult<()> {
+    let p = try!(to_utf16(p));
+    super::mkerr_winbool(unsafe {
+        // FIXME: turn mode into something useful? #2623
+        libc::CreateDirectoryW(p.as_ptr(), ptr::null_mut())
+    })
+}
+
+pub fn readdir(p: &Path) -> IoResult<Vec<Path>> {
+    fn prune(root: &Path, dirs: Vec<Path>) -> Vec<Path> {
+        dirs.into_iter().filter(|path| {
+            path.as_vec() != b"." && path.as_vec() != b".."
+        }).map(|path| root.join(path)).collect()
+    }
+
+    let star = p.join("*");
+    let path = try!(to_utf16(&star));
+
+    unsafe {
+        let mut wfd = mem::zeroed();
+        let find_handle = libc::FindFirstFileW(path.as_ptr(), &mut wfd);
+        if find_handle != libc::INVALID_HANDLE_VALUE {
+            let mut paths = vec![];
+            let mut more_files = 1 as libc::BOOL;
+            while more_files != 0 {
+                {
+                    let filename = str::truncate_utf16_at_nul(wfd.cFileName);
+                    match String::from_utf16(filename) {
+                        Some(filename) => paths.push(Path::new(filename)),
+                        None => {
+                            assert!(libc::FindClose(find_handle) != 0);
+                            return Err(IoError {
+                                kind: io::InvalidInput,
+                                desc: "path was not valid UTF-16",
+                                detail: Some(format!("path was not valid UTF-16: {}", filename)),
+                            })
+                        }, // FIXME #12056: Convert the UCS-2 to invalid utf-8 instead of erroring
+                    }
+                }
+                more_files = libc::FindNextFileW(find_handle, &mut wfd);
+            }
+            assert!(libc::FindClose(find_handle) != 0);
+            Ok(prune(p, paths))
+        } else {
+            Err(super::last_error())
+        }
+    }
+}
+
+pub fn unlink(p: &Path) -> IoResult<()> {
+    fn do_unlink(p_utf16: &Vec<u16>) -> IoResult<()> {
+        super::mkerr_winbool(unsafe { libc::DeleteFileW(p_utf16.as_ptr()) })
+    }
+
+    let p_utf16 = try!(to_utf16(p));
+    let res = do_unlink(&p_utf16);
+    match res {
+        Ok(()) => Ok(()),
+        Err(e) => {
+            // FIXME: change the code below to use more direct calls
+            // than `stat` and `chmod`, to avoid re-conversion to
+            // utf16 etc.
+
+            // On unix, a readonly file can be successfully removed. On windows,
+            // however, it cannot. To keep the two platforms in line with
+            // respect to their behavior, catch this case on windows, attempt to
+            // change it to read-write, and then remove the file.
+            if e.kind == io::PermissionDenied {
+                let stat = match stat(p) {
+                    Ok(stat) => stat,
+                    Err(..) => return Err(e),
+                };
+                if stat.perm.intersects(io::USER_WRITE) { return Err(e) }
+
+                match chmod(p, (stat.perm | io::USER_WRITE).bits() as uint) {
+                    Ok(()) => do_unlink(&p_utf16),
+                    Err(..) => {
+                        // Try to put it back as we found it
+                        let _ = chmod(p, stat.perm.bits() as uint);
+                        Err(e)
+                    }
+                }
+            } else {
+                Err(e)
+            }
+        }
+    }
+}
+
+pub fn rename(old: &Path, new: &Path) -> IoResult<()> {
+    let old = try!(to_utf16(old));
+    let new = try!(to_utf16(new));
+    super::mkerr_winbool(unsafe {
+        libc::MoveFileExW(old.as_ptr(), new.as_ptr(), libc::MOVEFILE_REPLACE_EXISTING)
+    })
+}
+
+pub fn chmod(p: &Path, mode: uint) -> IoResult<()> {
+    let p = try!(to_utf16(p));
+    mkerr_libc(unsafe {
+        libc::wchmod(p.as_ptr(), mode as libc::c_int)
+    })
+}
+
+pub fn rmdir(p: &Path) -> IoResult<()> {
+    let p = try!(to_utf16(p));
+    mkerr_libc(unsafe { libc::wrmdir(p.as_ptr()) })
+}
+
+pub fn chown(_p: &Path, _uid: int, _gid: int) -> IoResult<()> {
+    // libuv has this as a no-op, so seems like this should as well?
+    Ok(())
+}
+
+pub fn readlink(p: &Path) -> IoResult<Path> {
+    // FIXME: I have a feeling that this reads intermediate symlinks as well.
+    use sys::c::compat::kernel32::GetFinalPathNameByHandleW;
+    let p = try!(to_utf16(p));
+    let handle = unsafe {
+        libc::CreateFileW(p.as_ptr(),
+                          libc::GENERIC_READ,
+                          libc::FILE_SHARE_READ,
+                          ptr::null_mut(),
+                          libc::OPEN_EXISTING,
+                          libc::FILE_ATTRIBUTE_NORMAL,
+                          ptr::null_mut())
+    };
+    if handle == libc::INVALID_HANDLE_VALUE {
+        return Err(super::last_error())
+    }
+    // Specify (sz - 1) because the documentation states that it's the size
+    // without the null pointer
+    let ret = fill_utf16_buf_and_decode(|buf, sz| unsafe {
+        GetFinalPathNameByHandleW(handle,
+                                  buf as *const u16,
+                                  sz - 1,
+                                  libc::VOLUME_NAME_DOS)
+    });
+    let ret = match ret {
+        Some(ref s) if s.as_slice().starts_with(r"\\?\") => { // "
+            Ok(Path::new(s.as_slice().slice_from(4)))
+        }
+        Some(s) => Ok(Path::new(s)),
+        None => Err(super::last_error()),
+    };
+    assert!(unsafe { libc::CloseHandle(handle) } != 0);
+    return ret;
+}
+
+pub fn symlink(src: &Path, dst: &Path) -> IoResult<()> {
+    use sys::c::compat::kernel32::CreateSymbolicLinkW;
+    let src = try!(to_utf16(src));
+    let dst = try!(to_utf16(dst));
+    super::mkerr_winbool(unsafe {
+        CreateSymbolicLinkW(dst.as_ptr(), src.as_ptr(), 0) as libc::BOOL
+    })
+}
+
+pub fn link(src: &Path, dst: &Path) -> IoResult<()> {
+    let src = try!(to_utf16(src));
+    let dst = try!(to_utf16(dst));
+    super::mkerr_winbool(unsafe {
+        libc::CreateHardLinkW(dst.as_ptr(), src.as_ptr(), ptr::null_mut())
+    })
+}
+
+fn mkstat(stat: &libc::stat) -> FileStat {
+    FileStat {
+        size: stat.st_size as u64,
+        kind: match (stat.st_mode as libc::c_int) & libc::S_IFMT {
+            libc::S_IFREG => io::TypeFile,
+            libc::S_IFDIR => io::TypeDirectory,
+            libc::S_IFIFO => io::TypeNamedPipe,
+            libc::S_IFBLK => io::TypeBlockSpecial,
+            libc::S_IFLNK => io::TypeSymlink,
+            _ => io::TypeUnknown,
+        },
+        perm: FilePermission::from_bits_truncate(stat.st_mode as u32),
+        created: stat.st_ctime as u64,
+        modified: stat.st_mtime as u64,
+        accessed: stat.st_atime as u64,
+        unstable: UnstableFileStat {
+            device: stat.st_dev as u64,
+            inode: stat.st_ino as u64,
+            rdev: stat.st_rdev as u64,
+            nlink: stat.st_nlink as u64,
+            uid: stat.st_uid as u64,
+            gid: stat.st_gid as u64,
+            blksize:0,
+            blocks: 0,
+            flags: 0,
+            gen: 0,
+        },
+    }
+}
+
+pub fn stat(p: &Path) -> IoResult<FileStat> {
+    let mut stat: libc::stat = unsafe { mem::zeroed() };
+    let p = try!(to_utf16(p));
+    match unsafe { libc::wstat(p.as_ptr(), &mut stat) } {
+        0 => Ok(mkstat(&stat)),
+        _ => Err(super::last_error()),
+    }
+}
+
+// FIXME: move this to platform-specific modules (for now)?
+pub fn lstat(_p: &Path) -> IoResult<FileStat> {
+    // FIXME: implementation is missing
+    Err(super::unimpl())
+}
+
+pub fn utime(p: &Path, atime: u64, mtime: u64) -> IoResult<()> {
+    let mut buf = libc::utimbuf {
+        actime: atime as libc::time64_t,
+        modtime: mtime as libc::time64_t,
+    };
+    let p = try!(to_utf16(p));
+    mkerr_libc(unsafe {
+        libc::wutime(p.as_ptr(), &mut buf)
+    })
+}
diff --git a/src/libstd/sys/windows/mod.rs b/src/libstd/sys/windows/mod.rs
index 6f6ca3f2e62..f50244701e4 100644
--- a/src/libstd/sys/windows/mod.rs
+++ b/src/libstd/sys/windows/mod.rs
@@ -40,6 +40,7 @@ pub mod tcp;
 pub mod udp;
 pub mod pipe;
 pub mod helper_signal;
+pub mod process;
 
 pub mod addrinfo {
     pub use sys_common::net::get_host_addresses;
diff --git a/src/libstd/sys/windows/process.rs b/src/libstd/sys/windows/process.rs
new file mode 100644
index 00000000000..67e87841ed2
--- /dev/null
+++ b/src/libstd/sys/windows/process.rs
@@ -0,0 +1,511 @@
+// Copyright 2012-2014 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::{pid_t, c_void, c_int};
+use libc;
+use c_str::CString;
+use io;
+use mem;
+use os;
+use ptr;
+use prelude::*;
+use io::process::{ProcessExit, ExitStatus, ExitSignal};
+use collections;
+use path::BytesContainer;
+use hash::Hash;
+use io::{IoResult, IoError};
+
+use sys::fs;
+use sys::{mod, retry, c, wouldblock, set_nonblocking, ms_to_timeval, timer};
+use sys::fs::FileDesc;
+use sys_common::helper_thread::Helper;
+use sys_common::{AsFileDesc, mkerr_libc, timeout};
+
+use io::fs::PathExtensions;
+use string::String;
+
+pub use sys_common::ProcessConfig;
+
+/**
+ * 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).
+    pid: pid_t,
+
+    /// A HANDLE to the process, which will prevent the pid being
+    /// re-used until the handle is closed.
+    handle: *mut (),
+}
+
+impl Drop for Process {
+    fn drop(&mut self) {
+        free_handle(self.handle);
+    }
+}
+
+impl Process {
+    pub fn id(&self) -> pid_t {
+        self.pid
+    }
+
+    pub unsafe fn kill(&self, signal: int) -> IoResult<()> {
+        Process::killpid(self.pid, signal)
+    }
+
+    pub unsafe fn killpid(pid: pid_t, signal: int) -> IoResult<()> {
+        let handle = libc::OpenProcess(libc::PROCESS_TERMINATE |
+                                       libc::PROCESS_QUERY_INFORMATION,
+                                       libc::FALSE, pid as libc::DWORD);
+        if handle.is_null() {
+            return Err(super::last_error())
+        }
+        let ret = match signal {
+            // test for existence on signal 0
+            0 => {
+                let mut status = 0;
+                let ret = libc::GetExitCodeProcess(handle, &mut status);
+                if ret == 0 {
+                    Err(super::last_error())
+                } else if status != libc::STILL_ACTIVE {
+                    Err(IoError {
+                        kind: io::InvalidInput,
+                        desc: "no process to kill",
+                        detail: None,
+                    })
+                } else {
+                    Ok(())
+                }
+            }
+            15 | 9 => { // sigterm or sigkill
+                let ret = libc::TerminateProcess(handle, 1);
+                super::mkerr_winbool(ret)
+            }
+            _ => Err(IoError {
+                kind: io::IoUnavailable,
+                desc: "unsupported signal on windows",
+                detail: None,
+            })
+        };
+        let _ = libc::CloseHandle(handle);
+        return ret;
+    }
+
+    pub fn spawn<K, V, C, P>(cfg: &C, in_fd: Option<P>,
+                              out_fd: Option<P>, err_fd: Option<P>)
+                              -> IoResult<Process>
+        where C: ProcessConfig<K, V>, P: AsFileDesc,
+              K: BytesContainer + Eq + Hash, V: BytesContainer
+    {
+        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,
+            CreateProcessW
+        };
+        use libc::funcs::extra::msvcrt::get_osfhandle;
+
+        use mem;
+        use iter::Iterator;
+        use str::StrPrelude;
+
+        if cfg.gid().is_some() || cfg.uid().is_some() {
+            return Err(IoError {
+                kind: io::IoUnavailable,
+                desc: "unsupported gid/uid requested on windows",
+                detail: None,
+            })
+        }
+
+        // To have the spawning semantics of unix/windows stay the same, we need to
+        // read the *child's* PATH if one is provided. See #15149 for more details.
+        let program = cfg.env().and_then(|env| {
+            for (key, v) in env.iter() {
+                if b"PATH" != key.container_as_bytes() { continue }
+
+                // Split the value and test each path to see if the
+                // program exists.
+                for path in os::split_paths(v.container_as_bytes()).into_iter() {
+                    let path = path.join(cfg.program().as_bytes_no_nul())
+                                   .with_extension(os::consts::EXE_EXTENSION);
+                    if path.exists() {
+                        return Some(path.to_c_str())
+                    }
+                }
+                break
+            }
+            None
+        });
+
+        unsafe {
+            let mut si = zeroed_startupinfo();
+            si.cb = mem::size_of::<STARTUPINFO>() as DWORD;
+            si.dwFlags = STARTF_USESTDHANDLES;
+
+            let cur_proc = GetCurrentProcess();
+
+            // Similarly to unix, we don't actually leave holes for the stdio file
+            // descriptors, but rather open up /dev/null equivalents. These
+            // equivalents are drawn from libuv's windows process spawning.
+            let set_fd = |fd: &Option<P>, slot: &mut HANDLE,
+                          is_stdin: bool| {
+                match *fd {
+                    None => {
+                        let access = if is_stdin {
+                            libc::FILE_GENERIC_READ
+                        } else {
+                            libc::FILE_GENERIC_WRITE | libc::FILE_READ_ATTRIBUTES
+                        };
+                        let size = mem::size_of::<libc::SECURITY_ATTRIBUTES>();
+                        let mut sa = libc::SECURITY_ATTRIBUTES {
+                            nLength: size as libc::DWORD,
+                            lpSecurityDescriptor: ptr::null_mut(),
+                            bInheritHandle: 1,
+                        };
+                        let mut filename: Vec<u16> = "NUL".utf16_units().collect();
+                        filename.push(0);
+                        *slot = libc::CreateFileW(filename.as_ptr(),
+                                                  access,
+                                                  libc::FILE_SHARE_READ |
+                                                      libc::FILE_SHARE_WRITE,
+                                                  &mut sa,
+                                                  libc::OPEN_EXISTING,
+                                                  0,
+                                                  ptr::null_mut());
+                        if *slot == INVALID_HANDLE_VALUE {
+                            return Err(super::last_error())
+                        }
+                    }
+                    Some(ref fd) => {
+                        let orig = get_osfhandle(fd.as_fd().fd()) as HANDLE;
+                        if orig == INVALID_HANDLE_VALUE {
+                            return Err(super::last_error())
+                        }
+                        if DuplicateHandle(cur_proc, orig, cur_proc, slot,
+                                           0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE {
+                            return Err(super::last_error())
+                        }
+                    }
+                }
+                Ok(())
+            };
+
+            try!(set_fd(&in_fd, &mut si.hStdInput, true));
+            try!(set_fd(&out_fd, &mut si.hStdOutput, false));
+            try!(set_fd(&err_fd, &mut si.hStdError, false));
+
+            let cmd_str = make_command_line(program.as_ref().unwrap_or(cfg.program()),
+                                            cfg.args());
+            let mut pi = zeroed_process_information();
+            let mut create_err = None;
+
+            // stolen from the libuv code.
+            let mut flags = libc::CREATE_UNICODE_ENVIRONMENT;
+            if cfg.detach() {
+                flags |= libc::DETACHED_PROCESS | libc::CREATE_NEW_PROCESS_GROUP;
+            }
+
+            with_envp(cfg.env(), |envp| {
+                with_dirp(cfg.cwd(), |dirp| {
+                    let mut cmd_str: Vec<u16> = cmd_str.as_slice().utf16_units().collect();
+                    cmd_str.push(0);
+                    let created = CreateProcessW(ptr::null(),
+                                                 cmd_str.as_mut_ptr(),
+                                                 ptr::null_mut(),
+                                                 ptr::null_mut(),
+                                                 TRUE,
+                                                 flags, envp, dirp,
+                                                 &mut si, &mut pi);
+                    if created == FALSE {
+                        create_err = Some(super::last_error());
+                    }
+                })
+            });
+
+            assert!(CloseHandle(si.hStdInput) != 0);
+            assert!(CloseHandle(si.hStdOutput) != 0);
+            assert!(CloseHandle(si.hStdError) != 0);
+
+            match create_err {
+                Some(err) => return Err(err),
+                None => {}
+            }
+
+            // 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 std::we want the process id to stay valid at least until the
+            // calling code closes the process handle.
+            assert!(CloseHandle(pi.hThread) != 0);
+
+            Ok(Process {
+                pid: pi.dwProcessId as pid_t,
+                handle: pi.hProcess as *mut ()
+            })
+        }
+    }
+
+    /**
+     * 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.
+     */
+    pub fn wait(&self, deadline: u64) -> IoResult<ProcessExit> {
+        use libc::types::os::arch::extra::DWORD;
+        use libc::consts::os::extra::{
+            SYNCHRONIZE,
+            PROCESS_QUERY_INFORMATION,
+            FALSE,
+            STILL_ACTIVE,
+            INFINITE,
+            WAIT_TIMEOUT,
+            WAIT_OBJECT_0,
+        };
+        use libc::funcs::extra::kernel32::{
+            OpenProcess,
+            GetExitCodeProcess,
+            CloseHandle,
+            WaitForSingleObject,
+        };
+
+        unsafe {
+            let process = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
+                                      FALSE,
+                                      self.pid as DWORD);
+            if process.is_null() {
+                return Err(super::last_error())
+            }
+
+            loop {
+                let mut status = 0;
+                if GetExitCodeProcess(process, &mut status) == FALSE {
+                    let err = Err(super::last_error());
+                    assert!(CloseHandle(process) != 0);
+                    return err;
+                }
+                if status != STILL_ACTIVE {
+                    assert!(CloseHandle(process) != 0);
+                    return Ok(ExitStatus(status as int));
+                }
+                let interval = if deadline == 0 {
+                    INFINITE
+                } else {
+                    let now = timer::now();
+                    if deadline < now {0} else {(deadline - now) as u32}
+                };
+                match WaitForSingleObject(process, interval) {
+                    WAIT_OBJECT_0 => {}
+                    WAIT_TIMEOUT => {
+                        assert!(CloseHandle(process) != 0);
+                        return Err(timeout("process wait timed out"))
+                    }
+                    _ => {
+                        let err = Err(super::last_error());
+                        assert!(CloseHandle(process) != 0);
+                        return err
+                    }
+                }
+            }
+        }
+    }
+}
+
+fn zeroed_startupinfo() -> libc::types::os::arch::extra::STARTUPINFO {
+    libc::types::os::arch::extra::STARTUPINFO {
+        cb: 0,
+        lpReserved: ptr::null_mut(),
+        lpDesktop: ptr::null_mut(),
+        lpTitle: ptr::null_mut(),
+        dwX: 0,
+        dwY: 0,
+        dwXSize: 0,
+        dwYSize: 0,
+        dwXCountChars: 0,
+        dwYCountCharts: 0,
+        dwFillAttribute: 0,
+        dwFlags: 0,
+        wShowWindow: 0,
+        cbReserved2: 0,
+        lpReserved2: ptr::null_mut(),
+        hStdInput: libc::INVALID_HANDLE_VALUE,
+        hStdOutput: libc::INVALID_HANDLE_VALUE,
+        hStdError: libc::INVALID_HANDLE_VALUE,
+    }
+}
+
+fn zeroed_process_information() -> libc::types::os::arch::extra::PROCESS_INFORMATION {
+    libc::types::os::arch::extra::PROCESS_INFORMATION {
+        hProcess: ptr::null_mut(),
+        hThread: ptr::null_mut(),
+        dwProcessId: 0,
+        dwThreadId: 0
+    }
+}
+
+fn make_command_line(prog: &CString, args: &[CString]) -> String {
+    let mut cmd = String::new();
+    append_arg(&mut cmd, prog.as_str()
+                             .expect("expected program name to be utf-8 encoded"));
+    for arg in args.iter() {
+        cmd.push(' ');
+        append_arg(&mut cmd, arg.as_str()
+                                .expect("expected argument to be utf-8 encoded"));
+    }
+    return cmd;
+
+    fn append_arg(cmd: &mut String, arg: &str) {
+        // If an argument has 0 characters then we need to quote it to ensure
+        // that it actually gets passed through on the command line or otherwise
+        // it will be dropped entirely when parsed on the other end.
+        let quote = arg.chars().any(|c| c == ' ' || c == '\t') || arg.len() == 0;
+        if quote {
+            cmd.push('"');
+        }
+        let argvec: Vec<char> = arg.chars().collect();
+        for i in range(0u, argvec.len()) {
+            append_char_at(cmd, argvec.as_slice(), i);
+        }
+        if quote {
+            cmd.push('"');
+        }
+    }
+
+    fn append_char_at(cmd: &mut String, arg: &[char], i: uint) {
+        match arg[i] {
+            '"' => {
+                // 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('\\');
+                }
+            }
+            c => {
+                cmd.push(c);
+            }
+        }
+    }
+
+    fn backslash_run_ends_in_quote(s: &[char], mut i: uint) -> bool {
+        while i < s.len() && s[i] == '\\' {
+            i += 1;
+        }
+        return i < s.len() && s[i] == '"';
+    }
+}
+
+fn with_envp<K, V, T>(env: Option<&collections::HashMap<K, V>>,
+                      cb: |*mut c_void| -> T) -> T
+    where K: BytesContainer + Eq + Hash, V: BytesContainer
+{
+    // On Windows 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 = Vec::new();
+
+            for pair in env.iter() {
+                let kv = format!("{}={}",
+                                 pair.ref0().container_as_str().unwrap(),
+                                 pair.ref1().container_as_str().unwrap());
+                blk.extend(kv.as_slice().utf16_units());
+                blk.push(0);
+            }
+
+            blk.push(0);
+
+            cb(blk.as_mut_ptr() as *mut c_void)
+        }
+        _ => cb(ptr::null_mut())
+    }
+}
+
+fn with_dirp<T>(d: Option<&CString>, cb: |*const u16| -> T) -> T {
+    match d {
+      Some(dir) => {
+          let dir_str = dir.as_str()
+                           .expect("expected workingdirectory to be utf-8 encoded");
+          let mut dir_str: Vec<u16> = dir_str.utf16_units().collect();
+          dir_str.push(0);
+          cb(dir_str.as_ptr())
+      },
+      None => cb(ptr::null())
+    }
+}
+
+fn free_handle(handle: *mut ()) {
+    assert!(unsafe {
+        libc::CloseHandle(mem::transmute(handle)) != 0
+    })
+}
+
+#[cfg(test)]
+mod tests {
+
+    #[test]
+    fn test_make_command_line() {
+        use prelude::*;
+        use str;
+        use c_str::CString;
+        use super::make_command_line;
+
+        fn test_wrapper(prog: &str, args: &[&str]) -> String {
+            make_command_line(&prog.to_c_str(),
+                              args.iter()
+                                  .map(|a| a.to_c_str())
+                                  .collect::<Vec<CString>>()
+                                  .as_slice())
+        }
+
+        assert_eq!(
+            test_wrapper("prog", ["aaa", "bbb", "ccc"]),
+            "prog aaa bbb ccc".to_string()
+        );
+
+        assert_eq!(
+            test_wrapper("C:\\Program Files\\blah\\blah.exe", ["aaa"]),
+            "\"C:\\Program Files\\blah\\blah.exe\" aaa".to_string()
+        );
+        assert_eq!(
+            test_wrapper("C:\\Program Files\\test", ["aa\"bb"]),
+            "\"C:\\Program Files\\test\" aa\\\"bb".to_string()
+        );
+        assert_eq!(
+            test_wrapper("echo", ["a b c"]),
+            "echo \"a b c\"".to_string()
+        );
+        assert_eq!(
+            test_wrapper("\u03c0\u042f\u97f3\u00e6\u221e", []),
+            "\u03c0\u042f\u97f3\u00e6\u221e".to_string()
+        );
+    }
+}