use std::process::Command;
use crate::error::Error;
use crate::source::{Probe, Source, SourceKind};
use crate::sources::util::normalize;
#[derive(Debug, Default, Clone)]
pub struct SysctlKernHostId {
_priv: (),
}
impl SysctlKernHostId {
#[must_use]
pub fn new() -> Self {
Self { _priv: () }
}
}
impl Source for SysctlKernHostId {
fn kind(&self) -> SourceKind {
SourceKind::BsdKernHostId
}
fn probe(&self) -> Result<Option<Probe>, Error> {
let output = Command::new("/sbin/sysctl")
.args(["-n", "kern.hostid"])
.output()
.map_err(|e| Error::Platform {
source_kind: SourceKind::BsdKernHostId,
reason: format!("sysctl: {e}"),
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
log::debug!(
"host-identity: sysctl kern.hostid exited with {}: {}",
output.status,
stderr.trim()
);
return Ok(None);
}
let Ok(value) = std::str::from_utf8(&output.stdout) else {
return Ok(None);
};
Ok(normalize(value)
.filter(|v| !is_zero_hostid(v))
.map(|v| Probe::new(SourceKind::BsdKernHostId, v)))
}
}
fn is_zero_hostid(v: &str) -> bool {
let (digits, radix) = v
.strip_prefix("0x")
.or_else(|| v.strip_prefix("0X"))
.map_or((v, 10), |d| (d, 16));
u64::from_str_radix(digits, radix).is_ok_and(|n| n == 0)
}
#[cfg(test)]
mod tests {
use super::is_zero_hostid;
#[test]
fn decimal_zero_is_zero() {
assert!(is_zero_hostid("0"));
}
#[test]
fn lowercase_hex_zero_is_zero() {
assert!(is_zero_hostid("0x0"));
}
#[test]
fn uppercase_hex_zero_padded_is_zero() {
assert!(is_zero_hostid("0X00000000"));
}
#[test]
fn nonzero_hex_is_not_zero() {
assert!(!is_zero_hostid("0x1"));
}
#[test]
fn non_numeric_value_is_not_zero() {
assert!(!is_zero_hostid("deadbeef-not-a-number"));
}
}