From deef6c102d481871036b133161941d6b00a4b08c Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Wed, 21 Feb 2024 11:49:28 -0500 Subject: [PATCH] platform-host: check /bin/sh, then /bin/dash and then /bin/ls (#1818) Previously, we were only checking /bin/sh. While that works in most cases, it seems like there are still scenarios where /bin/sh isn't an executable itself, and is instead just a shell script that calls /bin/dash. (See #1810 for example.) In this PR, we make the `ld` detection a bit more robust by trying multiple paths. As with previous changes, we emit copious logs to help debug this in the future. It's not totally clear how to test this. I'm not sure how to reproduce the environment mentions in #1810 specifically since it seems like an internal variant of WSL Ubuntu. Fixes #1810 --- crates/platform-host/src/linux.rs | 48 +++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/crates/platform-host/src/linux.rs b/crates/platform-host/src/linux.rs index 1ea27e91d..d99d0a814 100644 --- a/crates/platform-host/src/linux.rs +++ b/crates/platform-host/src/linux.rs @@ -186,18 +186,50 @@ fn musl_ld_output_to_version(kind: &str, output: &[u8]) -> Result Result { - let buffer = fs::read("/bin/sh")?; - let error_str = "Couldn't parse /bin/sh for detecting the ld version"; - let elf = Elf::parse(&buffer) - .map_err(|err| PlatformError::OsVersionDetectionError(format!("{error_str}: {err}")))?; + // At first, we just looked for /bin/ls. But on some Linux distros, /bin/ls + // is a shell script that just calls /usr/bin/ls. So we switched to looking + // at /bin/sh. But apparently in some environments, /bin/sh is itself just + // a shell script that calls /bin/dash. So... We just try a few different + // paths. In most cases, /bin/sh should work. + // + // See: https://github.com/astral-sh/uv/pull/1493 + // See: https://github.com/astral-sh/uv/issues/1810 + let attempts = ["/bin/sh", "/bin/dash", "/bin/ls"]; + for path in attempts { + match find_ld_path_at(path) { + Ok(ld_path) => return Ok(ld_path), + Err(err) => { + tracing::trace!("attempt to find `ld` path at {path} failed: {err}"); + } + } + } + Err(PlatformError::OsVersionDetectionError(format!( + "Couldn't parse ELF interpreter path out of any of the following paths: {joined}", + joined = attempts.join(", "), + ))) +} + +/// Attempt to find the path to the `ld` executable by +/// ELF parsing the given path. If this fails for any +/// reason, then an error is returned. +fn find_ld_path_at(path: impl AsRef) -> Result { + let path = path.as_ref(); + let buffer = fs::read(path)?; + let elf = Elf::parse(&buffer).map_err(|err| { + PlatformError::OsVersionDetectionError(format!( + "Couldn't parse {path} as an ELF file: {err}", + path = path.display() + )) + })?; if let Some(elf_interpreter) = elf.interpreter { Ok(PathBuf::from(elf_interpreter)) } else { - Err(PlatformError::OsVersionDetectionError( - error_str.to_string(), - )) + Err(PlatformError::OsVersionDetectionError(format!( + "Couldn't find ELF interpreter path from {path}", + path = path.display() + ))) } }