about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDiggory Blake <diggsey@googlemail.com>2017-12-17 15:21:47 +0000
committerDiggory Blake <diggsey@googlemail.com>2017-12-24 14:24:31 +0000
commitccc91d7b4873a50678b3f65c895290915c54f6f5 (patch)
tree5884d3c794a88178b6853df2dde3bcb3ee67de1b
parentb058dc0107b734b0a1a664ca0209366bb59eb3e9 (diff)
downloadrust-ccc91d7b4873a50678b3f65c895290915c54f6f5.tar.gz
rust-ccc91d7b4873a50678b3f65c895290915c54f6f5.zip
Capture environment at spawn
-rw-r--r--src/libstd/process.rs29
-rw-r--r--src/libstd/sys/redox/process.rs24
-rw-r--r--src/libstd/sys/unix/process/process_common.rs143
-rw-r--r--src/libstd/sys/unix/process/process_fuchsia.rs10
-rw-r--r--src/libstd/sys/unix/process/process_unix.rs16
-rw-r--r--src/libstd/sys/wasm/process.rs15
-rw-r--r--src/libstd/sys/windows/os_str.rs8
-rw-r--r--src/libstd/sys/windows/process.rs98
-rw-r--r--src/libstd/sys_common/mod.rs1
-rw-r--r--src/libstd/sys_common/process.rs124
-rw-r--r--src/libstd/sys_common/wtf8.rs20
11 files changed, 321 insertions, 167 deletions
diff --git a/src/libstd/process.rs b/src/libstd/process.rs
index 2335695ae42..1f6ddde0027 100644
--- a/src/libstd/process.rs
+++ b/src/libstd/process.rs
@@ -513,7 +513,7 @@ impl Command {
     pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Command
         where K: AsRef<OsStr>, V: AsRef<OsStr>
     {
-        self.inner.env(key.as_ref(), val.as_ref());
+        self.inner.env_mut().set(key.as_ref(), val.as_ref());
         self
     }
 
@@ -546,7 +546,7 @@ impl Command {
         where I: IntoIterator<Item=(K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>
     {
         for (ref key, ref val) in vars {
-            self.inner.env(key.as_ref(), val.as_ref());
+            self.inner.env_mut().set(key.as_ref(), val.as_ref());
         }
         self
     }
@@ -567,7 +567,7 @@ impl Command {
     /// ```
     #[stable(feature = "process", since = "1.0.0")]
     pub fn env_remove<K: AsRef<OsStr>>(&mut self, key: K) -> &mut Command {
-        self.inner.env_remove(key.as_ref());
+        self.inner.env_mut().remove(key.as_ref());
         self
     }
 
@@ -587,7 +587,7 @@ impl Command {
     /// ```
     #[stable(feature = "process", since = "1.0.0")]
     pub fn env_clear(&mut self) -> &mut Command {
-        self.inner.env_clear();
+        self.inner.env_mut().clear();
         self
     }
 
@@ -1715,6 +1715,27 @@ mod tests {
                 "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output);
     }
 
+    #[test]
+    fn test_capture_env_at_spawn() {
+        use env;
+
+        let mut cmd = env_cmd();
+        cmd.env("RUN_TEST_NEW_ENV1", "123");
+
+        // This variable will not be present if the environment has already
+        // been captured above.
+        env::set_var("RUN_TEST_NEW_ENV2", "456");
+        let result = cmd.output().unwrap();
+        env::remove_var("RUN_TEST_NEW_ENV2");
+
+        let output = String::from_utf8_lossy(&result.stdout).to_string();
+
+        assert!(output.contains("RUN_TEST_NEW_ENV1=123"),
+                "didn't find RUN_TEST_NEW_ENV1 inside of:\n\n{}", output);
+        assert!(output.contains("RUN_TEST_NEW_ENV2=456"),
+                "didn't find RUN_TEST_NEW_ENV2 inside of:\n\n{}", output);
+    }
+
     // Regression tests for #30858.
     #[test]
     fn test_interior_nul_in_progname_is_error() {
diff --git a/src/libstd/sys/redox/process.rs b/src/libstd/sys/redox/process.rs
index d87364b8121..3fd54973896 100644
--- a/src/libstd/sys/redox/process.rs
+++ b/src/libstd/sys/redox/process.rs
@@ -8,8 +8,7 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-use collections::hash_map::HashMap;
-use env::{self, split_paths};
+use env::{split_paths};
 use ffi::OsStr;
 use os::unix::ffi::OsStrExt;
 use fmt;
@@ -19,6 +18,7 @@ use sys::fd::FileDesc;
 use sys::fs::{File, OpenOptions};
 use sys::pipe::{self, AnonPipe};
 use sys::{cvt, syscall};
+use sys_common::process::{CommandEnv, DefaultEnvKey};
 
 ////////////////////////////////////////////////////////////////////////////////
 // Command
@@ -44,7 +44,7 @@ pub struct Command {
     // other keys.
     program: String,
     args: Vec<String>,
-    env: HashMap<String, String>,
+    env: CommandEnv<DefaultEnvKey>,
 
     cwd: Option<String>,
     uid: Option<u32>,
@@ -90,7 +90,7 @@ impl Command {
         Command {
             program: program.to_str().unwrap().to_owned(),
             args: Vec::new(),
-            env: HashMap::new(),
+            env: Default::default(),
             cwd: None,
             uid: None,
             gid: None,
@@ -106,16 +106,8 @@ impl Command {
         self.args.push(arg.to_str().unwrap().to_owned());
     }
 
-    pub fn env(&mut self, key: &OsStr, val: &OsStr) {
-        self.env.insert(key.to_str().unwrap().to_owned(), val.to_str().unwrap().to_owned());
-    }
-
-    pub fn env_remove(&mut self, key: &OsStr) {
-        self.env.remove(key.to_str().unwrap());
-    }
-
-    pub fn env_clear(&mut self) {
-        self.env.clear();
+    pub fn env_mut(&mut self) -> &mut CommandEnv<DefaultEnvKey> {
+        &mut self.env
     }
 
     pub fn cwd(&mut self, dir: &OsStr) {
@@ -309,9 +301,7 @@ impl Command {
             args.push([arg.as_ptr() as usize, arg.len()]);
         }
 
-        for (key, val) in self.env.iter() {
-            env::set_var(key, val);
-        }
+        self.env.apply();
 
         let program = if self.program.contains(':') || self.program.contains('/') {
             Some(PathBuf::from(&self.program))
diff --git a/src/libstd/sys/unix/process/process_common.rs b/src/libstd/sys/unix/process/process_common.rs
index 383434b1cd8..c53bcdbf8e3 100644
--- a/src/libstd/sys/unix/process/process_common.rs
+++ b/src/libstd/sys/unix/process/process_common.rs
@@ -10,8 +10,6 @@
 
 use os::unix::prelude::*;
 
-use collections::hash_map::{HashMap, Entry};
-use env;
 use ffi::{OsString, OsStr, CString, CStr};
 use fmt;
 use io;
@@ -20,6 +18,8 @@ use ptr;
 use sys::fd::FileDesc;
 use sys::fs::{File, OpenOptions};
 use sys::pipe::{self, AnonPipe};
+use sys_common::process::{CommandEnv, DefaultEnvKey};
+use collections::BTreeMap;
 
 ////////////////////////////////////////////////////////////////////////////////
 // Command
@@ -45,9 +45,8 @@ pub struct Command {
     // other keys.
     program: CString,
     args: Vec<CString>,
-    env: Option<HashMap<OsString, (usize, CString)>>,
     argv: Vec<*const c_char>,
-    envp: Option<Vec<*const c_char>>,
+    env: CommandEnv<DefaultEnvKey>,
 
     cwd: Option<CString>,
     uid: Option<uid_t>,
@@ -96,8 +95,7 @@ impl Command {
             argv: vec![program.as_ptr(), ptr::null()],
             program,
             args: Vec::new(),
-            env: None,
-            envp: None,
+            env: Default::default(),
             cwd: None,
             uid: None,
             gid: None,
@@ -121,68 +119,6 @@ impl Command {
         self.args.push(arg);
     }
 
-    fn init_env_map(&mut self) -> (&mut HashMap<OsString, (usize, CString)>,
-                                   &mut Vec<*const c_char>) {
-        if self.env.is_none() {
-            let mut map = HashMap::new();
-            let mut envp = Vec::new();
-            for (k, v) in env::vars_os() {
-                let s = pair_to_key(&k, &v, &mut self.saw_nul);
-                envp.push(s.as_ptr());
-                map.insert(k, (envp.len() - 1, s));
-            }
-            envp.push(ptr::null());
-            self.env = Some(map);
-            self.envp = Some(envp);
-        }
-        (self.env.as_mut().unwrap(), self.envp.as_mut().unwrap())
-    }
-
-    pub fn env(&mut self, key: &OsStr, val: &OsStr) {
-        let new_key = pair_to_key(key, val, &mut self.saw_nul);
-        let (map, envp) = self.init_env_map();
-
-        // If `key` is already present then we just update `envp` in place
-        // (and store the owned value), but if it's not there we override the
-        // trailing NULL pointer, add a new NULL pointer, and store where we
-        // were located.
-        match map.entry(key.to_owned()) {
-            Entry::Occupied(mut e) => {
-                let (i, ref mut s) = *e.get_mut();
-                envp[i] = new_key.as_ptr();
-                *s = new_key;
-            }
-            Entry::Vacant(e) => {
-                let len = envp.len();
-                envp[len - 1] = new_key.as_ptr();
-                envp.push(ptr::null());
-                e.insert((len - 1, new_key));
-            }
-        }
-    }
-
-    pub fn env_remove(&mut self, key: &OsStr) {
-        let (map, envp) = self.init_env_map();
-
-        // If we actually ended up removing a key, then we need to update the
-        // position of all keys that come after us in `envp` because they're all
-        // one element sooner now.
-        if let Some((i, _)) = map.remove(key) {
-            envp.remove(i);
-
-            for (_, &mut (ref mut j, _)) in map.iter_mut() {
-                if *j >= i {
-                    *j -= 1;
-                }
-            }
-        }
-    }
-
-    pub fn env_clear(&mut self) {
-        self.env = Some(HashMap::new());
-        self.envp = Some(vec![ptr::null()]);
-    }
-
     pub fn cwd(&mut self, dir: &OsStr) {
         self.cwd = Some(os2c(dir, &mut self.saw_nul));
     }
@@ -196,9 +132,6 @@ impl Command {
     pub fn saw_nul(&self) -> bool {
         self.saw_nul
     }
-    pub fn get_envp(&self) -> &Option<Vec<*const c_char>> {
-        &self.envp
-    }
     pub fn get_argv(&self) -> &Vec<*const c_char> {
         &self.argv
     }
@@ -237,6 +170,15 @@ impl Command {
         self.stderr = Some(stderr);
     }
 
+    pub fn env_mut(&mut self) -> &mut CommandEnv<DefaultEnvKey> {
+        &mut self.env
+    }
+
+    pub fn capture_env(&mut self) -> Option<CStringArray> {
+        let maybe_env = self.env.capture_if_changed();
+        maybe_env.map(|env| construct_envp(env, &mut self.saw_nul))
+    }
+
     pub fn setup_io(&self, default: Stdio, needs_stdin: bool)
                 -> io::Result<(StdioPipes, ChildPipes)> {
         let null = Stdio::Null;
@@ -268,6 +210,53 @@ fn os2c(s: &OsStr, saw_nul: &mut bool) -> CString {
     })
 }
 
+// Helper type to manage ownership of the strings within a C-style array.
+pub struct CStringArray {
+    items: Vec<CString>,
+    ptrs: Vec<*const c_char>
+}
+
+impl CStringArray {
+    pub fn with_capacity(capacity: usize) -> Self {
+        let mut result = CStringArray {
+            items: Vec::with_capacity(capacity),
+            ptrs: Vec::with_capacity(capacity+1)
+        };
+        result.ptrs.push(ptr::null());
+        result
+    }
+    pub fn push(&mut self, item: CString) {
+        let l = self.ptrs.len();
+        self.ptrs[l-1] = item.as_ptr();
+        self.ptrs.push(ptr::null());
+        self.items.push(item);
+    }
+    pub fn as_ptr(&self) -> *const *const c_char {
+        self.ptrs.as_ptr()
+    }
+}
+
+fn construct_envp(env: BTreeMap<DefaultEnvKey, OsString>, saw_nul: &mut bool) -> CStringArray {
+    let mut result = CStringArray::with_capacity(env.len());
+    for (k, v) in env {
+        let mut k: OsString = k.into();
+
+        // Reserve additional space for '=' and null terminator
+        k.reserve_exact(v.len() + 2);
+        k.push("=");
+        k.push(&v);
+
+        // Add the new entry into the array
+        if let Ok(item) = CString::new(k.into_vec()) {
+            result.push(item);
+        } else {
+            *saw_nul = true;
+        }
+    }
+
+    result
+}
+
 impl Stdio {
     pub fn to_child_stdio(&self, readable: bool)
                       -> io::Result<(ChildStdio, Option<AnonPipe>)> {
@@ -337,18 +326,6 @@ impl ChildStdio {
     }
 }
 
-fn pair_to_key(key: &OsStr, value: &OsStr, saw_nul: &mut bool) -> CString {
-    let (key, value) = (key.as_bytes(), value.as_bytes());
-    let mut v = Vec::with_capacity(key.len() + value.len() + 1);
-    v.extend(key);
-    v.push(b'=');
-    v.extend(value);
-    CString::new(v).unwrap_or_else(|_e| {
-        *saw_nul = true;
-        CString::new("foo=bar").unwrap()
-    })
-}
-
 impl fmt::Debug for Command {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(f, "{:?}", self.program)?;
diff --git a/src/libstd/sys/unix/process/process_fuchsia.rs b/src/libstd/sys/unix/process/process_fuchsia.rs
index a7a67ed36e8..06c0540fec0 100644
--- a/src/libstd/sys/unix/process/process_fuchsia.rs
+++ b/src/libstd/sys/unix/process/process_fuchsia.rs
@@ -23,6 +23,8 @@ use sys::process::process_common::*;
 impl Command {
     pub fn spawn(&mut self, default: Stdio, needs_stdin: bool)
                  -> io::Result<(Process, StdioPipes)> {
+        let envp = self.capture_env();
+
         if self.saw_nul() {
             return Err(io::Error::new(io::ErrorKind::InvalidInput,
                                       "nul byte found in provided data"));
@@ -30,7 +32,7 @@ impl Command {
 
         let (ours, theirs) = self.setup_io(default, needs_stdin)?;
 
-        let process_handle = unsafe { self.do_exec(theirs)? };
+        let process_handle = unsafe { self.do_exec(theirs, envp.as_ref())? };
 
         Ok((Process { handle: Handle::new(process_handle) }, ours))
     }
@@ -50,13 +52,13 @@ impl Command {
         }
     }
 
-    unsafe fn do_exec(&mut self, stdio: ChildPipes)
+    unsafe fn do_exec(&mut self, stdio: ChildPipes, maybe_envp: Option<&CStringArray>)
                       -> io::Result<zx_handle_t> {
         use sys::process::zircon::*;
 
         let job_handle = zx_job_default();
-        let envp = match *self.get_envp() {
-            Some(ref envp) => envp.as_ptr(),
+        let envp = match maybe_envp {
+            Some(envp) => envp.as_ptr(),
             None => ptr::null(),
         };
 
diff --git a/src/libstd/sys/unix/process/process_unix.rs b/src/libstd/sys/unix/process/process_unix.rs
index 743c458d580..189280a4ba9 100644
--- a/src/libstd/sys/unix/process/process_unix.rs
+++ b/src/libstd/sys/unix/process/process_unix.rs
@@ -26,6 +26,8 @@ impl Command {
 
         const CLOEXEC_MSG_FOOTER: &'static [u8] = b"NOEX";
 
+        let envp = self.capture_env();
+
         if self.saw_nul() {
             return Err(io::Error::new(ErrorKind::InvalidInput,
                                       "nul byte found in provided data"));
@@ -38,7 +40,7 @@ impl Command {
             match cvt(libc::fork())? {
                 0 => {
                     drop(input);
-                    let err = self.do_exec(theirs);
+                    let err = self.do_exec(theirs, envp.as_ref());
                     let errno = err.raw_os_error().unwrap_or(libc::EINVAL) as u32;
                     let bytes = [
                         (errno >> 24) as u8,
@@ -99,13 +101,15 @@ impl Command {
     }
 
     pub fn exec(&mut self, default: Stdio) -> io::Error {
+        let envp = self.capture_env();
+
         if self.saw_nul() {
             return io::Error::new(ErrorKind::InvalidInput,
                                   "nul byte found in provided data")
         }
 
         match self.setup_io(default, true) {
-            Ok((_, theirs)) => unsafe { self.do_exec(theirs) },
+            Ok((_, theirs)) => unsafe { self.do_exec(theirs, envp.as_ref()) },
             Err(e) => e,
         }
     }
@@ -140,7 +144,11 @@ impl Command {
     // 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)
-    unsafe fn do_exec(&mut self, stdio: ChildPipes) -> io::Error {
+    unsafe fn do_exec(
+        &mut self,
+        stdio: ChildPipes,
+        maybe_envp: Option<&CStringArray>
+    ) -> io::Error {
         use sys::{self, cvt_r};
 
         macro_rules! t {
@@ -180,7 +188,7 @@ impl Command {
         if let Some(ref cwd) = *self.get_cwd() {
             t!(cvt(libc::chdir(cwd.as_ptr())));
         }
-        if let Some(ref envp) = *self.get_envp() {
+        if let Some(envp) = maybe_envp {
             *sys::os::environ() = envp.as_ptr();
         }
 
diff --git a/src/libstd/sys/wasm/process.rs b/src/libstd/sys/wasm/process.rs
index 4febe8a1463..f3f5de350f1 100644
--- a/src/libstd/sys/wasm/process.rs
+++ b/src/libstd/sys/wasm/process.rs
@@ -14,12 +14,14 @@ use io;
 use sys::fs::File;
 use sys::pipe::AnonPipe;
 use sys::{unsupported, Void};
+use sys_common::process::{CommandEnv, DefaultEnvKey};
 
 ////////////////////////////////////////////////////////////////////////////////
 // Command
 ////////////////////////////////////////////////////////////////////////////////
 
 pub struct Command {
+    env: CommandEnv<DefaultEnvKey>
 }
 
 // passed back to std::process with the pipes connected to the child, if any
@@ -38,19 +40,16 @@ pub enum Stdio {
 
 impl Command {
     pub fn new(_program: &OsStr) -> Command {
-        Command {}
+        Command {
+            env: Default::default()
+        }
     }
 
     pub fn arg(&mut self, _arg: &OsStr) {
     }
 
-    pub fn env(&mut self, _key: &OsStr, _val: &OsStr) {
-    }
-
-    pub fn env_remove(&mut self, _key: &OsStr) {
-    }
-
-    pub fn env_clear(&mut self) {
+    pub fn env_mut(&mut self) -> &mut CommandEnv<DefaultEnvKey> {
+        &mut self.env
     }
 
     pub fn cwd(&mut self, _dir: &OsStr) {
diff --git a/src/libstd/sys/windows/os_str.rs b/src/libstd/sys/windows/os_str.rs
index b8d2f7bc53c..414c9c5418e 100644
--- a/src/libstd/sys/windows/os_str.rs
+++ b/src/libstd/sys/windows/os_str.rs
@@ -17,7 +17,7 @@ use sys_common::wtf8::{Wtf8, Wtf8Buf};
 use mem;
 use rc::Rc;
 use sync::Arc;
-use sys_common::{AsInner, IntoInner};
+use sys_common::{AsInner, IntoInner, FromInner};
 
 #[derive(Clone, Hash)]
 pub struct Buf {
@@ -30,6 +30,12 @@ impl IntoInner<Wtf8Buf> for Buf {
     }
 }
 
+impl FromInner<Wtf8Buf> for Buf {
+    fn from_inner(inner: Wtf8Buf) -> Self {
+        Buf { inner }
+    }
+}
+
 impl AsInner<Wtf8> for Buf {
     fn as_inner(&self) -> &Wtf8 {
         &self.inner
diff --git a/src/libstd/sys/windows/process.rs b/src/libstd/sys/windows/process.rs
index 631d69b05e1..c93179869a6 100644
--- a/src/libstd/sys/windows/process.rs
+++ b/src/libstd/sys/windows/process.rs
@@ -8,9 +8,10 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+#![unstable(feature = "process_internals", issue = "0")]
+
 use ascii::AsciiExt;
-use collections::HashMap;
-use collections;
+use collections::BTreeMap;
 use env::split_paths;
 use env;
 use ffi::{OsString, OsStr};
@@ -28,19 +29,42 @@ use sys::fs::{OpenOptions, File};
 use sys::handle::Handle;
 use sys::pipe::{self, AnonPipe};
 use sys::stdio;
-use sys::{self, cvt};
-use sys_common::{AsInner, FromInner};
+use sys::cvt;
+use sys_common::{AsInner, FromInner, IntoInner};
+use sys_common::process::{CommandEnv, EnvKey};
+use alloc::borrow::Borrow;
 
 ////////////////////////////////////////////////////////////////////////////////
 // Command
 ////////////////////////////////////////////////////////////////////////////////
 
-fn mk_key(s: &OsStr) -> OsString {
-    FromInner::from_inner(sys::os_str::Buf {
-        inner: s.as_inner().inner.to_ascii_uppercase()
-    })
+#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
+#[doc(hidden)]
+pub struct WindowsEnvKey(OsString);
+
+impl From<OsString> for WindowsEnvKey {
+    fn from(k: OsString) -> Self {
+        let mut buf = k.into_inner().into_inner();
+        buf.make_ascii_uppercase();
+        WindowsEnvKey(FromInner::from_inner(FromInner::from_inner(buf)))
+    }
+}
+
+impl From<WindowsEnvKey> for OsString {
+    fn from(k: WindowsEnvKey) -> Self { k.0 }
 }
 
+impl Borrow<OsStr> for WindowsEnvKey {
+    fn borrow(&self) -> &OsStr { &self.0 }
+}
+
+impl AsRef<OsStr> for WindowsEnvKey {
+    fn as_ref(&self) -> &OsStr { &self.0 }
+}
+
+impl EnvKey for WindowsEnvKey {}
+
+
 fn ensure_no_nuls<T: AsRef<OsStr>>(str: T) -> io::Result<T> {
     if str.as_ref().encode_wide().any(|b| b == 0) {
         Err(io::Error::new(ErrorKind::InvalidInput, "nul byte found in provided data"))
@@ -52,7 +76,7 @@ fn ensure_no_nuls<T: AsRef<OsStr>>(str: T) -> io::Result<T> {
 pub struct Command {
     program: OsString,
     args: Vec<OsString>,
-    env: Option<HashMap<OsString, OsString>>,
+    env: CommandEnv<WindowsEnvKey>,
     cwd: Option<OsString>,
     flags: u32,
     detach: bool, // not currently exposed in std::process
@@ -83,7 +107,7 @@ impl Command {
         Command {
             program: program.to_os_string(),
             args: Vec::new(),
-            env: None,
+            env: Default::default(),
             cwd: None,
             flags: 0,
             detach: false,
@@ -96,23 +120,8 @@ impl Command {
     pub fn arg(&mut self, arg: &OsStr) {
         self.args.push(arg.to_os_string())
     }
-    fn init_env_map(&mut self){
-        if self.env.is_none() {
-            self.env = Some(env::vars_os().map(|(key, val)| {
-                (mk_key(&key), val)
-            }).collect());
-        }
-    }
-    pub fn env(&mut self, key: &OsStr, val: &OsStr) {
-        self.init_env_map();
-        self.env.as_mut().unwrap().insert(mk_key(key), val.to_os_string());
-    }
-    pub fn env_remove(&mut self, key: &OsStr) {
-        self.init_env_map();
-        self.env.as_mut().unwrap().remove(&mk_key(key));
-    }
-    pub fn env_clear(&mut self) {
-        self.env = Some(HashMap::new())
+    pub fn env_mut(&mut self) -> &mut CommandEnv<WindowsEnvKey> {
+        &mut self.env
     }
     pub fn cwd(&mut self, dir: &OsStr) {
         self.cwd = Some(dir.to_os_string())
@@ -132,13 +141,12 @@ impl Command {
 
     pub fn spawn(&mut self, default: Stdio, needs_stdin: bool)
                  -> io::Result<(Process, StdioPipes)> {
+        let maybe_env = self.env.capture_if_changed();
         // 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 = self.env.as_ref().and_then(|env| {
-            for (key, v) in env {
-                if OsStr::new("PATH") != &**key { continue }
-
+        let program = maybe_env.as_ref().and_then(|env| {
+            if let Some(v) = env.get(OsStr::new("PATH")) {
                 // Split the value and test each path to see if the
                 // program exists.
                 for path in split_paths(&v) {
@@ -148,7 +156,6 @@ impl Command {
                         return Some(path.into_os_string())
                     }
                 }
-                break
             }
             None
         });
@@ -167,7 +174,7 @@ impl Command {
             flags |= c::DETACHED_PROCESS | c::CREATE_NEW_PROCESS_GROUP;
         }
 
-        let (envp, _data) = make_envp(self.env.as_ref())?;
+        let (envp, _data) = make_envp(maybe_env)?;
         let (dirp, _data) = make_dirp(self.cwd.as_ref())?;
         let mut pi = zeroed_process_information();
 
@@ -488,25 +495,24 @@ fn make_command_line(prog: &OsStr, args: &[OsString]) -> io::Result<Vec<u16>> {
     }
 }
 
-fn make_envp(env: Option<&collections::HashMap<OsString, OsString>>)
+fn make_envp(maybe_env: Option<BTreeMap<WindowsEnvKey, OsString>>)
              -> io::Result<(*mut c_void, Vec<u16>)> {
     // 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 {
-                blk.extend(ensure_no_nuls(pair.0)?.encode_wide());
-                blk.push('=' as u16);
-                blk.extend(ensure_no_nuls(pair.1)?.encode_wide());
-                blk.push(0);
-            }
+    if let Some(env) = maybe_env {
+        let mut blk = Vec::new();
+
+        for (k, v) in env {
+            blk.extend(ensure_no_nuls(k.0)?.encode_wide());
+            blk.push('=' as u16);
+            blk.extend(ensure_no_nuls(v)?.encode_wide());
             blk.push(0);
-            Ok((blk.as_mut_ptr() as *mut c_void, blk))
         }
-        _ => Ok((ptr::null_mut(), Vec::new()))
+        blk.push(0);
+        Ok((blk.as_mut_ptr() as *mut c_void, blk))
+    } else {
+        Ok((ptr::null_mut(), Vec::new()))
     }
 }
 
diff --git a/src/libstd/sys_common/mod.rs b/src/libstd/sys_common/mod.rs
index 5c4d7b52754..b16299a1d63 100644
--- a/src/libstd/sys_common/mod.rs
+++ b/src/libstd/sys_common/mod.rs
@@ -44,6 +44,7 @@ pub mod thread_local;
 pub mod util;
 pub mod wtf8;
 pub mod bytestring;
+pub mod process;
 
 cfg_if! {
     if #[cfg(any(target_os = "redox", target_os = "l4re"))] {
diff --git a/src/libstd/sys_common/process.rs b/src/libstd/sys_common/process.rs
new file mode 100644
index 00000000000..fd1a5fdb410
--- /dev/null
+++ b/src/libstd/sys_common/process.rs
@@ -0,0 +1,124 @@
+// 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.
+
+#![allow(dead_code)]
+#![unstable(feature = "process_internals", issue = "0")]
+
+use ffi::{OsStr, OsString};
+use env;
+use collections::BTreeMap;
+use alloc::borrow::Borrow;
+
+pub trait EnvKey:
+    From<OsString> + Into<OsString> +
+    Borrow<OsStr> + Borrow<Self> + AsRef<OsStr> +
+    Ord + Clone {}
+
+// Implement a case-sensitive environment variable key
+#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub struct DefaultEnvKey(OsString);
+
+impl From<OsString> for DefaultEnvKey {
+    fn from(k: OsString) -> Self { DefaultEnvKey(k) }
+}
+
+impl From<DefaultEnvKey> for OsString {
+    fn from(k: DefaultEnvKey) -> Self { k.0 }
+}
+
+impl Borrow<OsStr> for DefaultEnvKey {
+    fn borrow(&self) -> &OsStr { &self.0 }
+}
+
+impl AsRef<OsStr> for DefaultEnvKey {
+    fn as_ref(&self) -> &OsStr { &self.0 }
+}
+
+impl EnvKey for DefaultEnvKey {}
+
+// Stores a set of changes to an environment
+#[derive(Clone, Debug)]
+pub struct CommandEnv<K> {
+    clear: bool,
+    vars: BTreeMap<K, Option<OsString>>
+}
+
+impl<K: EnvKey> Default for CommandEnv<K> {
+    fn default() -> Self {
+        CommandEnv {
+            clear: false,
+            vars: Default::default()
+        }
+    }
+}
+
+impl<K: EnvKey> CommandEnv<K> {
+    // Capture the current environment with these changes applied
+    pub fn capture(&self) -> BTreeMap<K, OsString> {
+        let mut result = BTreeMap::<K, OsString>::new();
+        if !self.clear {
+            for (k, v) in env::vars_os() {
+                result.insert(k.into(), v);
+            }
+        }
+        for (k, maybe_v) in &self.vars {
+            if let &Some(ref v) = maybe_v {
+                result.insert(k.clone(), v.clone());
+            } else {
+                result.remove(k);
+            }
+        }
+        result
+    }
+
+    // Apply these changes directly to the current environment
+    pub fn apply(&self) {
+        if self.clear {
+            for (k, _) in env::vars_os() {
+                env::remove_var(k);
+            }
+        }
+        for (key, maybe_val) in self.vars.iter() {
+            if let &Some(ref val) = maybe_val {
+                env::set_var(key, val);
+            } else {
+                env::remove_var(key);
+            }
+        }
+    }
+
+    pub fn is_unchanged(&self) -> bool {
+        !self.clear && self.vars.is_empty()
+    }
+
+    pub fn capture_if_changed(&self) -> Option<BTreeMap<K, OsString>> {
+        if self.is_unchanged() {
+            None
+        } else {
+            Some(self.capture())
+        }
+    }
+
+    // The following functions build up changes
+    pub fn set(&mut self, key: &OsStr, value: &OsStr) {
+        self.vars.insert(key.to_owned().into(), Some(value.to_owned()));
+    }
+    pub fn remove(&mut self, key: &OsStr) {
+        if self.clear {
+            self.vars.remove(key);
+        } else {
+            self.vars.insert(key.to_owned().into(), None);
+        }
+    }
+    pub fn clear(&mut self) {
+        self.clear = true;
+        self.vars.clear();
+    }
+}
diff --git a/src/libstd/sys_common/wtf8.rs b/src/libstd/sys_common/wtf8.rs
index b2fc559bb37..46d554d6411 100644
--- a/src/libstd/sys_common/wtf8.rs
+++ b/src/libstd/sys_common/wtf8.rs
@@ -134,6 +134,12 @@ impl ops::Deref for Wtf8Buf {
     }
 }
 
+impl ops::DerefMut for Wtf8Buf {
+    fn deref_mut(&mut self) -> &mut Wtf8 {
+        self.as_mut_slice()
+    }
+}
+
 /// Format the string with double quotes,
 /// and surrogates as `\u` followed by four hexadecimal digits.
 /// Example: `"a\u{D800}"` for a string with code points [U+0061, U+D800]
@@ -221,6 +227,11 @@ impl Wtf8Buf {
         unsafe { Wtf8::from_bytes_unchecked(&self.bytes) }
     }
 
+    #[inline]
+    pub fn as_mut_slice(&mut self) -> &mut Wtf8 {
+        unsafe { Wtf8::from_mut_bytes_unchecked(&mut self.bytes) }
+    }
+
     /// Reserves capacity for at least `additional` more bytes to be inserted
     /// in the given `Wtf8Buf`.
     /// The collection may reserve more space to avoid frequent reallocations.
@@ -486,6 +497,15 @@ impl Wtf8 {
         mem::transmute(value)
     }
 
+    /// Creates a mutable WTF-8 slice from a mutable WTF-8 byte slice.
+    ///
+    /// Since the byte slice is not checked for valid WTF-8, this functions is
+    /// marked unsafe.
+    #[inline]
+    unsafe fn from_mut_bytes_unchecked(value: &mut [u8]) -> &mut Wtf8 {
+        mem::transmute(value)
+    }
+
     /// Returns the length, in WTF-8 bytes.
     #[inline]
     pub fn len(&self) -> usize {