use ptr;
use slice;
use str;
-use sys_common::mutex::Mutex;
+use sys_common::mutex::{Mutex, MutexGuard};
use sys::cvt;
use sys::fd;
use vec;
const TMPBUF_SZ: usize = 128;
-// We never call `ENV_LOCK.init()`, so it is UB to attempt to
-// acquire this mutex reentrantly!
-static ENV_LOCK: Mutex = Mutex::new();
extern {
&mut environ
}
+pub unsafe fn env_lock() -> MutexGuard<'static> {
+ // We never call `ENV_LOCK.init()`, so it is UB to attempt to
+ // acquire this mutex reentrantly!
+ static ENV_LOCK: Mutex = Mutex::new();
+ ENV_LOCK.lock()
+}
+
/// Returns a vector of (variable, value) byte-vector pairs for all the
/// environment variables of the current process.
pub fn env() -> Env {
unsafe {
- let _guard = ENV_LOCK.lock();
+ let _guard = env_lock();
let mut environ = *environ();
let mut result = Vec::new();
while environ != ptr::null() && *environ != ptr::null() {
// always None as well
let k = CString::new(k.as_bytes())?;
unsafe {
- let _guard = ENV_LOCK.lock();
+ let _guard = env_lock();
let s = libc::getenv(k.as_ptr()) as *const libc::c_char;
let ret = if s.is_null() {
None
let v = CString::new(v.as_bytes())?;
unsafe {
- let _guard = ENV_LOCK.lock();
+ let _guard = env_lock();
cvt(libc::setenv(k.as_ptr(), v.as_ptr(), 1)).map(|_| ())
}
}
let nbuf = CString::new(n.as_bytes())?;
unsafe {
- let _guard = ENV_LOCK.lock();
+ let _guard = env_lock();
cvt(libc::unsetenv(nbuf.as_ptr())).map(|_| ())
}
}
use io::{self, Error, ErrorKind};
use libc::{self, c_int, gid_t, pid_t, uid_t};
use ptr;
-
use sys::cvt;
use sys::process::process_common::*;
+use sys;
////////////////////////////////////////////////////////////////////////////////
// Command
impl Command {
pub fn spawn(&mut self, default: Stdio, needs_stdin: bool)
-> io::Result<(Process, StdioPipes)> {
- use sys;
-
const CLOEXEC_MSG_FOOTER: &'static [u8] = b"NOEX";
let envp = self.capture_env();
let (input, output) = sys::pipe::anon_pipe()?;
+ // Whatever happens after the fork is almost for sure going to touch or
+ // look at the environment in one way or another (PATH in `execvp` or
+ // accessing the `environ` pointer ourselves). Make sure no other thread
+ // is accessing the environment when we do the fork itself.
+ //
+ // Note that as soon as we're done with the fork there's no need to hold
+ // a lock any more because the parent won't do anything and the child is
+ // in its own process.
+ let result = unsafe {
+ let _env_lock = sys::os::env_lock();
+ cvt(libc::fork())?
+ };
+
let pid = unsafe {
- match cvt(libc::fork())? {
+ match result {
0 => {
drop(input);
let err = self.do_exec(theirs, envp.as_ref());
}
match self.setup_io(default, true) {
- Ok((_, theirs)) => unsafe { self.do_exec(theirs, envp.as_ref()) },
+ Ok((_, theirs)) => {
+ unsafe {
+ // Similar to when forking, we want to ensure that access to
+ // the environment is synchronized, so make sure to grab the
+ // environment lock before we try to exec.
+ let _lock = sys::os::env_lock();
+
+ self.do_exec(theirs, envp.as_ref())
+ }
+ }
Err(e) => e,
}
}
if let Some(ref cwd) = *self.get_cwd() {
t!(cvt(libc::chdir(cwd.as_ptr())));
}
- if let Some(envp) = maybe_envp {
- *sys::os::environ() = envp.as_ptr();
- }
// emscripten has no signal support.
#[cfg(not(any(target_os = "emscripten")))]
t!(callback());
}
+ // Although we're performing an exec here we may also return with an
+ // error from this function (without actually exec'ing) in which case we
+ // want to be sure to restore the global environment back to what it
+ // once was, ensuring that our temporary override, when free'd, doesn't
+ // corrupt our process's environment.
+ let mut _reset = None;
+ if let Some(envp) = maybe_envp {
+ struct Reset(*const *const libc::c_char);
+
+ impl Drop for Reset {
+ fn drop(&mut self) {
+ unsafe {
+ *sys::os::environ() = self.0;
+ }
+ }
+ }
+
+ _reset = Some(Reset(*sys::os::environ()));
+ *sys::os::environ() = envp.as_ptr();
+ }
+
libc::execvp(self.get_argv()[0], self.get_argv().as_ptr());
io::Error::last_os_error()
}
libc::POSIX_SPAWN_SETSIGMASK;
cvt(libc::posix_spawnattr_setflags(&mut attrs.0, flags as _))?;
+ // Make sure we synchronize access to the global `environ` resource
+ let _env_lock = sys::os::env_lock();
let envp = envp.map(|c| c.as_ptr())
.unwrap_or_else(|| *sys::os::environ() as *const _);
let ret = libc::posix_spawnp(
println!("passed");
}
+ "exec-test5" => {
+ env::set_var("VARIABLE", "ABC");
+ Command::new("definitely-not-a-real-binary").env("VARIABLE", "XYZ").exec();
+ assert_eq!(env::var("VARIABLE").unwrap(), "ABC");
+ println!("passed");
+ }
+
+ "exec-test6" => {
+ let err = Command::new("echo").arg("passed").env_clear().exec();
+ panic!("failed to spawn: {}", err);
+ }
+
+ "exec-test7" => {
+ let err = Command::new("echo").arg("passed").env_remove("PATH").exec();
+ panic!("failed to spawn: {}", err);
+ }
+
_ => panic!("unknown argument: {}", arg),
}
return
assert!(output.status.success());
assert!(output.stderr.is_empty());
assert_eq!(output.stdout, b"passed\n");
+
+ let output = Command::new(&me).arg("exec-test5").output().unwrap();
+ assert!(output.status.success());
+ assert!(output.stderr.is_empty());
+ assert_eq!(output.stdout, b"passed\n");
+
+ if cfg!(target_os = "linux") {
+ let output = Command::new(&me).arg("exec-test6").output().unwrap();
+ println!("{:?}", output);
+ assert!(output.status.success());
+ assert!(output.stderr.is_empty());
+ assert_eq!(output.stdout, b"passed\n");
+
+ let output = Command::new(&me).arg("exec-test7").output().unwrap();
+ println!("{:?}", output);
+ assert!(output.status.success());
+ assert!(output.stderr.is_empty());
+ assert_eq!(output.stdout, b"passed\n");
+ }
}