Skip to main content

qemu_command_builder/args/
cpu.rs

1use crate::parsers::ARG_CPU;
2use std::collections::{BTreeMap, BTreeSet};
3use std::str::FromStr;
4
5use bon::Builder;
6use proptest_derive::Arbitrary;
7use winnow::prelude::*;
8use winnow::token::take_while;
9
10use crate::args::cpu_flags::CPUFlag;
11use crate::args::cpu_type::{CpuTypeAarch64, CpuTypeX86_64};
12use crate::common::{OnOff, YesNo};
13use crate::parsers::{DELIM_COMMA, ascii_plus_more};
14use crate::shell_string::ShellStringError;
15use crate::{ToArg, ToCommand, qao};
16
17const KEY_MIGRATABLE: &str = "migratable=";
18
19/// An x86 `-cpu` argument.
20///
21/// QEMU accepts a CPU model followed by comma-separated feature modifiers such
22/// as `migratable=yes`, `vmx`, or `-svm`.
23#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
24pub struct CpuX86 {
25    cpu_type: CpuTypeX86_64,
26    migratable: Option<YesNo>,
27    properties: Option<Vec<(String, String)>>,
28    flags: Option<BTreeMap<CPUFlag, OnOff>>,
29}
30
31impl CpuX86 {
32    /// Creates a CPU argument for the given x86_64 CPU model.
33    pub fn new(cpu_type: CpuTypeX86_64) -> Self {
34        CpuX86 {
35            cpu_type,
36            migratable: None,
37            properties: None,
38            flags: None,
39        }
40    }
41
42    /// Sets the `migratable=` CPU property.
43    pub fn migratable(&mut self, state: YesNo) -> &mut Self {
44        self.migratable = Some(state);
45        self
46    }
47
48    /// Sets CPU feature toggles.
49    ///
50    /// Enabled features are rendered as `flag`, while disabled features are
51    /// rendered as `-flag`. If the same feature appears more than once, the
52    /// final state for that feature wins.
53    pub fn flags(&mut self, flags: BTreeSet<(CPUFlag, OnOff)>) -> &mut Self {
54        let mut normalized = BTreeMap::new();
55        for (flag, state) in flags {
56            normalized.insert(flag, state);
57        }
58        self.flags = Some(normalized);
59        self
60    }
61}
62
63impl ToCommand for CpuX86 {
64    fn command(&self) -> String {
65        ARG_CPU.to_string()
66    }
67
68    fn to_args(&self) -> Vec<String> {
69        let mut args = vec![self.cpu_type.to_arg().to_string()];
70
71        qao!(&self.migratable, args, KEY_MIGRATABLE);
72        if let Some(properties) = &self.properties {
73            args.extend(properties.iter().map(|(key, value)| format!("{key}={value}")));
74        }
75        if let Some(flags) = &self.flags {
76            let flags: Vec<String> = flags
77                .iter()
78                .map(|(flag, state)| match state {
79                    OnOff::On => flag.to_arg().to_string(),
80                    OnOff::Off => format!("-{}", flag.to_arg()),
81                })
82                .collect();
83            if !flags.is_empty() {
84                args.push(flags.join(DELIM_COMMA));
85            }
86        }
87
88        vec![args.join(DELIM_COMMA)]
89    }
90}
91
92impl FromStr for CpuX86 {
93    type Err = ShellStringError;
94
95    fn from_str(s: &str) -> Result<Self, Self::Err> {
96        parse_cpu_x86(s).map_err(ShellStringError::new)
97    }
98}
99
100fn parse_cpu_x86(s: &str) -> Result<CpuX86, String> {
101    let mut parts = s.split(DELIM_COMMA);
102    let cpu_type = parts
103        .next()
104        .ok_or_else(|| "CPU model is required".to_string())?
105        .parse::<CpuTypeX86_64>()
106        .map_err(|_| "invalid CPU model".to_string())?;
107    let mut migratable = None;
108    let mut properties = Vec::new();
109    let mut flags = BTreeMap::new();
110
111    for part in parts {
112        if let Some(value) = part.strip_prefix(KEY_MIGRATABLE) {
113            migratable = Some(match value {
114                "yes" | "on" => YesNo::Yes,
115                "no" | "off" => YesNo::No,
116                _ => return Err(format!("invalid migratable value: {value}")),
117            });
118        } else if let Some((key, value)) = part.split_once('=') {
119            match (key.parse::<CPUFlag>(), value.parse::<OnOff>()) {
120                (Ok(flag), Ok(state)) => {
121                    flags.insert(flag, state);
122                }
123                _ => properties.push((key.to_string(), value.to_string())),
124            }
125        } else {
126            let (name, state) = part.strip_prefix('-').map_or((part, OnOff::On), |name| (name, OnOff::Off));
127            let flag = name.parse::<CPUFlag>().map_err(|_| format!("unsupported CPU feature: {name}"))?;
128            flags.insert(flag, state);
129        }
130    }
131
132    Ok(CpuX86 {
133        cpu_type,
134        migratable,
135        properties: (!properties.is_empty()).then_some(properties),
136        flags: (!flags.is_empty()).then_some(flags),
137    })
138}
139
140pub fn cpu_flag_parser<'a>(input: &mut &'a str) -> ModalResult<&'a str> {
141    take_while(1.., |c: char| c.is_ascii_alphanumeric() || c == '-' || c == '.').parse_next(input)
142}
143
144/// An aarch64 `-cpu` argument.
145#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
146pub struct CpuAarch64 {
147    pub cpu_type: CpuTypeAarch64,
148}
149
150impl ToCommand for CpuAarch64 {
151    fn command(&self) -> String {
152        ARG_CPU.to_string()
153    }
154    fn to_args(&self) -> Vec<String> {
155        vec![self.cpu_type.to_command().join("")]
156    }
157}
158
159fn cpu_type_aarch64(s: &mut &str) -> ModalResult<CpuTypeAarch64> {
160    ascii_plus_more.parse_to::<CpuTypeAarch64>().parse_next(s)
161}
162
163impl FromStr for CpuAarch64 {
164    type Err = ShellStringError;
165
166    fn from_str(s: &str) -> Result<Self, Self::Err> {
167        cpu_aarch64.parse(s).map_err(|e| ShellStringError::from_parse(e))
168    }
169}
170
171fn cpu_aarch64(s: &mut &str) -> ModalResult<CpuAarch64> {
172    let cpu_type = cpu_type_aarch64.parse_next(s)?;
173    Ok(CpuAarch64 { cpu_type })
174}