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::ascii::alphanumeric1;
8use winnow::combinator::{alt, opt, preceded, separated};
9use winnow::prelude::*;
10use winnow::token::{literal, take_while};
11
12use crate::args::cpu_flags::CPUFlag;
13use crate::args::cpu_type::{CpuTypeAarch64, CpuTypeX86_64};
14use crate::common::{OnOff, YesNo};
15use crate::parsers::{DELIM_COMMA, ascii_plus_more};
16use crate::shell_string::ShellStringError;
17use crate::{ToArg, ToCommand, qao};
18
19const KEY_MIGRATABLE: &str = "migratable=";
20
21/// An x86 `-cpu` argument.
22///
23/// QEMU accepts a CPU model followed by comma-separated feature modifiers such
24/// as `migratable=yes`, `vmx`, or `-svm`.
25#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
26pub struct CpuX86 {
27    cpu_type: CpuTypeX86_64,
28    migratable: Option<YesNo>,
29    flags: Option<BTreeMap<CPUFlag, OnOff>>,
30}
31
32impl CpuX86 {
33    /// Creates a CPU argument for the given x86_64 CPU model.
34    pub fn new(cpu_type: CpuTypeX86_64) -> Self {
35        CpuX86 {
36            cpu_type,
37            migratable: 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(flags) = &self.flags {
73            let flags: Vec<String> = flags
74                .iter()
75                .map(|(flag, state)| match state {
76                    OnOff::On => flag.to_arg().to_string(),
77                    OnOff::Off => format!("-{}", flag.to_arg()),
78                })
79                .collect();
80            if !flags.is_empty() {
81                args.push(flags.join(DELIM_COMMA));
82            }
83        }
84
85        vec![args.join(DELIM_COMMA)]
86    }
87}
88
89impl FromStr for CpuX86 {
90    type Err = ShellStringError;
91
92    fn from_str(s: &str) -> Result<Self, Self::Err> {
93        cpu_x86_64.parse(s).map_err(|e| ShellStringError::from_parse(e))
94    }
95}
96
97fn cpu_type(s: &mut &str) -> ModalResult<CpuTypeX86_64> {
98    ascii_plus_more.parse_to::<CpuTypeX86_64>().parse_next(s)
99}
100
101fn migratable_item(s: &mut &str) -> ModalResult<YesNo> {
102    let _ = literal(KEY_MIGRATABLE).parse_next(s)?;
103    alphanumeric1.parse_to::<YesNo>().parse_next(s)
104}
105
106pub fn cpu_flag_parser<'a>(input: &mut &'a str) -> ModalResult<&'a str> {
107    take_while(1.., |c: char| c.is_ascii_alphanumeric() || c == '-' || c == '.').parse_next(input)
108}
109
110fn cpu_flag(s: &mut &str) -> ModalResult<(CPUFlag, OnOff)> {
111    let state = opt(literal('-')).parse_next(s)?;
112    let flag = cpu_flag_parser.parse_to::<CPUFlag>().parse_next(s)?;
113    Ok((
114        flag,
115        match state {
116            None => OnOff::On,
117            Some(_) => OnOff::Off,
118        },
119    ))
120}
121
122fn cpu_flag_property(s: &mut &str) -> ModalResult<(CPUFlag, OnOff)> {
123    let flag = cpu_flag_parser.parse_to::<CPUFlag>().parse_next(s)?;
124    let _ = literal("=").parse_next(s)?;
125    let state = alphanumeric1.parse_to::<OnOff>().parse_next(s)?;
126    Ok((flag, state))
127}
128
129#[derive(Debug, Clone, Eq, PartialEq)]
130enum CpuX86Item {
131    Migratable(YesNo),
132    Flag(CPUFlag, OnOff),
133}
134
135fn cpu_x86_item(s: &mut &str) -> ModalResult<CpuX86Item> {
136    alt((
137        migratable_item.map(CpuX86Item::Migratable),
138        cpu_flag_property.map(|(flag, state)| CpuX86Item::Flag(flag, state)),
139        cpu_flag.map(|(flag, state)| CpuX86Item::Flag(flag, state)),
140    ))
141    .parse_next(s)
142}
143
144fn cpu_x86_64(s: &mut &str) -> ModalResult<CpuX86> {
145    let cpu_type = cpu_type(s)?;
146    let items: Option<Vec<CpuX86Item>> = opt(preceded(literal(DELIM_COMMA), separated(1.., cpu_x86_item, DELIM_COMMA))).parse_next(s)?;
147    let mut migratable = None;
148    let mut flags = BTreeMap::new();
149
150    if let Some(items) = items {
151        for item in items {
152            match item {
153                CpuX86Item::Migratable(value) => migratable = Some(value),
154                CpuX86Item::Flag(flag, state) => {
155                    flags.insert(flag, state);
156                }
157            }
158        }
159    }
160
161    let flags = (!flags.is_empty()).then_some(flags);
162    Ok(CpuX86 { cpu_type, migratable, flags })
163}
164
165/// An aarch64 `-cpu` argument.
166#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
167pub struct CpuAarch64 {
168    pub cpu_type: CpuTypeAarch64,
169}
170
171impl ToCommand for CpuAarch64 {
172    fn command(&self) -> String {
173        ARG_CPU.to_string()
174    }
175    fn to_args(&self) -> Vec<String> {
176        vec![self.cpu_type.to_command().join("")]
177    }
178}
179
180fn cpu_type_aarch64(s: &mut &str) -> ModalResult<CpuTypeAarch64> {
181    ascii_plus_more.parse_to::<CpuTypeAarch64>().parse_next(s)
182}
183
184impl FromStr for CpuAarch64 {
185    type Err = ShellStringError;
186
187    fn from_str(s: &str) -> Result<Self, Self::Err> {
188        cpu_aarch64.parse(s).map_err(|e| ShellStringError::from_parse(e))
189    }
190}
191
192fn cpu_aarch64(s: &mut &str) -> ModalResult<CpuAarch64> {
193    let cpu_type = cpu_type_aarch64.parse_next(s)?;
194    Ok(CpuAarch64 { cpu_type })
195}