qemu_command_builder/args/
cpu.rs1use 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#[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 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 pub fn migratable(&mut self, state: YesNo) -> &mut Self {
44 self.migratable = Some(state);
45 self
46 }
47
48 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#[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}