use super::{Check, Doctor, Finding, Verdict};
use hennery_host::runtime::glibc::{self, Said};
pub use hennery_host::runtime::glibc::{Libc, major_minor};
#[cfg(test)]
pub use hennery_host::runtime::glibc::{parse_getconf, parse_loader};
use hennery_host::runtime::install;
use hennery_host::runtime::manifest::Platform;
pub const MIN_KERNEL: (u32, u32) = (4, 18);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Facts {
pub name: String,
pub platform: Option<Platform>,
pub loader: Option<(&'static str, bool)>,
pub nixos: bool,
pub libc: Libc,
pub kernel: Option<String>,
}
pub fn gather(doctor: &Doctor) -> Facts {
let root = &doctor.cx.root;
let platform = Platform::current();
let nixos = root.join("etc/NIXOS").exists();
let loader = platform
.and_then(install::glibc_loader)
.map(|path| (path, root.join(path.trim_start_matches('/')).exists()));
let libc = match loader {
Some((path, true)) => libc_behind(doctor, path, nixos),
_ => Libc::Unknown,
};
Facts {
name: format!("{}-{}", std::env::consts::OS, std::env::consts::ARCH),
platform,
loader,
nixos,
libc,
kernel: if cfg!(target_os = "linux") {
kernel_release()
} else {
None
},
}
}
pub fn libc_behind(doctor: &Doctor, path: &str, nixos: bool) -> Libc {
let root = &doctor.cx.root;
let run = |program: &std::path::Path, args: &[&str]| {
(doctor.run)(program, args).map(|ran| Said {
ok: ran.ok,
stdout: ran.stdout,
stderr: ran.stderr,
})
};
glibc::libc_behind(
&root.join(path.trim_start_matches('/')),
&root.join("usr/bin/getconf"),
nixos,
&run,
)
}
fn kernel_release() -> Option<String> {
let mut name: libc::utsname = unsafe { std::mem::zeroed() };
if unsafe { libc::uname(&mut name) } != 0 {
return None;
}
let release = unsafe { std::ffi::CStr::from_ptr(name.release.as_ptr()) };
Some(release.to_string_lossy().into_owned())
}
pub fn platform(doctor: &Doctor) -> Finding {
let strict = doctor.dirs.host.is_some() && !doctor.agents_given();
Finding::Checked(judge(&gather(doctor), strict))
}
pub fn judge(facts: &Facts, strict: bool) -> Check {
let mut verdict = Verdict::default();
let bad = |verdict: &mut Verdict, summary: String, fix: &str| {
if strict {
verdict.fail(summary, fix);
} else {
verdict.warn(format!("{summary}: no managed runtime can run here"), fix);
}
};
match facts.platform {
None => bad(
&mut verdict,
format!("hennery's managed runtime does not support {}", facts.name),
"run hosts on macOS on Apple silicon or on Linux (x86-64, arm64) with glibc; this machine can run the collector",
),
Some(Platform::DarwinArm64) => verdict.ok("macOS on Apple silicon"),
Some(platform) => {
match (glibc::refusal(facts.loader, facts.nixos, facts.libc), facts.libc) {
(Some((summary, fix)), _) => bad(&mut verdict, summary, fix),
(None, Libc::Glibc(major, minor)) => {
verdict.ok(format!("{} with glibc {major}.{minor}", platform.key()))
}
(None, _) => verdict.warn(
format!("{}: glibc's version is unknown", platform.key()),
"check that glibc is 2.28 or later (`ldd --version`)",
),
}
match facts.kernel.as_deref().map(|k| (k, major_minor(k))) {
Some((release, Some(version))) if version < MIN_KERNEL => bad(
&mut verdict,
format!("Linux {release} is older than 4.18"),
"upgrade the kernel to 4.18 or later",
),
Some((release, Some(_))) => verdict.ok(format!("Linux {release}")),
_ => verdict.warn(
"the kernel's release is unknown",
"check that `uname -r` says 4.18 or later",
),
}
}
}
verdict.check(2, "platform")
}