use clawft_types::placement::Provenance;
use super::host::ProbeHost;
use super::probe::{Collected, bytes_attr, cap, normalize_arch, note_external_withheld, parse_df, public_mount};
const BINFMT: &str = "/proc/sys/fs/binfmt_misc";
const EXTERNAL_ROOTS: &[&str] = &["/media/", "/mnt/", "/run/media/"];
pub fn binfmt_arch(name: &str) -> Option<String> {
if name.starts_with("rosetta") {
return Some("x86_64".into());
}
let arch = name.strip_prefix("qemu-")?;
Some(match arch {
"arm" => "armv7".into(),
other => normalize_arch(other),
})
.filter(|a: &String| !a.is_empty())
}
pub fn emulated_from_listing(lines: &str, native: &str) -> Vec<String> {
let mut out: Vec<String> = lines
.lines()
.filter_map(|l| {
let mut parts = l.split_whitespace();
let name = parts.next()?;
match parts.next() {
Some("enabled") | None => binfmt_arch(name),
_ => None,
}
})
.filter(|a| a != native)
.collect();
out.sort();
out.dedup();
out
}
pub fn host_binfmt_emulated(host: &dyn ProbeHost, native: &str) -> Vec<String> {
let listing: String = host
.list_dir(BINFMT)
.unwrap_or_default()
.into_iter()
.filter(|n| n != "register" && n != "status")
.map(|n| {
let state = host
.read_file(&format!("{BINFMT}/{n}"))
.and_then(|s| s.lines().next().map(str::to_string))
.unwrap_or_default();
format!("{n} {state}\n")
})
.collect();
emulated_from_listing(&listing, native)
}
fn kv<'a>(text: &'a str, key: &str, sep: char) -> Option<&'a str> {
text.lines()
.filter_map(|l| l.split_once(sep))
.find(|(k, _)| k.trim() == key)
.map(|(_, v)| v.trim().trim_matches('"'))
}
pub fn live_free(host: &dyn ProbeHost) -> Option<u64> {
host.read_file("/proc/meminfo")
.as_deref()
.and_then(|t| meminfo_bytes(t, "MemAvailable"))
}
fn meminfo_bytes(text: &str, key: &str) -> Option<u64> {
kv(text, key, ':')?
.split_whitespace()
.next()?
.parse::<u64>()
.ok()
.map(|kb| kb.saturating_mul(1024))
}
pub fn probe(host: &dyn ProbeHost, arch: &str, c: &mut Collected) {
let cpuinfo = host.read_file("/proc/cpuinfo").unwrap_or_default();
let board = host
.read_file("/proc/device-tree/model")
.map(|s| s.trim_end_matches('\0').trim().to_string())
.filter(|s| !s.is_empty());
if let Some(mut cp) = cap(&format!("cpu.arch.{arch}"), Provenance::Probed) {
let cores = cpuinfo
.lines()
.filter(|l| l.starts_with("processor"))
.count();
if cores > 0 {
cp = cp.with_attr("cores", cores as i64);
}
let model = board
.clone()
.or_else(|| kv(&cpuinfo, "model name", ':').map(str::to_string));
if let Some(m) = model {
cp = cp.with_attr("model", m.as_str());
}
c.push(cp);
}
if let Some(mut os) = cap("os.linux", Provenance::Probed) {
if let Some(d) = host
.read_file("/etc/os-release")
.as_deref()
.and_then(|t| kv(t, "PRETTY_NAME", '=').map(str::to_string))
{
os = os.with_attr("distro", d.as_str());
}
if let Some(k) = host.run("uname", &["-r"]) {
os = os.with_attr("kernel", k.trim());
}
c.push(os);
}
if let Some(mi) = host.read_file("/proc/meminfo")
&& let (Some(total), Some(m)) = (
meminfo_bytes(&mi, "MemTotal"),
cap("mem.system", Provenance::Probed),
)
{
let mut m = m.with_attr("total", bytes_attr(total));
if let Some(avail) = meminfo_bytes(&mi, "MemAvailable") {
m = m.with_attr("free", bytes_attr(avail));
}
c.push(m);
}
storage(host, c);
if let Some(b) = &board {
let class = if b.contains("Raspberry Pi 5") {
"pi5"
} else {
"other"
};
if let Some(nc) = cap(&format!("node.class.{class}"), Provenance::Probed) {
c.push(nc.with_attr("board", b.as_str()));
}
}
}
fn storage(host: &dyn ProbeHost, c: &mut Collected) {
if let Some((free, mount)) = host.run("df", &["-kP", "/"]).as_deref().and_then(parse_df)
&& let Some(s) = cap("store.tier.internal", Provenance::Probed)
{
let mut s = s.with_attr("free", bytes_attr(free));
if let Some(m) = public_mount(&mount) {
s = s.with_attr("mount", m);
}
c.push(s);
}
let mounts = host.read_file("/proc/mounts").unwrap_or_default();
let mut external = 0;
for line in mounts.lines() {
let Some(mp) = line.split_whitespace().nth(1) else {
continue;
};
let mp = mp.replace("\\040", " ");
if !EXTERNAL_ROOTS.iter().any(|r| mp.starts_with(r)) {
continue;
}
let free = host.run("df", &["-kP", &mp]).as_deref().and_then(parse_df);
if let Some(mut s) = cap("store.tier.external", Provenance::Probed) {
s = s.with_attr("mounted", true);
if let Some((f, _)) = free {
s = s.with_attr("free", bytes_attr(f));
}
c.push(s);
external += 1;
}
}
note_external_withheld(c, external);
}