use gregg_protocol::SystemIdentity;
use crate::collector::error::CollectError;
use crate::collector::linux::source::ProcSource;
const MAX_IDENTITY_LEN: usize = 128;
pub fn collect_identity(
source: &ProcSource,
display_name: Option<&str>,
) -> Result<SystemIdentity, CollectError> {
let hostname = source.hostname()?;
let kernel = source.kernel_identity()?;
let architecture = source.architecture();
let _ = source.logical_core_count().unwrap_or(1).max(1);
let os_release = source.read_os_release()?;
let (os_name, os_version) = match os_release {
Some(content) => parse_os_release(&content),
None => ("linux".to_string(), "unknown".to_string()),
};
let name = match display_name {
Some(value) => clip_identifier(value),
None => hostname.clone(),
};
Ok(SystemIdentity {
name,
hostname: clip_identifier(&hostname),
os_name: clip_identifier(&os_name),
os_version: clip_identifier(&os_version),
kernel_name: clip_identifier(&kernel.sysname),
kernel_release: clip_identifier(&kernel.release),
architecture: clip_identifier(&architecture),
})
}
fn clip_identifier(input: &str) -> String {
if input.len() <= MAX_IDENTITY_LEN {
input.to_string()
} else {
let mut end = MAX_IDENTITY_LEN;
while end > 0 && !input.is_char_boundary(end) {
end -= 1;
}
input[..end].to_string()
}
}
fn parse_os_release(content: &str) -> (String, String) {
let mut pretty_name: Option<String> = None;
let mut name: Option<String> = None;
let mut version: Option<String> = None;
let mut version_id: Option<String> = None;
for line in content.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
let Some((key, value)) = trimmed.split_once('=') else {
continue;
};
let value = unquote(value.trim());
match key.trim() {
"PRETTY_NAME" => pretty_name = Some(value),
"NAME" => name = Some(value),
"VERSION" => version = Some(value),
"VERSION_ID" => version_id = Some(value),
_ => {}
}
}
let chosen = pretty_name
.or(name.clone())
.unwrap_or_else(|| "linux".to_string());
let version = version
.or(version_id)
.or(name)
.unwrap_or_else(|| "unknown".to_string());
(chosen, version)
}
fn unquote(value: &str) -> String {
let trimmed = value.trim();
if trimmed.len() >= 2
&& ((trimmed.starts_with('"') && trimmed.ends_with('"'))
|| (trimmed.starts_with('\'') && trimmed.ends_with('\'')))
{
let inner = &trimmed[1..trimmed.len() - 1];
inner
.replace("\\\"", "\"")
.replace("\\'", "'")
.replace("\\\\", "\\")
} else {
trimmed.to_string()
}
}
#[allow(dead_code)]
pub fn synthetic_identity(
name: &str,
hostname: &str,
os_name: &str,
os_version: &str,
kernel_name: &str,
kernel_release: &str,
architecture: &str,
) -> SystemIdentity {
SystemIdentity {
name: name.to_string(),
hostname: hostname.to_string(),
os_name: os_name.to_string(),
os_version: os_version.to_string(),
kernel_name: kernel_name.to_string(),
kernel_release: kernel_release.to_string(),
architecture: architecture.to_string(),
}
}
#[allow(dead_code)]
pub fn os_release_sample() -> &'static str {
r#"NAME="Ubuntu"
VERSION="24.04 LTS (Noble Numbat)"
ID=ubuntu
VERSION_ID="24.04"
PRETTY_NAME="Ubuntu 24.04 LTS"
"#
}