Skip to content

Commit 23dfde3

Browse files
committed
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 <ecordonnier@snap.com>
1 parent 98e7077 commit 23dfde3

File tree

1 file changed

+56
-4
lines changed
  • library/std/src/sys/pal/unix

1 file changed

+56
-4
lines changed

library/std/src/sys/pal/unix/os.rs

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,10 +264,62 @@ pub fn current_exe() -> io::Result<PathBuf> {
264264
))]
265265
pub fn current_exe() -> io::Result<PathBuf> {
266266
match crate::fs::read_link("/proc/self/exe") {
267-
Err(ref e) if e.kind() == io::ErrorKind::NotFound => Err(io::const_error!(
268-
io::ErrorKind::Uncategorized,
269-
"no /proc/self/exe available. Is /proc mounted?",
270-
)),
267+
Ok(path) => Ok(path),
268+
Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
269+
// /proc is not available (e.g., masked in containers, chroot, or unmounted).
270+
// Fall back to parsing argv[0] and searching PATH.
271+
#[cfg(test)]
272+
use realstd::env;
273+
274+
#[cfg(not(test))]
275+
use crate::env;
276+
277+
let exe_path = env::args_os().next().ok_or_else(|| {
278+
io::const_error!(
279+
io::ErrorKind::Uncategorized,
280+
"no /proc/self/exe available and no argv[0] provided",
281+
)
282+
})?;
283+
284+
// In test mode, convert from realstd::OsString to local PathBuf via bytes.
285+
let path = PathBuf::from(OsStr::from_bytes(exe_path.as_encoded_bytes()));
286+
287+
// If argv[0] is an absolute path, canonicalize it.
288+
if path.is_absolute() {
289+
return path.canonicalize();
290+
}
291+
292+
// If argv[0] contains a path separator, it's a relative path.
293+
// Join it with the current working directory and canonicalize.
294+
if path.as_os_str().as_bytes().contains(&b'/') {
295+
return getcwd().map(|cwd| cwd.join(path))?.canonicalize();
296+
}
297+
298+
// argv[0] is just a command name. Search PATH to find the executable.
299+
if let Some(path_var) = env::var_os("PATH") {
300+
// In test mode, convert from realstd::OsString to local OsStr via bytes.
301+
let path_bytes = path_var.as_encoded_bytes();
302+
let path_osstr = OsStr::from_bytes(path_bytes);
303+
for search_path in split_paths(path_osstr) {
304+
let candidate = search_path.join(&path);
305+
if candidate.is_file() {
306+
if let Ok(metadata) = crate::fs::metadata(&candidate) {
307+
if metadata.permissions().mode() & 0o111 != 0 {
308+
// Canonicalize to resolve symlinks, ensure absolute path,
309+
// and clean up path components (matching AIX behavior).
310+
return candidate.canonicalize();
311+
}
312+
}
313+
}
314+
}
315+
}
316+
317+
// Could not find the executable in PATH.
318+
Err(io::const_error!(
319+
io::ErrorKind::NotFound,
320+
"no /proc/self/exe available and could not find executable in PATH",
321+
))
322+
}
271323
other => other,
272324
}
273325
}

0 commit comments

Comments
 (0)