Skip to content

Commit ca6c12b

Browse files
home: Handle empty HOME on unix
1 parent cb81efb commit ca6c12b

File tree

5 files changed

+107
-6
lines changed

5 files changed

+107
-6
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/home/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,8 @@ readme = "README.md"
1616
repository = "https://github.com/rust-lang/cargo"
1717
description = "Shared definitions of home directories."
1818

19+
[target.'cfg(unix)'.dependencies]
20+
libc = "0.2"
21+
1922
[target.'cfg(windows)'.dependencies]
2023
windows-sys = { version = "0.45.0", features = ["Win32_Foundation", "Win32_UI_Shell"] }

crates/home/src/lib.rs

+10-5
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
//! incorrect because it considers the `HOME` environment variable on
1414
//! Windows. This causes surprising situations where a Rust program
1515
//! will behave differently depending on whether it is run under a
16-
//! Unix emulation environment like Cygwin or MinGW. Neither Cargo nor
17-
//! rustup use the standard libraries definition - they use the
18-
//! definition here.
16+
//! Unix emulation environment like Cygwin or MinGW. Additionally, on Unix,
17+
//! if the `HOME` environment variable is set but empty, an empty path is
18+
//! returned. Neither Cargo nor rustup use the standard libraries definition
19+
//! - they use the definition here.
1920
//!
2021
//! This crate further provides two functions, `cargo_home` and
2122
//! `rustup_home`, which are the canonical way to determine the
@@ -30,7 +31,9 @@
3031

3132
pub mod env;
3233

33-
#[cfg(target_os = "windows")]
34+
#[cfg(unix)]
35+
mod unix;
36+
#[cfg(windows)]
3437
mod windows;
3538

3639
use std::io;
@@ -65,10 +68,12 @@ pub fn home_dir() -> Option<PathBuf> {
6568
env::home_dir_with_env(&env::OS_ENV)
6669
}
6770

71+
#[cfg(unix)]
72+
use unix::home_dir_inner;
6873
#[cfg(windows)]
6974
use windows::home_dir_inner;
7075

71-
#[cfg(any(unix, target_os = "redox"))]
76+
#[cfg(target_os = "redox")]
7277
fn home_dir_inner() -> Option<PathBuf> {
7378
#[allow(deprecated)]
7479
std::env::home_dir()

crates/home/src/unix.rs

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use std::env;
2+
use std::ffi::{CStr, OsString};
3+
use std::mem;
4+
use std::os::unix::prelude::OsStringExt;
5+
use std::path::PathBuf;
6+
use std::ptr;
7+
8+
pub fn home_dir_inner() -> Option<PathBuf> {
9+
return env::var_os("HOME")
10+
.filter(|s| !s.is_empty())
11+
.or_else(|| unsafe { fallback() })
12+
.map(PathBuf::from);
13+
14+
#[cfg(any(
15+
target_os = "android",
16+
target_os = "ios",
17+
target_os = "watchos",
18+
target_os = "emscripten",
19+
target_os = "redox",
20+
target_os = "vxworks",
21+
target_os = "espidf",
22+
target_os = "horizon"
23+
))]
24+
unsafe fn fallback() -> Option<OsString> {
25+
None
26+
}
27+
#[cfg(not(any(
28+
target_os = "android",
29+
target_os = "ios",
30+
target_os = "watchos",
31+
target_os = "emscripten",
32+
target_os = "redox",
33+
target_os = "vxworks",
34+
target_os = "espidf",
35+
target_os = "horizon"
36+
)))]
37+
unsafe fn fallback() -> Option<OsString> {
38+
let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) {
39+
n if n < 0 => 512_usize,
40+
n => n as usize,
41+
};
42+
let mut buf = Vec::with_capacity(amt);
43+
let mut passwd: libc::passwd = mem::zeroed();
44+
let mut result = ptr::null_mut();
45+
match libc::getpwuid_r(
46+
libc::getuid(),
47+
&mut passwd,
48+
buf.as_mut_ptr(),
49+
buf.capacity(),
50+
&mut result,
51+
) {
52+
0 if !result.is_null() => {
53+
let ptr = passwd.pw_dir as *const _;
54+
let bytes = CStr::from_ptr(ptr).to_bytes().to_vec();
55+
Some(OsStringExt::from_vec(bytes))
56+
}
57+
_ => None,
58+
}
59+
}
60+
}
61+
62+
#[cfg(not(any(
63+
target_os = "android",
64+
target_os = "ios",
65+
target_os = "watchos",
66+
target_os = "emscripten",
67+
target_os = "redox",
68+
target_os = "vxworks",
69+
target_os = "espidf",
70+
target_os = "horizon"
71+
)))]
72+
#[cfg(test)]
73+
mod tests {
74+
use super::home_dir_inner;
75+
use std::env;
76+
use std::path::{Path, PathBuf};
77+
78+
#[test]
79+
fn test_with_without() {
80+
let oldhome: Option<PathBuf> = Some(env::var_os("HOME").unwrap().into());
81+
env::remove_var("HOME");
82+
assert_eq!(home_dir_inner(), oldhome);
83+
84+
let home = Path::new("");
85+
env::set_var("HOME", home.as_os_str());
86+
assert_eq!(home_dir_inner(), oldhome);
87+
88+
let home = Path::new("/home/foobarbaz");
89+
env::set_var("HOME", home.as_os_str());
90+
assert_eq!(home_dir_inner().as_deref(), Some(home));
91+
}
92+
}

crates/home/src/windows.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ mod tests {
5555

5656
assert_eq!(home_dir_inner(), Some(PathBuf::from(olduserprofile)));
5757

58-
let home = Path::new(r"C:\Users\foo tar baz");
58+
let home = Path::new(r"C:\Users\foo bar baz");
5959

6060
env::set_var("HOME", home.as_os_str());
6161
assert_ne!(home_dir_inner().as_ref().map(Deref::deref), Some(home));

0 commit comments

Comments
 (0)