From 70771c27a586360996bd7e7c71667ee53cb0f107 Mon Sep 17 00:00:00 2001 From: Etienne Cordonnier Date: Thu, 5 Mar 2026 00:00:42 +0100 Subject: [PATCH] Add argv[0] fallback for current_exe() on Linux when /proc is unavailable When /proc/self/exe is not accessible (e.g., in containers with masked /proc, chroot environments, or systemd services with ProtectProc=invisible), current_exe() will now fall back to parsing argv[0] and searching PATH. This brings the Linux implementation in line with existing Rust stdlib patterns on Fuchsia, Solaris/illumos, and AIX, which also use argv[0] when direct kernel APIs are unavailable. Fallback strategy: 1. Try /proc/self/exe (fast path, existing behavior) 2. On NotFound error, retrieve argv[0] from env::args() 3. If argv[0] is absolute, use it directly 4. If argv[0] contains '/', resolve it as relative path against getcwd() 5. Otherwise, search PATH for the executable (checking execute permissions) 6. Return NotFound error if executable not found in PATH This handles all common invocation patterns: - Absolute path: `/usr/bin/program` - Relative path: `./program` or `../bin/program` - PATH lookup: `program` (searches directories in PATH) This maintains backward compatibility: behavior is unchanged when /proc is accessible. Signed-off-by: Etienne Cordonnier --- library/std/src/sys/paths/unix.rs | 60 ++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/library/std/src/sys/paths/unix.rs b/library/std/src/sys/paths/unix.rs index 544d495340db7..f2d514d0a40c7 100644 --- a/library/std/src/sys/paths/unix.rs +++ b/library/std/src/sys/paths/unix.rs @@ -261,10 +261,62 @@ pub fn current_exe() -> io::Result { ))] pub fn current_exe() -> io::Result { match crate::fs::read_link("/proc/self/exe") { - Err(ref e) if e.kind() == io::ErrorKind::NotFound => Err(io::const_error!( - io::ErrorKind::Uncategorized, - "no /proc/self/exe available. Is /proc mounted?", - )), + Ok(path) => Ok(path), + Err(ref e) if e.kind() == io::ErrorKind::NotFound => { + // /proc is not available (e.g., masked in containers, chroot, or unmounted). + // Fall back to parsing argv[0] and searching PATH. + #[cfg(test)] + use realstd::env; + + #[cfg(not(test))] + use crate::env; + + let exe_path = env::args_os().next().ok_or_else(|| { + io::const_error!( + io::ErrorKind::Uncategorized, + "no /proc/self/exe available and no argv[0] provided", + ) + })?; + + // In test mode, convert from realstd::OsString to local PathBuf via bytes. + let path = PathBuf::from(OsStr::from_bytes(exe_path.as_encoded_bytes())); + + // If argv[0] is an absolute path, canonicalize it. + if path.is_absolute() { + return path.canonicalize(); + } + + // If argv[0] contains a path separator, it's a relative path. + // Join it with the current working directory and canonicalize. + if path.as_os_str().as_bytes().contains(&b'/') { + return getcwd().map(|cwd| cwd.join(path))?.canonicalize(); + } + + // argv[0] is just a command name. Search PATH to find the executable. + if let Some(path_var) = env::var_os("PATH") { + // In test mode, convert from realstd::OsString to local OsStr via bytes. + let path_bytes = path_var.as_encoded_bytes(); + let path_osstr = OsStr::from_bytes(path_bytes); + for search_path in split_paths(path_osstr) { + let candidate = search_path.join(&path); + if candidate.is_file() { + if let Ok(metadata) = crate::fs::metadata(&candidate) { + if metadata.permissions().mode() & 0o111 != 0 { + // Canonicalize to resolve symlinks, ensure absolute path, + // and clean up path components (matching AIX behavior). + return candidate.canonicalize(); + } + } + } + } + } + + // Could not find the executable in PATH. + Err(io::const_error!( + io::ErrorKind::NotFound, + "no /proc/self/exe available and could not find executable in PATH", + )) + } other => other, } }