about summary refs log tree commit diff
path: root/src/libstd/sys
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2015-11-06 00:56:08 +0000
committerbors <bors@rust-lang.org>2015-11-06 00:56:08 +0000
commit41308a05203c342ae0899d80b1bc3657de7fec8b (patch)
treed68ab0a72705855be5943a891e9b75c708110c8d /src/libstd/sys
parent1dac3adc34a85670d81f86345054ec58e658b68b (diff)
parent4b43e07af90558d7d3c0eafd08776909d542b7d7 (diff)
downloadrust-41308a05203c342ae0899d80b1bc3657de7fec8b.tar.gz
rust-41308a05203c342ae0899d80b1bc3657de7fec8b.zip
Auto merge of #29305 - alexcrichton:bad-getenv, r=brson
As discovered in #29298, `env::set_var("", "")` will panic, but it turns out
that it *also* deadlocks on Unix systems. This happens because if a panic
happens while holding the environment lock, we then go try to read
RUST_BACKTRACE, grabbing the environment lock, causing a deadlock.

Specifically, the changes made here are:

* The environment lock is pushed into `std::sys` instead of `std::env`. This
  also only puts it in the Unix implementation, not Windows where the functions
  are already threadsafe.
* The `std::sys` implementation now returns `io::Result` so panics are
  explicitly at the `std::env` level.
Diffstat (limited to 'src/libstd/sys')
-rw-r--r--src/libstd/sys/unix/os.rs49
-rw-r--r--src/libstd/sys/windows/c.rs1
-rw-r--r--src/libstd/sys/windows/os.rs48
3 files changed, 52 insertions, 46 deletions
diff --git a/src/libstd/sys/unix/os.rs b/src/libstd/sys/unix/os.rs
index c328c84f62a..4e7058d0687 100644
--- a/src/libstd/sys/unix/os.rs
+++ b/src/libstd/sys/unix/os.rs
@@ -26,11 +26,14 @@ use path::{self, PathBuf};
 use ptr;
 use slice;
 use str;
+use sync::StaticMutex;
 use sys::c;
 use sys::fd;
+use sys::cvt;
 use vec;
 
 const TMPBUF_SZ: usize = 128;
+static ENV_LOCK: StaticMutex = StaticMutex::new();
 
 /// Returns the platform-specific value of errno
 pub fn errno() -> i32 {
@@ -378,6 +381,7 @@ pub unsafe fn environ() -> *mut *const *const c_char {
 /// Returns a vector of (variable, value) byte-vector pairs for all the
 /// environment variables of the current process.
 pub fn env() -> Env {
+    let _g = ENV_LOCK.lock();
     return unsafe {
         let mut environ = *environ();
         if environ as usize == 0 {
@@ -401,35 +405,36 @@ pub fn env() -> Env {
     }
 }
 
-pub fn getenv(k: &OsStr) -> Option<OsString> {
-    unsafe {
-        let s = k.to_cstring().unwrap();
-        let s = libc::getenv(s.as_ptr()) as *const _;
+pub fn getenv(k: &OsStr) -> io::Result<Option<OsString>> {
+    // environment variables with a nul byte can't be set, so their value is
+    // always None as well
+    let k = try!(CString::new(k.as_bytes()));
+    let _g = ENV_LOCK.lock();
+    Ok(unsafe {
+        let s = libc::getenv(k.as_ptr()) as *const _;
         if s.is_null() {
             None
         } else {
             Some(OsStringExt::from_vec(CStr::from_ptr(s).to_bytes().to_vec()))
         }
-    }
+    })
 }
 
-pub fn setenv(k: &OsStr, v: &OsStr) {
-    unsafe {
-        let k = k.to_cstring().unwrap();
-        let v = v.to_cstring().unwrap();
-        if libc::funcs::posix01::unistd::setenv(k.as_ptr(), v.as_ptr(), 1) != 0 {
-            panic!("failed setenv: {}", io::Error::last_os_error());
-        }
-    }
+pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
+    let k = try!(CString::new(k.as_bytes()));
+    let v = try!(CString::new(v.as_bytes()));
+    let _g = ENV_LOCK.lock();
+    cvt(unsafe {
+        libc::funcs::posix01::unistd::setenv(k.as_ptr(), v.as_ptr(), 1)
+    }).map(|_| ())
 }
 
-pub fn unsetenv(n: &OsStr) {
-    unsafe {
-        let nbuf = n.to_cstring().unwrap();
-        if libc::funcs::posix01::unistd::unsetenv(nbuf.as_ptr()) != 0 {
-            panic!("failed unsetenv: {}", io::Error::last_os_error());
-        }
-    }
+pub fn unsetenv(n: &OsStr) -> io::Result<()> {
+    let nbuf = try!(CString::new(n.as_bytes()));
+    let _g = ENV_LOCK.lock();
+    cvt(unsafe {
+        libc::funcs::posix01::unistd::unsetenv(nbuf.as_ptr())
+    }).map(|_| ())
 }
 
 pub fn page_size() -> usize {
@@ -439,7 +444,7 @@ pub fn page_size() -> usize {
 }
 
 pub fn temp_dir() -> PathBuf {
-    getenv("TMPDIR".as_ref()).map(PathBuf::from).unwrap_or_else(|| {
+    ::env::var_os("TMPDIR").map(PathBuf::from).unwrap_or_else(|| {
         if cfg!(target_os = "android") {
             PathBuf::from("/data/local/tmp")
         } else {
@@ -449,7 +454,7 @@ pub fn temp_dir() -> PathBuf {
 }
 
 pub fn home_dir() -> Option<PathBuf> {
-    return getenv("HOME".as_ref()).or_else(|| unsafe {
+    return ::env::var_os("HOME").or_else(|| unsafe {
         fallback()
     }).map(PathBuf::from);
 
diff --git a/src/libstd/sys/windows/c.rs b/src/libstd/sys/windows/c.rs
index b168081ff8a..270e260d504 100644
--- a/src/libstd/sys/windows/c.rs
+++ b/src/libstd/sys/windows/c.rs
@@ -32,6 +32,7 @@ pub const WSASYS_STATUS_LEN: usize = 128;
 pub const FIONBIO: libc::c_long = 0x8004667e;
 pub const FD_SETSIZE: usize = 64;
 pub const MSG_DONTWAIT: libc::c_int = 0;
+pub const ERROR_ENVVAR_NOT_FOUND: libc::c_int = 203;
 pub const ERROR_ILLEGAL_CHARACTER: libc::c_int = 582;
 pub const ENABLE_ECHO_INPUT: libc::DWORD = 0x4;
 pub const ENABLE_EXTENDED_FLAGS: libc::DWORD = 0x80;
diff --git a/src/libstd/sys/windows/os.rs b/src/libstd/sys/windows/os.rs
index 1680ea88d0b..ba4bce38014 100644
--- a/src/libstd/sys/windows/os.rs
+++ b/src/libstd/sys/windows/os.rs
@@ -26,7 +26,7 @@ use os::windows::ffi::EncodeWide;
 use path::{self, PathBuf};
 use ptr;
 use slice;
-use sys::c;
+use sys::{c, cvt};
 use sys::handle::Handle;
 
 use libc::funcs::extra::kernel32::{
@@ -248,41 +248,41 @@ pub fn chdir(p: &path::Path) -> io::Result<()> {
     let mut p = p.encode_wide().collect::<Vec<_>>();
     p.push(0);
 
-    unsafe {
-        match libc::SetCurrentDirectoryW(p.as_ptr()) != (0 as libc::BOOL) {
-            true => Ok(()),
-            false => Err(io::Error::last_os_error()),
-        }
-    }
+    cvt(unsafe {
+        libc::SetCurrentDirectoryW(p.as_ptr())
+    }).map(|_| ())
 }
 
-pub fn getenv(k: &OsStr) -> Option<OsString> {
+pub fn getenv(k: &OsStr) -> io::Result<Option<OsString>> {
     let k = super::to_utf16_os(k);
-    super::fill_utf16_buf(|buf, sz| unsafe {
+    let res = super::fill_utf16_buf(|buf, sz| unsafe {
         libc::GetEnvironmentVariableW(k.as_ptr(), buf, sz)
     }, |buf| {
         OsStringExt::from_wide(buf)
-    }).ok()
+    });
+    match res {
+        Ok(value) => Ok(Some(value)),
+        Err(ref e) if e.raw_os_error() == Some(c::ERROR_ENVVAR_NOT_FOUND) => {
+            Ok(None)
+        }
+        Err(e) => Err(e)
+    }
 }
 
-pub fn setenv(k: &OsStr, v: &OsStr) {
+pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
     let k = super::to_utf16_os(k);
     let v = super::to_utf16_os(v);
 
-    unsafe {
-        if libc::SetEnvironmentVariableW(k.as_ptr(), v.as_ptr()) == 0 {
-            panic!("failed to set env: {}", io::Error::last_os_error());
-        }
-    }
+    cvt(unsafe {
+        libc::SetEnvironmentVariableW(k.as_ptr(), v.as_ptr())
+    }).map(|_| ())
 }
 
-pub fn unsetenv(n: &OsStr) {
+pub fn unsetenv(n: &OsStr) -> io::Result<()> {
     let v = super::to_utf16_os(n);
-    unsafe {
-        if libc::SetEnvironmentVariableW(v.as_ptr(), ptr::null()) == 0 {
-            panic!("failed to unset env: {}", io::Error::last_os_error());
-        }
-    }
+    cvt(unsafe {
+        libc::SetEnvironmentVariableW(v.as_ptr(), ptr::null())
+    }).map(|_| ())
 }
 
 pub struct Args {
@@ -339,8 +339,8 @@ pub fn temp_dir() -> PathBuf {
 }
 
 pub fn home_dir() -> Option<PathBuf> {
-    getenv("HOME".as_ref()).or_else(|| {
-        getenv("USERPROFILE".as_ref())
+    ::env::var_os("HOME").or_else(|| {
+        ::env::var_os("USERPROFILE")
     }).map(PathBuf::from).or_else(|| unsafe {
         let me = c::GetCurrentProcess();
         let mut token = ptr::null_mut();