Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: alternative libc detection #209

Merged
merged 2 commits into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/rattler_virtual_packages/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ serde = { version = "1.0.130", features = ["derive"] }

[target.'cfg(target_os="macos")'.dependencies]
plist = "1"

[target.'cfg(unix)'.dependencies]
regex = "1.8.2"
103 changes: 33 additions & 70 deletions crates/rattler_virtual_packages/src/libc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

use once_cell::sync::OnceCell;
use rattler_conda_types::{ParseVersionError, Version};
use std::ffi::{FromVecWithNulError, IntoStringError};

/// Returns the LibC version and family of the current platform.
///
Expand All @@ -15,19 +14,6 @@ pub fn libc_family_and_version() -> Result<Option<(String, Version)>, DetectLibC
.cloned()
}

#[cfg(unix)]
mod ffi {
use std::os::raw::{c_char, c_int};

pub const CS_GNU_LIBC_VERSION: c_int = 2;
pub const CS_GNU_LIBPTHREAD_VERSION: c_int = 3;

extern "C" {
/// Get configuration dependent string variables
pub fn confstr(name: c_int, buf: *mut c_char, length: usize) -> usize;
}
}

/// An error that could occur when trying to detect to libc version
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
#[allow(missing_docs)]
Expand All @@ -36,75 +22,52 @@ pub enum DetectLibCError {
ParseLibCVersion(#[from] ParseVersionError),
}

/// Returns the detected libc version used by the system.
/// Tries to detected the libc family and version that is available on the system.
///
/// Note that this may differ from the libc version against which this binary was build. For
/// instance when compiling against musl libc the resulting binary can still run on a glibc based
/// system. For environments we are interested in the libc family that is available on the *system*.
///
/// Currently this code is only able to detect glibc properly. We can add more detection methods in
/// the future.
#[cfg(unix)]
fn try_detect_libc_version() -> Result<Option<(String, Version)>, DetectLibCError> {
use std::str::FromStr;

// Use confstr to determine the LibC family and version
let version = match [ffi::CS_GNU_LIBC_VERSION, ffi::CS_GNU_LIBPTHREAD_VERSION]
.into_iter()
.find_map(|name| confstr(name).unwrap_or(None))
{
Some(version) => version,
None => return Ok(None),
};

// Split into family and version
let (family, version) = match version.split_once(' ') {
Some(split) => split,
None => return Ok(None),
// Run `ldd --version` to detect the libc version and family on the system. `ldd` is shipped
// with libc so if an error occured during its execution we can assume no libc is available on
// the system.
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,
};

// Parse the version string
let version = Version::from_str(version)?;
let stdout = String::from_utf8_lossy(&output.stdout);

// The family might be NPTL but thats just the name of the threading library, even though the
// version refers to that of uClibc.
if family == "NPTL" {
let family = String::from("uClibc");
tracing::warn!(
"failed to detect non-glibc family, assuming {} ({})",
&family,
&version
);
Ok(Some((family, version)))
} else {
Ok(Some((family.to_owned(), version)))
// GNU libc writes to stdout
static GNU_LIBC_RE: once_cell::sync::Lazy<regex::Regex> = once_cell::sync::Lazy::new(|| {
regex::Regex::new("(?mi)(?:glibc|gnu libc).*?([0-9]+(:?.[0-9]+)*)$").unwrap()
});
if let Some(version_match) = GNU_LIBC_RE
.captures(&stdout)
.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((String::from("glibc"), version)));
}

Ok(None)
}

#[cfg(not(unix))]
const fn try_detect_libc_version() -> Result<Option<(String, Version)>, DetectLibCError> {
Ok(None)
}

/// A possible error returned by `confstr`.
#[derive(Debug, thiserror::Error)]
enum ConfStrError {
#[error("invalid string returned: {0}")]
FromVecWithNulError(#[from] FromVecWithNulError),

#[error("invalid utf8 string: {0}")]
InvalidUtf8String(#[from] IntoStringError),
}

/// Safe wrapper around `confstr`
#[cfg(unix)]
fn confstr(name: std::os::raw::c_int) -> Result<Option<String>, ConfStrError> {
let len = match unsafe { ffi::confstr(name, std::ptr::null_mut(), 0) } {
0 => return Ok(None),
len => len,
};
let mut bytes = vec![0u8; len];
if unsafe { ffi::confstr(name, bytes.as_mut_ptr() as *mut _, bytes.len()) } == 0 {
return Ok(None);
}
Ok(Some(
std::ffi::CString::from_vec_with_nul(bytes)?.into_string()?,
))
}

#[cfg(test)]
mod test {
#[test]
Expand Down