Skip to content

Commit ca75044

Browse files
committed
fix(fileio): expand ~ via passwd/AppData home when HOME is unset (GNU get_homedir)
The Windows pdump generation failed with file-missing 'Opening directory ~': directory-files "~" -> expand-file-name -> home_directory_for_expand_file_name read only getenv("HOME"). On the Windows build runner HOME is unset (only APPDATA/ USERPROFILE), so `~` stayed literal and the listing failed fatally at startup. Reproduced locally on Linux with 'env -u HOME' (`~` -> "~"), which let me fix and verify without Windows CI round-trips. Mirror GNU's get_homedir (fileio.c): when HOME is unset, fall back to the current user's home -- getpwnam($LOGNAME/$USER) then getpwuid(getuid()) on Unix; the AppData-derived home (APPDATA/USERPROFILE/"C:/", where GNU w32.c init_environment puts HOME) on Windows -- via new current_user_home_bytes(), wired into both home_directory_for_expand_file_name and home_env_bytes. Verified: 'env -u HOME neomacs' now expands `~` to the passwd home and directory-files "~" succeeds; with HOME set, behavior is unchanged.
1 parent 2759eef commit ca75044

1 file changed

Lines changed: 58 additions & 10 deletions

File tree

neovm-core/src/emacs_core/fileio.rs

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -230,16 +230,17 @@ fn expand_file_name_bytes_with_home(
230230
fn home_env_bytes() -> Option<Vec<u8>> {
231231
#[cfg(unix)]
232232
{
233-
Some(std::env::var_os("HOME")?.as_bytes().to_vec())
233+
if let Some(home) = std::env::var_os("HOME") {
234+
return Some(home.as_bytes().to_vec());
235+
}
236+
current_user_home_bytes()
234237
}
235238
#[cfg(not(unix))]
236239
{
237-
let home = std::env::var_os("HOME")
238-
.or_else(|| std::env::var_os("APPDATA"))
239-
.or_else(|| std::env::var_os("USERPROFILE"))
240-
.map(|s| s.to_string_lossy().into_owned())
241-
.unwrap_or_else(|| "C:/".to_string());
242-
Some(home.into_bytes())
240+
if let Some(home) = std::env::var_os("HOME") {
241+
return Some(home.to_string_lossy().into_owned().into_bytes());
242+
}
243+
current_user_home_bytes()
243244
}
244245
}
245246

@@ -305,6 +306,46 @@ fn user_homedir_bytes(user: &[u8]) -> Option<Vec<u8>> {
305306
}
306307
}
307308

309+
/// Home directory of the *current* user, mirroring GNU `get_homedir`'s fallback
310+
/// when `HOME` is unset (fileio.c): on Unix, `getpwnam` on $LOGNAME then $USER,
311+
/// else `getpwuid(getuid())`; on non-Unix, the OS-derived home where GNU
312+
/// `w32.c init_environment` puts HOME -- the roaming AppData folder
313+
/// (CSIDL_APPDATA == %APPDATA%), else %USERPROFILE%, else "C:/".
314+
fn current_user_home_bytes() -> Option<Vec<u8>> {
315+
#[cfg(unix)]
316+
{
317+
for var in ["LOGNAME", "USER"] {
318+
if let Some(user) = std::env::var_os(var) {
319+
if let Some(home) = user_homedir_bytes(user.as_bytes()) {
320+
return Some(home);
321+
}
322+
}
323+
}
324+
let passwd = unsafe { libc::getpwuid(libc::getuid()) };
325+
if passwd.is_null() {
326+
return None;
327+
}
328+
let pw_dir = unsafe { (*passwd).pw_dir };
329+
if pw_dir.is_null() {
330+
return None;
331+
}
332+
let dir = unsafe { CStr::from_ptr(pw_dir) }.to_bytes();
333+
if dir.is_empty() {
334+
return None;
335+
}
336+
Some(dir.to_vec())
337+
}
338+
339+
#[cfg(not(unix))]
340+
{
341+
let home = std::env::var_os("APPDATA")
342+
.or_else(|| std::env::var_os("USERPROFILE"))
343+
.map(|s| s.to_string_lossy().into_owned())
344+
.unwrap_or_else(|| "C:/".to_string());
345+
Some(home.into_bytes())
346+
}
347+
}
348+
308349
/// Byte-native twin of `join_file_name`.
309350
fn join_file_name_bytes(base: &[u8], name: &[u8]) -> Vec<u8> {
310351
let mut out = Vec::with_capacity(base.len() + 1 + name.len());
@@ -1867,10 +1908,17 @@ pub(crate) fn expand_file_name_lisp(
18671908
/// HOME for `expand-file-name`, as Emacs-internal-encoding bytes to feed the
18681909
/// byte-native expansion core without a storage-String detour.
18691910
fn home_directory_for_expand_file_name(eval: &mut Context) -> Option<Vec<u8>> {
1870-
match super::process::builtin_getenv_internal(eval, vec![Value::string("HOME")]) {
1871-
Ok(value) => value.as_lisp_string().map(|ls| ls.as_bytes().to_vec()),
1872-
Err(_) => None,
1911+
// GNU `get_homedir`: the HOME environment variable, else the current user's
1912+
// home directory (passwd entry on Unix, OS-derived on Windows). Without the
1913+
// fallback, a session with HOME unset -- e.g. a minimal Windows build env,
1914+
// where GNU relies on `init_environment` having set HOME -- leaves `~`
1915+
// unexpanded, so `directory-files "~"` fails at startup.
1916+
if let Ok(value) = super::process::builtin_getenv_internal(eval, vec![Value::string("HOME")]) {
1917+
if let Some(ls) = value.as_lisp_string() {
1918+
return Some(ls.as_bytes().to_vec());
1919+
}
18731920
}
1921+
current_user_home_bytes()
18741922
}
18751923

18761924
#[cfg(windows)]

0 commit comments

Comments
 (0)