use std::ptr;
use crate::env::ShellEnv;
pub fn fallback_default_path() -> String {
"/bin:/usr/bin".to_string()
}
pub fn call_confstr() -> Option<String> {
let needed = unsafe { libc::confstr(libc::_CS_PATH, ptr::null_mut(), 0) };
if needed == 0 {
return None;
}
let mut buf = vec![0u8; needed];
let written = unsafe { libc::confstr(libc::_CS_PATH, buf.as_mut_ptr().cast(), needed) };
if written == 0 || written > needed {
return None;
}
buf.truncate(written.saturating_sub(1));
String::from_utf8(buf).ok()
}
pub fn default_path(env: &ShellEnv) -> &str {
env.default_path_cache
.get_or_init(|| call_confstr().unwrap_or_else(fallback_default_path))
.as_str()
}
pub fn ensure_default_path(env: &mut ShellEnv) {
if env.vars.get("PATH").is_some() {
return;
}
let dp = default_path(env).to_string();
let _ = env.vars.set("PATH", dp);
env.vars.export("PATH");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fallback_is_bin_usr_bin() {
assert_eq!(fallback_default_path(), "/bin:/usr/bin");
}
#[test]
fn fallback_does_not_contain_cwd_or_empty() {
let p = fallback_default_path();
assert!(!p.split(':').any(|d| d == "." || d.is_empty()));
}
#[test]
fn call_confstr_returns_something_usable() {
let p = call_confstr().expect("confstr(_CS_PATH) should succeed on POSIX systems");
assert!(!p.is_empty());
assert!(
p.split(':').any(|d| d == "/bin" || d == "/usr/bin"),
"expected /bin or /usr/bin in confstr PATH, got: {p}"
);
}
#[test]
fn call_confstr_has_no_cwd_or_empty_entries() {
let p = call_confstr().expect("confstr should succeed");
assert!(!p.split(':').any(|d| d == "." || d.is_empty()));
}
use crate::env::ShellEnv;
#[test]
fn default_path_is_non_empty() {
let env = ShellEnv::new("yosh", vec![]);
assert!(!default_path(&env).is_empty());
}
#[test]
fn default_path_contains_bin_or_usr_bin() {
let env = ShellEnv::new("yosh", vec![]);
let dp = default_path(&env);
assert!(
dp.split(':').any(|d| d == "/bin" || d == "/usr/bin"),
"expected /bin or /usr/bin in default path, got: {dp}"
);
}
#[test]
fn default_path_finds_sh() {
use crate::exec::command::find_in_path;
let env = ShellEnv::new("yosh", vec![]);
let dp = default_path(&env);
assert!(
find_in_path("sh", dp).is_some(),
"expected to find sh in: {dp}"
);
}
#[test]
fn default_path_is_cached() {
let env = ShellEnv::new("yosh", vec![]);
let a = default_path(&env).as_ptr();
let b = default_path(&env).as_ptr();
assert_eq!(a, b, "default_path should return the same cached string");
}
#[test]
fn ensure_default_path_populates_when_unset() {
let mut env = ShellEnv::new("yosh", vec![]);
let _ = env.vars.unset("PATH");
assert!(env.vars.get("PATH").is_none());
ensure_default_path(&mut env);
let pv = env.vars.get("PATH").expect("PATH should be set now");
assert!(!pv.is_empty());
let v = env.vars.get_var("PATH").expect("variable exists");
assert!(v.exported, "PATH should be exported so children inherit it");
}
#[test]
fn ensure_default_path_preserves_existing() {
let mut env = ShellEnv::new("yosh", vec![]);
let _ = env.vars.set("PATH", "/custom/path");
ensure_default_path(&mut env);
assert_eq!(env.vars.get("PATH"), Some("/custom/path"));
}
}