summary refs log tree commit diff
path: root/src/libstd/sys/windows
diff options
context:
space:
mode:
authorAaron Turon <aturon@mozilla.com>2014-10-09 16:27:28 -0700
committerAaron Turon <aturon@mozilla.com>2014-11-08 20:40:39 -0800
commit0f98e75b69d16edce9ca60d7961b8440856a3f72 (patch)
treec742de98f63f2ca7d2ac3236a35fcf6cbaa60f01 /src/libstd/sys/windows
parent3d195482a45bf3ed0f12dc9d70d14192262ca711 (diff)
downloadrust-0f98e75b69d16edce9ca60d7961b8440856a3f72.tar.gz
rust-0f98e75b69d16edce9ca60d7961b8440856a3f72.zip
Runtime removal: refactor process
This patch continues the runtime removal by moving and refactoring the
process implementation into the new `sys` module.

Because this eliminates APIs in `libnative` and `librustrt`, it is a:

[breaking-change]

This functionality is likely to be available publicly, in some form,
from `std` in the future.
Diffstat (limited to 'src/libstd/sys/windows')
-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
3 files changed, 972 insertions, 0 deletions
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()
+        );
+    }
+}