From f6584225c206ef1214cd136c46b6e664e5cc8411 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Sat, 25 Oct 2025 15:39:22 +0200 Subject: [PATCH] Pin down "status fish-path" at startup On some platforms, Rust's std::env::current_exe() may use relative paths from at least argv[0]. That function also canonicalizes the path, so we could only detect this case by duplicating logic from std::env::current_exe() (not sure if that's worth it). Relative path canonicalization seems potentially surprising, especially after fish has used "cd". Let's try to reduce surprise by saving the value we compute at startup (before any "cd"), and use only that. The remaining problem is that creat("/some/directory/with/FISH"); chdir("/some/directory/with/"); execve("/bin/fish", "FISH", "-c", "status fish-path") surprisingly prints "/some/directory/with/FISH" on some platforms. But that requires the user actively trying to break things (passing a relative argv[0] that doesn't match the cwd). --- src/env/config_paths.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/env/config_paths.rs b/src/env/config_paths.rs index 5062648b3..519044352 100644 --- a/src/env/config_paths.rs +++ b/src/env/config_paths.rs @@ -1,9 +1,10 @@ use crate::common::{BUILD_DIR, PROGRAM_NAME}; use crate::{FLOG, FLOGF}; use fish_build_helper::workspace_root; +use once_cell::sync::OnceCell; use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; /// A struct of configuration directories, determined in main() that fish will optionally pass to /// env_init. @@ -22,7 +23,7 @@ pub struct ConfigPaths { impl ConfigPaths { pub fn new() -> Self { - let exec_path = get_fish_path(); + let exec_path = FISH_PATH.get_or_init(compute_fish_path); FLOG!(config, format!("executable path: {}", exec_path.display())); let paths = Self::from_exec_path(exec_path); FLOGF!( @@ -57,7 +58,7 @@ fn static_paths() -> Self { } #[cfg(feature = "embed-data")] - fn from_exec_path(exec_path: PathBuf) -> Self { + fn from_exec_path(exec_path: &Path) -> Self { FLOG!(config, "embed-data feature is active, ignoring data paths"); Self { sysconf: if exec_path @@ -73,9 +74,7 @@ fn from_exec_path(exec_path: PathBuf) -> Self { } #[cfg(not(feature = "embed-data"))] - fn from_exec_path(unresolved_exec_path: PathBuf) -> Self { - use std::path::Path; - + fn from_exec_path(unresolved_exec_path: &Path) -> Self { let invalid_exec_path = |exec_path: &Path| { FLOG!( config, @@ -87,7 +86,7 @@ fn from_exec_path(unresolved_exec_path: PathBuf) -> Self { Self::static_paths() }; let Ok(exec_path) = unresolved_exec_path.canonicalize() else { - return invalid_exec_path(&unresolved_exec_path); + return invalid_exec_path(unresolved_exec_path); }; let Some(exec_path_parent) = exec_path.parent() else { return invalid_exec_path(&exec_path); @@ -144,8 +143,14 @@ fn from_exec_path(unresolved_exec_path: PathBuf) -> Self { } } +static FISH_PATH: OnceCell = OnceCell::new(); + /// Get the absolute path to the fish executable itself -pub fn get_fish_path() -> PathBuf { +pub fn get_fish_path() -> &'static PathBuf { + FISH_PATH.get().unwrap() +} + +fn compute_fish_path() -> PathBuf { let Ok(path) = std::env::current_exe() else { assert!(PROGRAM_NAME.get().unwrap() == "fish"); return PathBuf::from("fish");