use once_cell::sync::OnceCell;
use rattler_conda_types::{ParseVersionError, Version};
pub fn libc_family_and_version() -> Result<Option<(String, Version)>, DetectLibCError> {
static DETECTED_LIBC_VERSION: OnceCell<Option<(String, Version)>> = OnceCell::new();
DETECTED_LIBC_VERSION
.get_or_try_init(try_detect_libc_version)
.cloned()
}
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum DetectLibCError {
#[error("failed to parse libc version returned by the system")]
ParseLibCVersion(#[from] ParseVersionError),
}
#[cfg(target_os = "linux")]
fn try_detect_libc_version_via_symbol() -> Result<Option<Version>, DetectLibCError> {
unsafe {
let lib = match libloading::Library::new("libc.so.6") {
Ok(lib) => lib,
Err(e) => {
tracing::debug!("failed to load libc.so.6: {e}");
return Ok(None);
}
};
let gnu_get_libc_version: libloading::Symbol<
'_,
unsafe extern "C" fn() -> *const std::os::raw::c_char,
> = match lib.get(b"gnu_get_libc_version") {
Ok(sym) => sym,
Err(e) => {
tracing::debug!("failed to load gnu_get_libc_version symbol: {e}");
return Ok(None);
}
};
let version_ptr = gnu_get_libc_version();
if version_ptr.is_null() {
tracing::debug!("gnu_get_libc_version returned null");
return Ok(None);
}
let version_cstr = std::ffi::CStr::from_ptr(version_ptr);
let version_str = match version_cstr.to_str() {
Ok(s) => s,
Err(e) => {
tracing::debug!("failed to convert version string to UTF-8: {e}");
return Ok(None);
}
};
let version = std::str::FromStr::from_str(version_str)?;
tracing::debug!("detected glibc version via symbol: {version}");
Ok(Some(version))
}
}
#[cfg(unix)]
fn try_detect_libc_version() -> Result<Option<(String, Version)>, DetectLibCError> {
#[cfg(target_os = "linux")]
{
if let Some(version) = try_detect_libc_version_via_symbol()? {
return Ok(Some((String::from("glibc"), version)));
}
let output = match std::process::Command::new("ldd").arg("--version").output() {
Err(e) => {
tracing::info!(
"failed to execute `ldd --version`: {e}. Assuming libc is not available."
);
return Ok(None);
}
Ok(output) => output,
};
Ok(
parse_glibc_ldd_version(&String::from_utf8_lossy(&output.stdout))?
.map(|version| (String::from("glibc"), version)),
)
}
#[cfg(not(target_os = "linux"))]
{
Ok(None)
}
}
#[cfg(any(test, unix))]
#[allow(dead_code)] fn parse_glibc_ldd_version(input: &str) -> Result<Option<Version>, DetectLibCError> {
static GNU_LIBC_RE: once_cell::sync::Lazy<regex::Regex> = once_cell::sync::Lazy::new(|| {
regex::Regex::new("(?mi)(?:glibc|gentoo|gnu libc|solus).*?([0-9]+(:?.[0-9]+)*)$").unwrap()
});
if let Some(version_match) = GNU_LIBC_RE
.captures(input)
.and_then(|captures| captures.get(1))
.map(|version_match| version_match.as_str())
{
let version = std::str::FromStr::from_str(version_match)?;
return Ok(Some(version));
}
Ok(None)
}
#[cfg(not(unix))]
const fn try_detect_libc_version() -> Result<Option<(String, Version)>, DetectLibCError> {
Ok(None)
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use super::*;
#[test]
#[cfg(unix)]
pub fn doesnt_crash() {
let version = super::try_detect_libc_version().unwrap();
println!("LibC {version:?}");
}
#[test]
pub fn test_parse_glibc_ldd_version() {
assert_eq!(
parse_glibc_ldd_version("ldd (Ubuntu GLIBC 2.35-0ubuntu3.1) 2.35").unwrap(),
Some(Version::from_str("2.35").unwrap())
);
assert_eq!(
parse_glibc_ldd_version("ldd (Gentoo 2.39-r9 (patchset 9)) 2.39").unwrap(),
Some(Version::from_str("2.39").unwrap())
);
assert_eq!(
parse_glibc_ldd_version("ldd (GNU libc) 2.31").unwrap(),
Some(Version::from_str("2.31").unwrap())
);
assert_eq!(
parse_glibc_ldd_version("ldd (Solus) 2.39").unwrap(),
Some(Version::from_str("2.39").unwrap())
);
}
}