use crate::persona::Dmi;
use thiserror::Error;
#[derive(Debug, Error, PartialEq, Eq)]
pub enum SmbiosError {
#[error("DMI field '{field}' contains NUL byte; QEMU argv cannot encode it")]
NulByte {
field: &'static str,
},
}
pub fn smbios_argv(dmi: &Dmi) -> Result<Vec<String>, SmbiosError> {
check_nul("sys_vendor", &dmi.sys_vendor)?;
check_nul("product_name", &dmi.product_name)?;
check_nul("bios_vendor", &dmi.bios_vendor)?;
check_nul("bios_version", &dmi.bios_version)?;
check_nul("bios_date", &dmi.bios_date)?;
if let Some(pv) = dmi.product_version.as_deref() {
check_nul("product_version", pv)?;
}
if let Some(bn) = dmi.board_name.as_deref() {
check_nul("board_name", bn)?;
}
let mut argv = Vec::with_capacity(8);
argv.push("-smbios".to_string());
argv.push(format!(
"type=0,vendor={},version={},date={}",
qemu_escape(&dmi.bios_vendor),
qemu_escape(&dmi.bios_version),
qemu_escape(&dmi.bios_date),
));
let mut type1 = format!(
"type=1,manufacturer={},product={}",
qemu_escape(&dmi.sys_vendor),
qemu_escape(&dmi.product_name),
);
if let Some(pv) = dmi.product_version.as_deref() {
type1.push_str(",version=");
type1.push_str(&qemu_escape(pv));
}
argv.push("-smbios".to_string());
argv.push(type1);
if let Some(bn) = dmi.board_name.as_deref() {
argv.push("-smbios".to_string());
argv.push(format!(
"type=2,manufacturer={},product={}",
qemu_escape(&dmi.sys_vendor),
qemu_escape(bn),
));
}
let _ = &dmi.chassis_type;
Ok(argv)
}
fn check_nul(field: &'static str, value: &str) -> Result<(), SmbiosError> {
if value.contains('\0') {
Err(SmbiosError::NulByte { field })
} else {
Ok(())
}
}
fn qemu_escape(s: &str) -> String {
if s.contains(',') {
s.replace(',', ",,")
} else {
s.to_string()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
fn minimal_dmi() -> Dmi {
Dmi {
sys_vendor: "LENOVO".into(),
product_name: "21HM".into(),
product_version: None,
bios_vendor: "LENOVO".into(),
bios_version: "N3VET18W".into(),
bios_date: "03/15/2024".into(),
board_name: None,
chassis_type: None,
}
}
#[test]
fn emits_type0_and_type1_for_minimal_dmi() {
let argv = smbios_argv(&minimal_dmi()).unwrap();
assert_eq!(argv.len(), 4, "minimal DMI: 2 -smbios pairs = 4 argv");
assert_eq!(argv[0], "-smbios");
assert_eq!(
argv[1],
"type=0,vendor=LENOVO,version=N3VET18W,date=03/15/2024"
);
assert_eq!(argv[2], "-smbios");
assert_eq!(argv[3], "type=1,manufacturer=LENOVO,product=21HM");
}
#[test]
fn emits_type2_when_board_name_set() {
let mut d = minimal_dmi();
d.board_name = Some("21HMCTO1WW".into());
let argv = smbios_argv(&d).unwrap();
assert!(argv.iter().any(|a| a.starts_with("type=2,")));
let t2 = argv
.iter()
.find(|a| a.starts_with("type=2,"))
.expect("type=2");
assert_eq!(t2, "type=2,manufacturer=LENOVO,product=21HMCTO1WW");
}
#[test]
fn skips_type2_when_board_name_unset() {
let argv = smbios_argv(&minimal_dmi()).unwrap();
assert!(!argv.iter().any(|a| a.starts_with("type=2,")));
}
#[test]
fn does_not_emit_type3_even_when_chassis_type_set() {
let mut d = minimal_dmi();
d.chassis_type = Some(10); let argv = smbios_argv(&d).unwrap();
assert!(
!argv.iter().any(|a| a.starts_with("type=3")),
"must not emit `-smbios type=3`; got {argv:?}"
);
}
#[test]
fn emits_product_version_when_set() {
let mut d = minimal_dmi();
d.product_version = Some("ThinkPad X1 Carbon Gen 11".into());
let argv = smbios_argv(&d).unwrap();
let t1 = argv
.iter()
.find(|a| a.starts_with("type=1,"))
.expect("type=1");
assert!(t1.contains("version=ThinkPad X1 Carbon Gen 11"));
}
#[test]
fn escapes_comma_in_value() {
let mut d = minimal_dmi();
d.product_name = "Standard PC (Q35 + ICH9, 2009)".into();
let argv = smbios_argv(&d).unwrap();
let t1 = argv
.iter()
.find(|a| a.starts_with("type=1,"))
.expect("type=1");
assert!(t1.contains("product=Standard PC (Q35 + ICH9,, 2009)"));
}
#[test]
fn rejects_nul_byte_in_any_field() {
let mut d = minimal_dmi();
d.product_name = "foo\0bar".into();
let err = smbios_argv(&d).unwrap_err();
assert_eq!(
err,
SmbiosError::NulByte {
field: "product_name"
}
);
}
#[test]
fn rejects_nul_byte_in_optional_field() {
let mut d = minimal_dmi();
d.board_name = Some("evil\0name".into());
let err = smbios_argv(&d).unwrap_err();
assert_eq!(
err,
SmbiosError::NulByte {
field: "board_name"
}
);
}
#[test]
fn shell_metacharacters_pass_through_as_literal_argv() {
let mut d = minimal_dmi();
d.product_name = "a;b|c&d`e$(f)g\\h".into();
let argv = smbios_argv(&d).unwrap();
let t1 = argv
.iter()
.find(|a| a.starts_with("type=1,"))
.expect("type=1");
assert!(t1.contains("product=a;b|c&d`e$(f)g\\h"));
}
}