Skip to content

Commit 4b43e07

Browse files
committed
std: Slightly more robust env var handling
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. The panic messages have also been improved in these situations.
1 parent 9a85566 commit 4b43e07

File tree

4 files changed

+62
-56
lines changed

4 files changed

+62
-56
lines changed

src/libstd/env.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ use ffi::{OsStr, OsString};
2323
use fmt;
2424
use io;
2525
use path::{Path, PathBuf};
26-
use sync::StaticMutex;
2726
use sys::os as os_imp;
2827

2928
/// Returns the current working directory as a `PathBuf`.
@@ -68,8 +67,6 @@ pub fn set_current_dir<P: AsRef<Path>>(p: P) -> io::Result<()> {
6867
os_imp::chdir(p.as_ref())
6968
}
7069

71-
static ENV_LOCK: StaticMutex = StaticMutex::new();
72-
7370
/// An iterator over a snapshot of the environment variables of this process.
7471
///
7572
/// This iterator is created through `std::env::vars()` and yields `(String,
@@ -133,7 +130,6 @@ pub fn vars() -> Vars {
133130
/// ```
134131
#[stable(feature = "env", since = "1.0.0")]
135132
pub fn vars_os() -> VarsOs {
136-
let _g = ENV_LOCK.lock();
137133
VarsOs { inner: os_imp::env() }
138134
}
139135

@@ -204,8 +200,9 @@ pub fn var_os<K: AsRef<OsStr>>(key: K) -> Option<OsString> {
204200
}
205201

206202
fn _var_os(key: &OsStr) -> Option<OsString> {
207-
let _g = ENV_LOCK.lock();
208-
os_imp::getenv(key)
203+
os_imp::getenv(key).unwrap_or_else(|e| {
204+
panic!("failed to get environment variable `{:?}`: {}", key, e)
205+
})
209206
}
210207

211208
/// Possible errors from the `env::var` method.
@@ -275,8 +272,10 @@ pub fn set_var<K: AsRef<OsStr>, V: AsRef<OsStr>>(k: K, v: V) {
275272
}
276273

277274
fn _set_var(k: &OsStr, v: &OsStr) {
278-
let _g = ENV_LOCK.lock();
279-
os_imp::setenv(k, v)
275+
os_imp::setenv(k, v).unwrap_or_else(|e| {
276+
panic!("failed to set environment variable `{:?}` to `{:?}`: {}",
277+
k, v, e)
278+
})
280279
}
281280

282281
/// Removes an environment variable from the environment of the currently running process.
@@ -310,8 +309,9 @@ pub fn remove_var<K: AsRef<OsStr>>(k: K) {
310309
}
311310

312311
fn _remove_var(k: &OsStr) {
313-
let _g = ENV_LOCK.lock();
314-
os_imp::unsetenv(k)
312+
os_imp::unsetenv(k).unwrap_or_else(|e| {
313+
panic!("failed to remove environment variable `{:?}`: {}", k, e)
314+
})
315315
}
316316

317317
/// An iterator over `Path` instances for parsing an environment variable

src/libstd/sys/unix/os.rs

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@ use path::{self, PathBuf};
2626
use ptr;
2727
use slice;
2828
use str;
29+
use sync::StaticMutex;
2930
use sys::c;
3031
use sys::fd;
32+
use sys::cvt;
3133
use vec;
3234

3335
const TMPBUF_SZ: usize = 128;
36+
static ENV_LOCK: StaticMutex = StaticMutex::new();
3437

3538
/// Returns the platform-specific value of errno
3639
pub fn errno() -> i32 {
@@ -397,6 +400,7 @@ pub unsafe fn environ() -> *mut *const *const c_char {
397400
/// Returns a vector of (variable, value) byte-vector pairs for all the
398401
/// environment variables of the current process.
399402
pub fn env() -> Env {
403+
let _g = ENV_LOCK.lock();
400404
return unsafe {
401405
let mut environ = *environ();
402406
if environ as usize == 0 {
@@ -420,35 +424,36 @@ pub fn env() -> Env {
420424
}
421425
}
422426

423-
pub fn getenv(k: &OsStr) -> Option<OsString> {
424-
unsafe {
425-
let s = k.to_cstring().unwrap();
426-
let s = libc::getenv(s.as_ptr()) as *const _;
427+
pub fn getenv(k: &OsStr) -> io::Result<Option<OsString>> {
428+
// environment variables with a nul byte can't be set, so their value is
429+
// always None as well
430+
let k = try!(CString::new(k.as_bytes()));
431+
let _g = ENV_LOCK.lock();
432+
Ok(unsafe {
433+
let s = libc::getenv(k.as_ptr()) as *const _;
427434
if s.is_null() {
428435
None
429436
} else {
430437
Some(OsStringExt::from_vec(CStr::from_ptr(s).to_bytes().to_vec()))
431438
}
432-
}
439+
})
433440
}
434441

435-
pub fn setenv(k: &OsStr, v: &OsStr) {
436-
unsafe {
437-
let k = k.to_cstring().unwrap();
438-
let v = v.to_cstring().unwrap();
439-
if libc::funcs::posix01::unistd::setenv(k.as_ptr(), v.as_ptr(), 1) != 0 {
440-
panic!("failed setenv: {}", io::Error::last_os_error());
441-
}
442-
}
442+
pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
443+
let k = try!(CString::new(k.as_bytes()));
444+
let v = try!(CString::new(v.as_bytes()));
445+
let _g = ENV_LOCK.lock();
446+
cvt(unsafe {
447+
libc::funcs::posix01::unistd::setenv(k.as_ptr(), v.as_ptr(), 1)
448+
}).map(|_| ())
443449
}
444450

445-
pub fn unsetenv(n: &OsStr) {
446-
unsafe {
447-
let nbuf = n.to_cstring().unwrap();
448-
if libc::funcs::posix01::unistd::unsetenv(nbuf.as_ptr()) != 0 {
449-
panic!("failed unsetenv: {}", io::Error::last_os_error());
450-
}
451-
}
451+
pub fn unsetenv(n: &OsStr) -> io::Result<()> {
452+
let nbuf = try!(CString::new(n.as_bytes()));
453+
let _g = ENV_LOCK.lock();
454+
cvt(unsafe {
455+
libc::funcs::posix01::unistd::unsetenv(nbuf.as_ptr())
456+
}).map(|_| ())
452457
}
453458

454459
pub fn page_size() -> usize {
@@ -458,7 +463,7 @@ pub fn page_size() -> usize {
458463
}
459464

460465
pub fn temp_dir() -> PathBuf {
461-
getenv("TMPDIR".as_ref()).map(PathBuf::from).unwrap_or_else(|| {
466+
::env::var_os("TMPDIR").map(PathBuf::from).unwrap_or_else(|| {
462467
if cfg!(target_os = "android") {
463468
PathBuf::from("/data/local/tmp")
464469
} else {
@@ -468,7 +473,7 @@ pub fn temp_dir() -> PathBuf {
468473
}
469474

470475
pub fn home_dir() -> Option<PathBuf> {
471-
return getenv("HOME".as_ref()).or_else(|| unsafe {
476+
return ::env::var_os("HOME").or_else(|| unsafe {
472477
fallback()
473478
}).map(PathBuf::from);
474479

src/libstd/sys/windows/c.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub const WSASYS_STATUS_LEN: usize = 128;
3232
pub const FIONBIO: libc::c_long = 0x8004667e;
3333
pub const FD_SETSIZE: usize = 64;
3434
pub const MSG_DONTWAIT: libc::c_int = 0;
35+
pub const ERROR_ENVVAR_NOT_FOUND: libc::c_int = 203;
3536
pub const ERROR_ILLEGAL_CHARACTER: libc::c_int = 582;
3637
pub const ENABLE_ECHO_INPUT: libc::DWORD = 0x4;
3738
pub const ENABLE_EXTENDED_FLAGS: libc::DWORD = 0x80;

src/libstd/sys/windows/os.rs

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use os::windows::ffi::EncodeWide;
2626
use path::{self, PathBuf};
2727
use ptr;
2828
use slice;
29-
use sys::c;
29+
use sys::{c, cvt};
3030
use sys::handle::Handle;
3131

3232
use libc::funcs::extra::kernel32::{
@@ -248,41 +248,41 @@ pub fn chdir(p: &path::Path) -> io::Result<()> {
248248
let mut p = p.encode_wide().collect::<Vec<_>>();
249249
p.push(0);
250250

251-
unsafe {
252-
match libc::SetCurrentDirectoryW(p.as_ptr()) != (0 as libc::BOOL) {
253-
true => Ok(()),
254-
false => Err(io::Error::last_os_error()),
255-
}
256-
}
251+
cvt(unsafe {
252+
libc::SetCurrentDirectoryW(p.as_ptr())
253+
}).map(|_| ())
257254
}
258255

259-
pub fn getenv(k: &OsStr) -> Option<OsString> {
256+
pub fn getenv(k: &OsStr) -> io::Result<Option<OsString>> {
260257
let k = super::to_utf16_os(k);
261-
super::fill_utf16_buf(|buf, sz| unsafe {
258+
let res = super::fill_utf16_buf(|buf, sz| unsafe {
262259
libc::GetEnvironmentVariableW(k.as_ptr(), buf, sz)
263260
}, |buf| {
264261
OsStringExt::from_wide(buf)
265-
}).ok()
262+
});
263+
match res {
264+
Ok(value) => Ok(Some(value)),
265+
Err(ref e) if e.raw_os_error() == Some(c::ERROR_ENVVAR_NOT_FOUND) => {
266+
Ok(None)
267+
}
268+
Err(e) => Err(e)
269+
}
266270
}
267271

268-
pub fn setenv(k: &OsStr, v: &OsStr) {
272+
pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
269273
let k = super::to_utf16_os(k);
270274
let v = super::to_utf16_os(v);
271275

272-
unsafe {
273-
if libc::SetEnvironmentVariableW(k.as_ptr(), v.as_ptr()) == 0 {
274-
panic!("failed to set env: {}", io::Error::last_os_error());
275-
}
276-
}
276+
cvt(unsafe {
277+
libc::SetEnvironmentVariableW(k.as_ptr(), v.as_ptr())
278+
}).map(|_| ())
277279
}
278280

279-
pub fn unsetenv(n: &OsStr) {
281+
pub fn unsetenv(n: &OsStr) -> io::Result<()> {
280282
let v = super::to_utf16_os(n);
281-
unsafe {
282-
if libc::SetEnvironmentVariableW(v.as_ptr(), ptr::null()) == 0 {
283-
panic!("failed to unset env: {}", io::Error::last_os_error());
284-
}
285-
}
283+
cvt(unsafe {
284+
libc::SetEnvironmentVariableW(v.as_ptr(), ptr::null())
285+
}).map(|_| ())
286286
}
287287

288288
pub struct Args {
@@ -339,8 +339,8 @@ pub fn temp_dir() -> PathBuf {
339339
}
340340

341341
pub fn home_dir() -> Option<PathBuf> {
342-
getenv("HOME".as_ref()).or_else(|| {
343-
getenv("USERPROFILE".as_ref())
342+
::env::var_os("HOME").or_else(|| {
343+
::env::var_os("USERPROFILE")
344344
}).map(PathBuf::from).or_else(|| unsafe {
345345
let me = c::GetCurrentProcess();
346346
let mut token = ptr::null_mut();

0 commit comments

Comments
 (0)