nix: get user info without direct libc calls

The `get_home` function has no users, so delete it instead of rewriting
it.

Closes #12380
This commit is contained in:
Daniel Rainer
2026-01-25 22:25:24 +01:00
committed by Johannes Altmanninger
parent 68e408b930
commit 0cd8d64607
2 changed files with 31 additions and 95 deletions

View File

@@ -6,7 +6,7 @@
use crate::abbrs::{Abbreviation, Position, abbrs_get_set};
use crate::builtins::shared::{BuiltinResult, SUCCESS};
use crate::common::{
UnescapeStringStyle, cstr2wcstring, osstr2wcstring, str2wcstring, unescape_string, wcs2zstring,
UnescapeStringStyle, cstr2wcstring, osstr2wcstring, str2wcstring, unescape_string,
};
use crate::env::config_paths::ConfigPaths;
use crate::env::{EnvMode, EnvSetMode, EnvVar, Statuses};
@@ -29,10 +29,12 @@
use crate::wutil::{fish_wcstol, wgetcwd};
use fish_wcstringutil::join_strings;
use libc::c_int;
use nix::unistd::{Uid, gethostname};
use nix::{
NixPath,
unistd::{Uid, User, gethostname},
};
use std::collections::HashMap;
use std::ffi::CStr;
use std::mem::MaybeUninit;
use std::path::PathBuf;
use std::sync::{Arc, LazyLock, OnceLock};
@@ -458,69 +460,28 @@ fn get_pwd_slash(&self) -> WString {
const FISH_USER_DATA_DIR: &wstr = L!("__fish_user_data_dir");
const FISH_CACHE_DIR: &wstr = L!("__fish_cache_dir");
/// Get values for $HOME via getpwuid,
/// without trusting $USER or $HOME.
pub fn get_home() -> Option<String> {
let uid = Uid::effective();
let mut userinfo: MaybeUninit<libc::passwd> = MaybeUninit::uninit();
let mut result: *mut libc::passwd = std::ptr::null_mut();
let mut buf = [0 as libc::c_char; 8192];
// We need to get the data via the uid and don't trust $USER.
let retval = unsafe {
libc::getpwuid_r(
uid.as_raw(),
userinfo.as_mut_ptr(),
buf.as_mut_ptr(),
buf.len(),
&mut result,
)
};
if retval != 0 || result.is_null() {
return None;
}
let userinfo = unsafe { userinfo.assume_init() };
if !userinfo.pw_dir.is_null() {
let home = unsafe { CStr::from_ptr(userinfo.pw_dir) };
let home = home.to_str().ok().map(|x| x.to_owned());
home
} else {
None
}
}
/// Set up the USER and HOME variable.
fn setup_user(global_exported_mode: EnvSetMode, vars: &EnvStack) {
let uid = Uid::effective();
let user_var = vars.get_unless_empty(L!("USER"));
let mut userinfo: MaybeUninit<libc::passwd> = MaybeUninit::uninit();
let mut result: *mut libc::passwd = std::ptr::null_mut();
let mut buf = [0 as libc::c_char; 8192];
// If we have a $USER, we try to get the passwd entry for the name.
// If that has the same UID that we use, we assume the data is correct.
if let Some(user_var) = user_var {
let unam_narrow = wcs2zstring(&user_var.as_string());
let retval = unsafe {
libc::getpwnam_r(
unam_narrow.as_ptr(),
userinfo.as_mut_ptr(),
buf.as_mut_ptr(),
buf.len(),
&mut result,
)
};
if retval == 0 && !result.is_null() {
let userinfo = unsafe { userinfo.assume_init() };
if unsafe { *result }.pw_uid == uid.as_raw() {
// POSIX.1-2017 requires that user names are a subset of ASCII, so converting to String here
// is ok.
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_437
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282
if let Ok(Some(userinfo)) = User::from_name(&user_var.as_string().to_string()) {
if userinfo.uid == uid {
// The uid matches but we still might need to set $HOME.
if vars.get_unless_empty(L!("HOME")).is_none() {
if !userinfo.pw_dir.is_null() {
let s = unsafe { CStr::from_ptr(userinfo.pw_dir) };
vars.set_one(L!("HOME"), global_exported_mode, cstr2wcstring(s));
if !userinfo.dir.is_empty() {
vars.set_one(
L!("HOME"),
global_exported_mode,
osstr2wcstring(userinfo.dir),
);
} else {
vars.set_empty(L!("HOME"), global_exported_mode);
}
@@ -530,28 +491,18 @@ fn setup_user(global_exported_mode: EnvSetMode, vars: &EnvStack) {
}
}
// Either we didn't have a $USER or it had a different uid.
// We need to get the data *again* via the uid.
let retval = unsafe {
libc::getpwuid_r(
uid.as_raw(),
userinfo.as_mut_ptr(),
buf.as_mut_ptr(),
buf.len(),
&mut result,
)
};
if retval == 0 && !result.is_null() {
let userinfo = unsafe { userinfo.assume_init() };
let s = unsafe { CStr::from_ptr(userinfo.pw_name) };
let uname = cstr2wcstring(s);
if let Ok(Some(userinfo)) = User::from_uid(uid) {
let uname = str2wcstring(userinfo.name);
vars.set_one(L!("USER"), global_exported_mode, uname);
// Only change $HOME if it's empty, so we allow e.g. `HOME=(mktemp -d)`.
// This is okay with common `su` and `sudo` because they set $HOME.
if vars.get_unless_empty(L!("HOME")).is_none() {
if !userinfo.pw_dir.is_null() {
let s = unsafe { CStr::from_ptr(userinfo.pw_dir) };
vars.set_one(L!("HOME"), global_exported_mode, cstr2wcstring(s));
if !userinfo.dir.is_empty() {
vars.set_one(
L!("HOME"),
global_exported_mode,
osstr2wcstring(userinfo.dir),
);
} else {
// We cannot get $HOME. This triggers warnings for history and config.fish already,
// so it isn't necessary to warn here as well.

View File

@@ -8,9 +8,8 @@
STATUS_INVALID_ARGS, STATUS_NOT_EXECUTABLE, STATUS_READ_TOO_MUCH, STATUS_UNMATCHED_WILDCARD,
};
use crate::common::{
EscapeFlags, EscapeStringStyle, UnescapeFlags, UnescapeStringStyle, charptr2wcstring, escape,
escape_string, escape_string_for_double_quotes, unescape_string, valid_var_name_char,
wcs2zstring,
EscapeFlags, EscapeStringStyle, UnescapeFlags, UnescapeStringStyle, escape, escape_string,
escape_string_for_double_quotes, osstr2wcstring, unescape_string, valid_var_name_char,
};
use crate::complete::{CompleteFlags, Completion, CompletionList, CompletionReceiver};
use crate::env::{EnvVar, Environment};
@@ -30,7 +29,7 @@
use fish_util::wcsfilecmp_glob;
use fish_wcstringutil::{join_strings, trim};
use fish_widestring::char_offset;
use std::mem::MaybeUninit;
use nix::unistd::User;
bitflags! {
/// Set of flags controlling expansions.
@@ -1154,23 +1153,9 @@ fn expand_home_directory(input: &mut WString, vars: &dyn Environment) {
}
}
} else {
// Some other user's home directory.
let name_cstr = wcs2zstring(username);
let mut userinfo = MaybeUninit::uninit();
let mut result: *mut libc::passwd = std::ptr::null_mut();
let mut buf = [0 as libc::c_char; 8192];
let retval = unsafe {
libc::getpwnam_r(
name_cstr.as_ptr(),
userinfo.as_mut_ptr(),
&mut buf[0],
size_of_val(&buf),
&mut result,
)
};
if retval == 0 && !result.is_null() {
let userinfo = unsafe { userinfo.assume_init() };
home = Some(charptr2wcstring(userinfo.pw_dir));
// POSIX-conforming usernames are ASCII-only
if let Ok(Some(userinfo)) = User::from_name(&username.to_string()) {
home = Some(osstr2wcstring(userinfo.dir));
}
}