Skip to main content

qemu_command_builder/args/
smbios.rs

1use crate::parsers::ARG_SMBIOS;
2use bon::Builder;
3use proptest_derive::Arbitrary;
4use std::path::PathBuf;
5use std::str::FromStr;
6
7use crate::common::OnOff;
8use crate::parsers::DELIM_COMMA;
9use crate::to_command::{ToArg, ToCommand};
10
11/// Load SMBIOS entry from binary file.
12#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
13pub struct SmbiosFile {
14    path: PathBuf,
15}
16
17impl ToCommand for SmbiosFile {
18    fn to_args(&self) -> Vec<String> {
19        let mut args = vec![];
20        args.push(format!("file={}", self.path.display()));
21        args
22    }
23}
24
25impl FromStr for SmbiosFile {
26    type Err = String;
27
28    fn from_str(s: &str) -> Result<Self, Self::Err> {
29        let path = s.strip_prefix("file=").ok_or_else(|| format!("unsupported smbios file form: {s}"))?;
30        Ok(Self { path: PathBuf::from(path) })
31    }
32}
33/// Specify SMBIOS type 0 fields
34///
35/// Corresponds to QEMU `-smbios type=0[,vendor=str][,version=str][,date=str][,release=%d.%d][,uefi=on|off]`.
36#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
37pub struct SmbiosType0 {
38    vendor: Option<String>,
39    version: Option<String>,
40    date: Option<String>,
41    release: Option<(usize, usize)>,
42    uefi: Option<OnOff>,
43}
44
45impl ToCommand for SmbiosType0 {
46    fn to_args(&self) -> Vec<String> {
47        let mut args = vec!["type=0".to_string()];
48        if let Some(vendor) = &self.vendor {
49            args.push(format!("vendor={}", vendor));
50        }
51        if let Some(version) = &self.version {
52            args.push(format!("version={}", version));
53        }
54        if let Some(date) = &self.date {
55            args.push(format!("date={}", date));
56        }
57        if let Some(release) = &self.release {
58            args.push(format!("release={}.{}", release.0, release.1));
59        }
60        if let Some(uefi) = &self.uefi {
61            args.push(format!("uefi={}", uefi.to_arg()));
62        }
63        vec![args.join(DELIM_COMMA)]
64    }
65}
66
67impl FromStr for SmbiosType0 {
68    type Err = String;
69
70    fn from_str(s: &str) -> Result<Self, Self::Err> {
71        let mut value = Self::default();
72        for part in s.split(',') {
73            let (key, raw) = part.split_once('=').ok_or_else(|| format!("invalid smbios type 0 option: {part}"))?;
74            match key {
75                "type" if raw == "0" => {}
76                "vendor" => value.vendor = Some(raw.to_string()),
77                "version" => value.version = Some(raw.to_string()),
78                "date" => value.date = Some(raw.to_string()),
79                "release" => {
80                    let (major, minor) = raw.split_once('.').ok_or_else(|| format!("invalid release value: {raw}"))?;
81                    value.release = Some((major.parse::<usize>().map_err(|e| e.to_string())?, minor.parse::<usize>().map_err(|e| e.to_string())?));
82                }
83                "uefi" => value.uefi = Some(raw.parse::<OnOff>().map_err(|_| format!("invalid uefi value: {raw}"))?),
84                other => return Err(format!("unsupported smbios type 0 option: {other}")),
85            }
86        }
87        Ok(value)
88    }
89}
90/// Specify SMBIOS type 1 fields
91///
92/// Corresponds to QEMU `-smbios type=1[,manufacturer=str][,product=str][,version=str][,serial=str][,uuid=uuid][,sku=str][,family=str]`.
93#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
94pub struct SmbiosType1 {
95    manufacturer: Option<String>,
96    product: Option<String>,
97    version: Option<String>,
98    serial: Option<String>,
99    uuid: Option<String>,
100    sku: Option<String>,
101    family: Option<String>,
102}
103
104impl ToCommand for SmbiosType1 {
105    fn to_args(&self) -> Vec<String> {
106        let mut args = vec!["type=1".to_string()];
107        if let Some(manufacturer) = &self.manufacturer {
108            args.push(format!("manufacturer={}", manufacturer));
109        }
110        if let Some(product) = &self.product {
111            args.push(format!("product={}", product));
112        }
113        if let Some(version) = &self.version {
114            args.push(format!("version={}", version));
115        }
116        if let Some(serial) = &self.serial {
117            args.push(format!("serial={}", serial));
118        }
119        if let Some(uuid) = &self.uuid {
120            args.push(format!("uuid={}", uuid));
121        }
122        if let Some(sku) = &self.sku {
123            args.push(format!("sku={}", sku));
124        }
125        if let Some(family) = &self.family {
126            args.push(format!("family={}", family));
127        }
128        vec![args.join(DELIM_COMMA)]
129    }
130}
131
132impl FromStr for SmbiosType1 {
133    type Err = String;
134
135    fn from_str(s: &str) -> Result<Self, Self::Err> {
136        let mut value = Self::default();
137        for part in s.split(',') {
138            let (key, raw) = part.split_once('=').ok_or_else(|| format!("invalid smbios type 1 option: {part}"))?;
139            match key {
140                "type" if raw == "1" => {}
141                "manufacturer" => value.manufacturer = Some(raw.to_string()),
142                "product" => value.product = Some(raw.to_string()),
143                "version" => value.version = Some(raw.to_string()),
144                "serial" => value.serial = Some(raw.to_string()),
145                "uuid" => value.uuid = Some(raw.to_string()),
146                "sku" => value.sku = Some(raw.to_string()),
147                "family" => value.family = Some(raw.to_string()),
148                other => return Err(format!("unsupported smbios type 1 option: {other}")),
149            }
150        }
151        Ok(value)
152    }
153}
154
155/// Specify SMBIOS type 2 fields
156///
157/// Corresponds to QEMU `-smbios type=2[,manufacturer=str][,product=str][,version=str][,serial=str][,asset=str][,location=str]`.
158#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
159pub struct SmbiosType2 {
160    manufacturer: Option<String>,
161    product: Option<String>,
162    version: Option<String>,
163    serial: Option<String>,
164    asset: Option<String>,
165    location: Option<String>,
166}
167
168impl ToCommand for SmbiosType2 {
169    fn to_args(&self) -> Vec<String> {
170        let mut args = vec!["type=2".to_string()];
171        if let Some(manufacturer) = &self.manufacturer {
172            args.push(format!("manufacturer={}", manufacturer));
173        }
174        if let Some(product) = &self.product {
175            args.push(format!("product={}", product));
176        }
177        if let Some(version) = &self.version {
178            args.push(format!("version={}", version));
179        }
180        if let Some(serial) = &self.serial {
181            args.push(format!("serial={}", serial));
182        }
183        if let Some(asset) = &self.asset {
184            args.push(format!("asset={}", asset));
185        }
186        if let Some(location) = &self.location {
187            args.push(format!("location={}", location));
188        }
189        vec![args.join(DELIM_COMMA)]
190    }
191}
192
193impl FromStr for SmbiosType2 {
194    type Err = String;
195
196    fn from_str(s: &str) -> Result<Self, Self::Err> {
197        let mut value = Self::default();
198        for part in s.split(',') {
199            let (key, raw) = part.split_once('=').ok_or_else(|| format!("invalid smbios type 2 option: {part}"))?;
200            match key {
201                "type" if raw == "2" => {}
202                "manufacturer" => value.manufacturer = Some(raw.to_string()),
203                "product" => value.product = Some(raw.to_string()),
204                "version" => value.version = Some(raw.to_string()),
205                "serial" => value.serial = Some(raw.to_string()),
206                "asset" => value.asset = Some(raw.to_string()),
207                "location" => value.location = Some(raw.to_string()),
208                other => return Err(format!("unsupported smbios type 2 option: {other}")),
209            }
210        }
211        Ok(value)
212    }
213}
214
215/// Specify SMBIOS type 3 fields
216#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
217pub struct SmbiosType3 {
218    manufacturer: Option<String>,
219    version: Option<String>,
220    serial: Option<String>,
221    asset: Option<String>,
222    sku: Option<String>,
223}
224
225impl ToCommand for SmbiosType3 {
226    fn to_args(&self) -> Vec<String> {
227        let mut args = vec!["type=3".to_string()];
228        if let Some(manufacturer) = &self.manufacturer {
229            args.push(format!("manufacturer={}", manufacturer));
230        }
231        if let Some(version) = &self.version {
232            args.push(format!("version={}", version));
233        }
234        if let Some(serial) = &self.serial {
235            args.push(format!("serial={}", serial));
236        }
237        if let Some(asset) = &self.asset {
238            args.push(format!("asset={}", asset));
239        }
240        if let Some(sku) = &self.sku {
241            args.push(format!("sku={}", sku));
242        }
243        vec![args.join(DELIM_COMMA)]
244    }
245}
246
247impl FromStr for SmbiosType3 {
248    type Err = String;
249
250    fn from_str(s: &str) -> Result<Self, Self::Err> {
251        let mut value = Self::default();
252        for part in s.split(',') {
253            let (key, raw) = part.split_once('=').ok_or_else(|| format!("invalid smbios type 3 option: {part}"))?;
254            match key {
255                "type" if raw == "3" => {}
256                "manufacturer" => value.manufacturer = Some(raw.to_string()),
257                "version" => value.version = Some(raw.to_string()),
258                "serial" => value.serial = Some(raw.to_string()),
259                "asset" => value.asset = Some(raw.to_string()),
260                "sku" => value.sku = Some(raw.to_string()),
261                other => return Err(format!("unsupported smbios type 3 option: {other}")),
262            }
263        }
264        Ok(value)
265    }
266}
267
268/// Specify SMBIOS type 4 fields
269#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
270pub struct SmbiosType4 {
271    sock_pfx: Option<String>,
272    manufacturer: Option<String>,
273    version: Option<String>,
274    serial: Option<String>,
275    asset: Option<String>,
276    part: Option<String>,
277    max_speed: Option<usize>,
278    current_speed: Option<usize>,
279    processor_family: Option<usize>,
280    processor_id: Option<usize>,
281}
282
283impl ToCommand for SmbiosType4 {
284    fn to_args(&self) -> Vec<String> {
285        let mut args = vec!["type=4".to_string()];
286        if let Some(sock_pfx) = &self.sock_pfx {
287            args.push(format!("sock_pfx={}", sock_pfx));
288        }
289        if let Some(manufacturer) = &self.manufacturer {
290            args.push(format!("manufacturer={}", manufacturer));
291        }
292        if let Some(version) = &self.version {
293            args.push(format!("version={}", version));
294        }
295        if let Some(serial) = &self.serial {
296            args.push(format!("serial={}", serial));
297        }
298        if let Some(asset) = &self.asset {
299            args.push(format!("asset={}", asset));
300        }
301        if let Some(part) = &self.part {
302            args.push(format!("part={}", part));
303        }
304        if let Some(max_speed) = &self.max_speed {
305            args.push(format!("max-speed={}", max_speed));
306        }
307        if let Some(current_speed) = &self.current_speed {
308            args.push(format!("current-speed={}", current_speed));
309        }
310        if let Some(processor_family) = &self.processor_family {
311            args.push(format!("processor-family={}", processor_family));
312        }
313        if let Some(processor_id) = &self.processor_id {
314            args.push(format!("processor-id={}", processor_id));
315        }
316        vec![args.join(DELIM_COMMA)]
317    }
318}
319
320impl FromStr for SmbiosType4 {
321    type Err = String;
322
323    fn from_str(s: &str) -> Result<Self, Self::Err> {
324        let mut value = Self::default();
325        for part in s.split(',') {
326            let (key, raw) = part.split_once('=').ok_or_else(|| format!("invalid smbios type 4 option: {part}"))?;
327            match key {
328                "type" if raw == "4" => {}
329                "sock_pfx" => value.sock_pfx = Some(raw.to_string()),
330                "manufacturer" => value.manufacturer = Some(raw.to_string()),
331                "version" => value.version = Some(raw.to_string()),
332                "serial" => value.serial = Some(raw.to_string()),
333                "asset" => value.asset = Some(raw.to_string()),
334                "part" => value.part = Some(raw.to_string()),
335                "max-speed" => value.max_speed = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
336                "current-speed" => value.current_speed = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
337                "processor-family" => value.processor_family = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
338                "processor-id" => value.processor_id = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
339                other => return Err(format!("unsupported smbios type 4 option: {other}")),
340            }
341        }
342        Ok(value)
343    }
344}
345
346/// Specify SMBIOS type 8 fields
347#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
348pub struct SmbiosType8 {
349    external_reference: Option<String>,
350    internal_reference: Option<String>,
351    connector_type: Option<usize>,
352    port_type: Option<usize>,
353}
354
355impl ToCommand for SmbiosType8 {
356    fn to_args(&self) -> Vec<String> {
357        let mut args = vec!["type=8".to_string()];
358        if let Some(external_reference) = &self.external_reference {
359            args.push(format!("external_reference={}", external_reference));
360        }
361        if let Some(internal_reference) = &self.internal_reference {
362            args.push(format!("internal_reference={}", internal_reference));
363        }
364        if let Some(connector_type) = &self.connector_type {
365            args.push(format!("connector_type={}", connector_type));
366        }
367        if let Some(port_type) = &self.port_type {
368            args.push(format!("port_type={}", port_type));
369        }
370        vec![args.join(DELIM_COMMA)]
371    }
372}
373
374impl FromStr for SmbiosType8 {
375    type Err = String;
376
377    fn from_str(s: &str) -> Result<Self, Self::Err> {
378        let mut value = Self::default();
379        for part in s.split(',') {
380            let (key, raw) = part.split_once('=').ok_or_else(|| format!("invalid smbios type 8 option: {part}"))?;
381            match key {
382                "type" if raw == "8" => {}
383                "external_reference" => value.external_reference = Some(raw.to_string()),
384                "internal_reference" => value.internal_reference = Some(raw.to_string()),
385                "connector_type" => value.connector_type = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
386                "port_type" => value.port_type = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
387                other => return Err(format!("unsupported smbios type 8 option: {other}")),
388            }
389        }
390        Ok(value)
391    }
392}
393
394/// Specify SMBIOS type 11 fields
395#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
396pub struct SmbiosType11 {
397    value: Option<String>,
398    path: Option<String>,
399}
400
401impl ToCommand for SmbiosType11 {
402    fn to_args(&self) -> Vec<String> {
403        let mut args = vec!["type=11".to_string()];
404        if let Some(value) = &self.value {
405            args.push(format!("value={}", value));
406        }
407        if let Some(path) = &self.path {
408            args.push(format!("path={}", path));
409        }
410        vec![args.join(DELIM_COMMA)]
411    }
412}
413
414impl FromStr for SmbiosType11 {
415    type Err = String;
416
417    fn from_str(s: &str) -> Result<Self, Self::Err> {
418        let mut value = Self::default();
419        for part in s.split(',') {
420            let (key, raw) = part.split_once('=').ok_or_else(|| format!("invalid smbios type 11 option: {part}"))?;
421            match key {
422                "type" if raw == "11" => {}
423                "value" => value.value = Some(raw.to_string()),
424                "path" => value.path = Some(raw.to_string()),
425                other => return Err(format!("unsupported smbios type 11 option: {other}")),
426            }
427        }
428        Ok(value)
429    }
430}
431
432/// Specify SMBIOS type 17 fields
433#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
434pub struct SmbiosType17 {
435    loc_pfx: Option<String>,
436    bank: Option<String>,
437    manufacturer: Option<String>,
438    serial: Option<String>,
439    asset: Option<String>,
440    part: Option<String>,
441    speed: Option<usize>,
442}
443
444impl ToCommand for SmbiosType17 {
445    fn to_args(&self) -> Vec<String> {
446        let mut args = vec!["type=17".to_string()];
447
448        if let Some(loc_pfx) = &self.loc_pfx {
449            args.push(format!("loc_pfx={}", loc_pfx));
450        }
451        if let Some(bank) = &self.bank {
452            args.push(format!("bank={}", bank));
453        }
454        if let Some(manufacturer) = &self.manufacturer {
455            args.push(format!("manufacturer={}", manufacturer));
456        }
457        if let Some(serial) = &self.serial {
458            args.push(format!("serial={}", serial));
459        }
460        if let Some(asset) = &self.asset {
461            args.push(format!("asset={}", asset));
462        }
463        if let Some(part) = &self.part {
464            args.push(format!("part={}", part));
465        }
466        if let Some(speed) = &self.speed {
467            args.push(format!("speed={}", speed));
468        }
469        vec![args.join(DELIM_COMMA)]
470    }
471}
472
473impl FromStr for SmbiosType17 {
474    type Err = String;
475
476    fn from_str(s: &str) -> Result<Self, Self::Err> {
477        let mut value = Self::default();
478        for part in s.split(',') {
479            let (key, raw) = part.split_once('=').ok_or_else(|| format!("invalid smbios type 17 option: {part}"))?;
480            match key {
481                "type" if raw == "17" => {}
482                "loc_pfx" => value.loc_pfx = Some(raw.to_string()),
483                "bank" => value.bank = Some(raw.to_string()),
484                "manufacturer" => value.manufacturer = Some(raw.to_string()),
485                "serial" => value.serial = Some(raw.to_string()),
486                "asset" => value.asset = Some(raw.to_string()),
487                "part" => value.part = Some(raw.to_string()),
488                "speed" => value.speed = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
489                other => return Err(format!("unsupported smbios type 17 option: {other}")),
490            }
491        }
492        Ok(value)
493    }
494}
495
496/// Specify SMBIOS type 41 fields
497#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
498pub struct SmbiosType41 {
499    designation: Option<String>,
500    kind: Option<String>,
501    instance: Option<usize>,
502    pcidev: Option<String>,
503}
504
505impl ToCommand for SmbiosType41 {
506    fn to_args(&self) -> Vec<String> {
507        let mut args = vec!["type=41".to_string()];
508
509        if let Some(designation) = &self.designation {
510            args.push(format!("designation={}", designation));
511        }
512        if let Some(kind) = &self.kind {
513            args.push(format!("kind={}", kind));
514        }
515        if let Some(instance) = self.instance {
516            args.push(format!("instance={}", instance));
517        }
518        if let Some(pcidev) = &self.pcidev {
519            args.push(format!("pcidev={}", pcidev));
520        }
521        vec![args.join(DELIM_COMMA)]
522    }
523}
524
525impl FromStr for SmbiosType41 {
526    type Err = String;
527
528    fn from_str(s: &str) -> Result<Self, Self::Err> {
529        let mut value = Self::default();
530        for part in s.split(',') {
531            let (key, raw) = part.split_once('=').ok_or_else(|| format!("invalid smbios type 41 option: {part}"))?;
532            match key {
533                "type" if raw == "41" => {}
534                "designation" => value.designation = Some(raw.to_string()),
535                "kind" => value.kind = Some(raw.to_string()),
536                "instance" => value.instance = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
537                "pcidev" => value.pcidev = Some(raw.to_string()),
538                other => return Err(format!("unsupported smbios type 41 option: {other}")),
539            }
540        }
541        Ok(value)
542    }
543}
544
545/// A supported `-smbios` payload.
546///
547/// The formatter emits the canonical QEMU comma-separated form for the
548/// selected SMBIOS record type or `file=` source.
549#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
550pub enum Smbios {
551    File(SmbiosFile),
552    Type0(SmbiosType0),
553    Type1(SmbiosType1),
554    Type2(SmbiosType2),
555    Type3(SmbiosType3),
556    Type4(SmbiosType4),
557    Type8(SmbiosType8),
558    Type11(SmbiosType11),
559    Type17(SmbiosType17),
560    Type41(SmbiosType41),
561}
562
563impl ToCommand for Smbios {
564    fn command(&self) -> String {
565        ARG_SMBIOS.to_string()
566    }
567    fn to_args(&self) -> Vec<String> {
568        match self {
569            Smbios::File(file) => file.to_args(),
570            Smbios::Type0(type0) => type0.to_args(),
571            Smbios::Type1(type1) => type1.to_args(),
572            Smbios::Type2(type2) => type2.to_args(),
573            Smbios::Type3(type3) => type3.to_args(),
574            Smbios::Type4(type4) => type4.to_args(),
575            Smbios::Type8(type8) => type8.to_args(),
576            Smbios::Type11(type11) => type11.to_args(),
577            Smbios::Type17(type17) => type17.to_args(),
578            Smbios::Type41(type41) => type41.to_args(),
579        }
580    }
581}
582
583impl FromStr for Smbios {
584    type Err = String;
585
586    fn from_str(s: &str) -> Result<Self, Self::Err> {
587        if s.starts_with("file=") {
588            return Ok(Self::File(s.parse::<SmbiosFile>()?));
589        }
590        if s.starts_with("type=41") {
591            return Ok(Self::Type41(s.parse::<SmbiosType41>()?));
592        }
593        if s.starts_with("type=17") {
594            return Ok(Self::Type17(s.parse::<SmbiosType17>()?));
595        }
596        if s.starts_with("type=11") {
597            return Ok(Self::Type11(s.parse::<SmbiosType11>()?));
598        }
599        if s.starts_with("type=0") {
600            return Ok(Self::Type0(s.parse::<SmbiosType0>()?));
601        }
602        if s.starts_with("type=1") {
603            return Ok(Self::Type1(s.parse::<SmbiosType1>()?));
604        }
605        if s.starts_with("type=2") {
606            return Ok(Self::Type2(s.parse::<SmbiosType2>()?));
607        }
608        if s.starts_with("type=3") {
609            return Ok(Self::Type3(s.parse::<SmbiosType3>()?));
610        }
611        if s.starts_with("type=4") {
612            return Ok(Self::Type4(s.parse::<SmbiosType4>()?));
613        }
614        if s.starts_with("type=8") {
615            return Ok(Self::Type8(s.parse::<SmbiosType8>()?));
616        }
617
618        Err(format!("unsupported smbios argument: {s}"))
619    }
620}