use anyhow::{anyhow, Context, Result};
use smbioslib::{
SMBiosBaseboardInformation, SMBiosData, SMBiosEntryPoint32, SMBiosEntryPoint64,
SMBiosInformation, SMBiosProcessorInformation, SMBiosSystemChassisInformation,
SMBiosSystemInformation, SMBiosVersion,
};
use std::{
fs::read_to_string,
io::{Error as IoError, ErrorKind},
path::Path,
};
use crate::{
collectors::cpuinfo::{CpuFrequency, CpuInfo},
helpers::append_to_pathbuf,
models::devices::{Bios, Board, Chassis, Processor},
};
impl TryFrom<&SMBiosInformation<'_>> for Bios {
type Error = anyhow::Error;
fn try_from(bios_info: &SMBiosInformation) -> Result<Self> {
let release_date = bios_info.release_date().to_string();
let firmware_revision = match (
bios_info.system_bios_major_release(),
bios_info.system_bios_minor_release(),
) {
(Some(major), Some(minor)) => Some(format!("{major}.{minor}")),
_ => None,
};
Ok(Self {
firmware_revision,
release_date: Some(release_date),
revision: Some(bios_info.version().to_string()),
vendor: bios_info.vendor().to_string(),
version: bios_info.version().to_string(),
})
}
}
impl TryFrom<(&SMBiosProcessorInformation<'_>, &Path)> for Processor {
type Error = anyhow::Error;
fn try_from(value: (&SMBiosProcessorInformation, &Path)) -> Result<Self> {
let (processor_info, max_cpu_frequency_filepath) = value;
let cpu_identifier = processor_info
.processor_id()
.ok_or_else(|| anyhow!("processor ID cannot be retrieved"))?;
let cpu_freq = CpuFrequency::from_k_hz_file(max_cpu_frequency_filepath)?.get_m_hz();
Ok(Processor {
identifier: Some(*cpu_identifier),
frequency: cpu_freq,
manufacturer: processor_info.processor_manufacturer().to_string(),
version: processor_info.processor_version().to_string(),
})
}
}
impl TryFrom<(&Path, &Path)> for Processor {
type Error = anyhow::Error;
fn try_from(value: (&Path, &Path)) -> Result<Self> {
let (cpuinfo_filepath, max_cpu_frequency_filepath) = value;
let cpu_info = CpuInfo::from_file(cpuinfo_filepath)?;
let cpu_freq = CpuFrequency::from_k_hz_file(max_cpu_frequency_filepath)?.get_m_hz();
Ok(Processor {
identifier: None,
frequency: cpu_freq,
manufacturer: cpu_info.cpu_type,
version: cpu_info.model,
})
}
}
impl TryFrom<&SMBiosSystemChassisInformation<'_>> for Chassis {
type Error = anyhow::Error;
fn try_from(chassis_info: &SMBiosSystemChassisInformation) -> Result<Self> {
let chassis_type = chassis_info
.chassis_type()
.ok_or("failed to get chassis type")
.unwrap()
.to_string();
let manufacturer = chassis_info.manufacturer().ok().unwrap_or_default();
let sku = chassis_info.sku_number().ok().unwrap_or_default();
let version = chassis_info.version().ok().unwrap_or_default();
Ok(Chassis {
chassis_type,
manufacturer,
sku,
version,
})
}
}
impl TryFrom<&SMBiosBaseboardInformation<'_>> for Board {
type Error = anyhow::Error;
fn try_from(board_info: &SMBiosBaseboardInformation) -> Result<Self> {
let manufacturer = board_info.manufacturer().ok().unwrap_or_default();
let product_name = board_info.product().ok().unwrap_or_default();
let version = board_info.version().ok().unwrap_or_default();
Ok(Board {
manufacturer,
product_name,
version,
})
}
}
impl TryFrom<&Path> for Board {
type Error = anyhow::Error;
fn try_from(device_tree_filepath: &Path) -> Result<Self> {
let try_read = |file| {
read_to_string(file)
.as_ref()
.map(|s| s.trim())
.unwrap_or("Unknown")
.to_string()
};
let manufacturer = String::from("Unknown");
let product_name = try_read(append_to_pathbuf(
device_tree_filepath.to_path_buf(),
"model",
));
let version = try_read(append_to_pathbuf(
device_tree_filepath.to_path_buf(),
"compatible",
));
Ok(Board {
manufacturer,
product_name,
version,
})
}
}
pub struct SystemInfo {
pub product_name: String,
pub manufacturer: String,
}
impl SystemInfo {
pub fn try_from_smbios(system_data: &SMBiosSystemInformation) -> Result<Self> {
Ok(SystemInfo {
product_name: system_data.product_name().ok().unwrap_or_default(),
manufacturer: system_data.manufacturer().ok().unwrap_or_default(),
})
}
}
#[cfg(target_os = "linux")]
pub(crate) fn table_load_from_device(
entry_file: &Path,
table_file: &Path,
) -> Result<SMBiosData, anyhow::Error> {
let version = SMBiosEntryPoint64::try_load_from_file(entry_file)
.with_context(|| format!("cannot load SMBIOS entry from: {:?}", entry_file.display()))
.map(|entry_point| SMBiosVersion {
major: entry_point.major_version(),
minor: entry_point.minor_version(),
revision: 0,
})
.or_else(|err| {
if let Some(io_err) = err.downcast_ref::<IoError>() {
if io_err.kind() != ErrorKind::InvalidData {
return Err(err);
}
SMBiosEntryPoint32::try_load_from_file(entry_file)
.with_context(|| {
format!("cannot load SMBIOS entry from: {:?}", entry_file.display())
})
.map(|entry_point| SMBiosVersion {
major: entry_point.major_version(),
minor: entry_point.minor_version(),
revision: 0,
})
} else {
Err(err)
}
})?;
SMBiosData::try_load_from_file(table_file.to_str().unwrap(), Some(version))
.with_context(|| format!("cannot load SMBIOS table from: {:?}", table_file.display()))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::helpers::test_utils::get_test_filepath;
#[test]
fn test_table_load_from_device() {
let result = table_load_from_device(
&get_test_filepath("amd64/dgx_station/smbios_entry_point"),
&get_test_filepath("amd64/dgx_station/DMI"),
);
assert!(result.is_ok());
}
#[test]
fn test_bios_from_smbios() {
let smbios_data = table_load_from_device(
&get_test_filepath("amd64/dgx_station/smbios_entry_point"),
&get_test_filepath("amd64/dgx_station/DMI"),
)
.unwrap();
let bios_info_vec = smbios_data.collect::<SMBiosInformation>();
let bios_info = bios_info_vec.first().unwrap();
let bios = Bios::try_from(bios_info).unwrap();
assert!(bios.firmware_revision.is_some());
assert!(bios.release_date.is_some());
assert!(bios.revision.is_some());
assert_eq!(bios.vendor, "American Megatrends Inc.");
assert_eq!(bios.version, "0406");
assert_eq!(bios.release_date.unwrap(), "08/27/2018");
assert_eq!(bios.firmware_revision.unwrap(), "5.11");
assert_eq!(bios.revision.unwrap(), "0406");
}
#[test]
fn test_bios_release_date_smbios() {
let smbios_data = table_load_from_device(
&get_test_filepath("amd64/questing_vm/smbios_entry_point"),
&get_test_filepath("amd64/questing_vm/DMI"),
)
.unwrap();
let bios_info_vec = smbios_data.collect::<SMBiosInformation>();
let bios_info = bios_info_vec.first().unwrap();
let bios = Bios::try_from(bios_info).unwrap();
assert_eq!(bios.release_date.unwrap(), "2/2/98");
}
#[test]
fn test_processor_from_smbios() {
let smbios_data = table_load_from_device(
&get_test_filepath("amd64/dgx_station/smbios_entry_point"),
&get_test_filepath("amd64/dgx_station/DMI"),
)
.unwrap();
let processor_info_vec = smbios_data.collect::<SMBiosProcessorInformation>();
let processor_info = processor_info_vec.first().unwrap();
let processor = Processor::try_from((
processor_info,
get_test_filepath("amd64/dgx_station/cpuinfo_max_freq").as_path(),
))
.unwrap();
assert_eq!(
processor.identifier,
Some([0xf1, 0x6, 0x4, 0x0, 0xff, 0xfb, 0xeb, 0xbf]) );
assert_eq!(processor.frequency, 800);
assert_eq!(processor.manufacturer, "Intel");
assert_eq!(
processor.version,
"Intel(R) Xeon(R) CPU E5-2698 v4 @ 2.20GHz"
);
}
#[test]
fn test_collect_chassis_info() {
let smbios_data = table_load_from_device(
&get_test_filepath("amd64/dgx_station/smbios_entry_point"),
&get_test_filepath("amd64/dgx_station/DMI"),
)
.unwrap();
let chassis_info_vec = smbios_data.collect::<SMBiosSystemChassisInformation>();
let chassis_info = chassis_info_vec.first().unwrap();
let chassis = Chassis::try_from(chassis_info).unwrap();
assert_eq!(chassis.chassis_type, "Desktop");
assert_eq!(chassis.manufacturer, "EMPTY");
assert_eq!(chassis.sku, "Default string");
assert_eq!(chassis.version, "Default string");
}
#[test]
fn test_collect_motherboard_info() {
let smbios_data = table_load_from_device(
&get_test_filepath("amd64/dgx_station/smbios_entry_point"),
&get_test_filepath("amd64/dgx_station/DMI"),
)
.unwrap();
let board_info_vec = smbios_data.collect::<SMBiosBaseboardInformation>();
let board_info = board_info_vec.first().unwrap();
let board = Board::try_from(board_info).unwrap();
assert_eq!(board.manufacturer, "EMPTY");
assert_eq!(board.product_name, "X99-E-10G WS");
assert_eq!(board.version, "Rev 1.xx");
}
#[test]
fn test_collect_motherboard_info_from_device_tree() {
let board =
Board::try_from(get_test_filepath("arm64/rpi4b8g/device-tree/").as_path()).unwrap();
assert_eq!(board.manufacturer, "Unknown");
assert_eq!(board.product_name, "Raspberry Pi 4 Model B Rev 1.5");
assert_eq!(board.version, "raspberrypi,4-model-bbrcm,bcm2711");
}
#[test]
fn test_get_system_info() {
let smbios_data = table_load_from_device(
&get_test_filepath("amd64/dgx_station/smbios_entry_point"),
&get_test_filepath("amd64/dgx_station/DMI"),
)
.unwrap();
let system_data_vec = smbios_data.collect::<SMBiosSystemInformation>();
let system_data = system_data_vec.first().unwrap();
let system_info = SystemInfo::try_from_smbios(system_data).unwrap();
assert_eq!(system_info.product_name, "DGX Station");
assert_eq!(system_info.manufacturer, "NVIDIA");
}
}