From 95dd0883db3d8fd5c79bdf57912fdc2e64a64fcb Mon Sep 17 00:00:00 2001 From: Utkarsh Gupta Date: Mon, 24 Apr 2023 07:12:03 +0530 Subject: [PATCH] home: Handle empty `HOME` on unix --- Cargo.lock | 1 + crates/home/Cargo.toml | 3 ++ crates/home/src/lib.rs | 15 ++++--- crates/home/src/unix.rs | 92 ++++++++++++++++++++++++++++++++++++++ crates/home/src/windows.rs | 2 +- 5 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 crates/home/src/unix.rs diff --git a/Cargo.lock b/Cargo.lock index e774b5db0ba..b55cddaf900 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1703,6 +1703,7 @@ dependencies = [ name = "home" version = "0.5.5" dependencies = [ + "libc", "windows-sys 0.48.0", ] diff --git a/crates/home/Cargo.toml b/crates/home/Cargo.toml index 90211c28d78..a49f09169d9 100644 --- a/crates/home/Cargo.toml +++ b/crates/home/Cargo.toml @@ -16,5 +16,8 @@ readme = "README.md" repository = "https://github.com/rust-lang/cargo" description = "Shared definitions of home directories." +[target.'cfg(unix)'.dependencies] +libc = "0.2" + [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.48.0", features = ["Win32_Foundation", "Win32_UI_Shell"] } diff --git a/crates/home/src/lib.rs b/crates/home/src/lib.rs index a8af963f34e..b23ea863924 100644 --- a/crates/home/src/lib.rs +++ b/crates/home/src/lib.rs @@ -13,9 +13,10 @@ //! incorrect because it considers the `HOME` environment variable on //! Windows. This causes surprising situations where a Rust program //! will behave differently depending on whether it is run under a -//! Unix emulation environment like Cygwin or MinGW. Neither Cargo nor -//! rustup use the standard libraries definition - they use the -//! definition here. +//! Unix emulation environment like Cygwin or MinGW. Additionally, on Unix, +//! if the `HOME` environment variable is set but empty, an empty path is +//! returned. Neither Cargo nor rustup use the standard libraries definition +//! - they use the definition here. //! //! This crate further provides two functions, `cargo_home` and //! `rustup_home`, which are the canonical way to determine the @@ -30,7 +31,9 @@ pub mod env; -#[cfg(target_os = "windows")] +#[cfg(unix)] +mod unix; +#[cfg(windows)] mod windows; use std::io; @@ -65,10 +68,12 @@ pub fn home_dir() -> Option { env::home_dir_with_env(&env::OS_ENV) } +#[cfg(unix)] +use unix::home_dir_inner; #[cfg(windows)] use windows::home_dir_inner; -#[cfg(any(unix, target_os = "redox"))] +#[cfg(target_os = "redox")] fn home_dir_inner() -> Option { #[allow(deprecated)] std::env::home_dir() diff --git a/crates/home/src/unix.rs b/crates/home/src/unix.rs new file mode 100644 index 00000000000..412da269176 --- /dev/null +++ b/crates/home/src/unix.rs @@ -0,0 +1,92 @@ +use std::env; +use std::ffi::{CStr, OsString}; +use std::mem; +use std::os::unix::prelude::OsStringExt; +use std::path::PathBuf; +use std::ptr; + +pub fn home_dir_inner() -> Option { + return env::var_os("HOME") + .filter(|s| !s.is_empty()) + .or_else(|| unsafe { fallback() }) + .map(PathBuf::from); + + #[cfg(any( + target_os = "android", + target_os = "ios", + target_os = "watchos", + target_os = "emscripten", + target_os = "redox", + target_os = "vxworks", + target_os = "espidf", + target_os = "horizon" + ))] + unsafe fn fallback() -> Option { + None + } + #[cfg(not(any( + target_os = "android", + target_os = "ios", + target_os = "watchos", + target_os = "emscripten", + target_os = "redox", + target_os = "vxworks", + target_os = "espidf", + target_os = "horizon" + )))] + unsafe fn fallback() -> Option { + let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) { + n if n < 0 => 512_usize, + n => n as usize, + }; + let mut buf = Vec::with_capacity(amt); + let mut passwd: libc::passwd = mem::zeroed(); + let mut result = ptr::null_mut(); + match libc::getpwuid_r( + libc::getuid(), + &mut passwd, + buf.as_mut_ptr(), + buf.capacity(), + &mut result, + ) { + 0 if !result.is_null() => { + let ptr = passwd.pw_dir as *const _; + let bytes = CStr::from_ptr(ptr).to_bytes().to_vec(); + Some(OsStringExt::from_vec(bytes)) + } + _ => None, + } + } +} + +#[cfg(not(any( + target_os = "android", + target_os = "ios", + target_os = "watchos", + target_os = "emscripten", + target_os = "redox", + target_os = "vxworks", + target_os = "espidf", + target_os = "horizon" +)))] +#[cfg(test)] +mod tests { + use super::home_dir_inner; + use std::env; + use std::path::{Path, PathBuf}; + + #[test] + fn test_with_without() { + let oldhome: Option = Some(env::var_os("HOME").unwrap().into()); + env::remove_var("HOME"); + assert_eq!(home_dir_inner(), oldhome); + + let home = Path::new(""); + env::set_var("HOME", home.as_os_str()); + assert_eq!(home_dir_inner(), oldhome); + + let home = Path::new("/home/foobarbaz"); + env::set_var("HOME", home.as_os_str()); + assert_eq!(home_dir_inner().as_deref(), Some(home)); + } +} diff --git a/crates/home/src/windows.rs b/crates/home/src/windows.rs index a35dc9c5719..7a2cd7c5e3e 100644 --- a/crates/home/src/windows.rs +++ b/crates/home/src/windows.rs @@ -55,7 +55,7 @@ mod tests { assert_eq!(home_dir_inner(), Some(PathBuf::from(olduserprofile))); - let home = Path::new(r"C:\Users\foo tar baz"); + let home = Path::new(r"C:\Users\foo bar baz"); env::set_var("HOME", home.as_os_str()); assert_ne!(home_dir_inner().as_ref().map(Deref::deref), Some(home));