qemu_command_builder/args/
fsdev.rs1use crate::parsers::{ARG_FSDEV, DELIM_COMMA};
2use crate::to_command::{ToArg, ToCommand};
3use bon::Builder;
4use proptest_derive::Arbitrary;
5use std::path::PathBuf;
6use std::str::FromStr;
7
8#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
10pub enum SecurityModel {
11 Passthrough,
12 MappedXAttr,
13 MappedFile,
14 None,
15}
16
17impl ToArg for SecurityModel {
18 fn to_arg(&self) -> &str {
19 match self {
20 SecurityModel::Passthrough => "passthrough",
21 SecurityModel::MappedXAttr => "mapped-xattr",
22 SecurityModel::MappedFile => "mapped-file",
23 SecurityModel::None => "none",
24 }
25 }
26}
27
28impl FromStr for SecurityModel {
29 type Err = String;
30
31 fn from_str(s: &str) -> Result<Self, Self::Err> {
32 match s {
33 "passthrough" => Ok(Self::Passthrough),
34 "mapped-xattr" => Ok(Self::MappedXAttr),
35 "mapped-file" => Ok(Self::MappedFile),
36 "none" => Ok(Self::None),
37 _ => Err(format!("invalid security_model value: {s}")),
38 }
39 }
40}
41
42#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
46pub struct FsDevLocal {
47 id: String,
49
50 path: PathBuf,
52
53 security_model: SecurityModel,
55
56 writeout: Option<()>,
58
59 readonly: Option<()>,
61
62 fmode: Option<String>,
64
65 dmode: Option<String>,
67
68 max_xattr: Option<u64>,
70
71 throttling_bps_total: Option<u64>,
73 throttling_bps_read: Option<u64>,
74 throttling_bps_write: Option<u64>,
75
76 throttling_bps_total_max: Option<u64>,
78 bps_read_max: Option<u64>,
79 bps_write_max: Option<u64>,
80
81 throttling_iops_total: Option<u64>,
83 throttling_iops_read: Option<u64>,
84 throttling_iops_write: Option<u64>,
85
86 throttling_iops_total_max: Option<u64>,
88 throttling_iops_read_max: Option<u64>,
89 throttling_iops_write_max: Option<u64>,
90
91 throttling_iops_size: Option<u64>,
93}
94
95#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
97pub struct FsDevSynth {
98 id: String,
100 readonly: Option<()>,
102 max_xattr: Option<u64>,
104}
105
106#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
108pub enum FsDev {
109 Local(Box<FsDevLocal>),
110 Synth(FsDevSynth),
111}
112
113impl ToCommand for FsDev {
114 fn command(&self) -> String {
115 ARG_FSDEV.to_string()
116 }
117
118 fn to_args(&self) -> Vec<String> {
119 let mut args = vec![];
120 match self {
121 FsDev::Local(local) => {
122 args.push("local".to_string());
123 args.push(format!("id={}", local.id));
124 args.push(format!("path={}", local.path.display()));
125 args.push(format!("security_model={}", local.security_model.to_arg()));
126
127 if local.writeout.is_some() {
128 args.push("writeout=immediate".to_string());
129 }
130 if local.readonly.is_some() {
131 args.push("readonly=on".to_string());
132 }
133 if let Some(fmode) = &local.fmode {
134 args.push(format!("fmode={}", fmode));
135 }
136 if let Some(dmode) = &local.dmode {
137 args.push(format!("dmode={}", dmode));
138 }
139 if let Some(max_xattr) = local.max_xattr {
140 args.push(format!("max_xattr={}", max_xattr));
141 }
142 if let Some(v) = local.throttling_bps_total {
143 args.push(format!("throttling.bps-total={}", v));
144 }
145 if let Some(v) = local.throttling_bps_read {
146 args.push(format!("throttling.bps-read={}", v));
147 }
148 if let Some(v) = local.throttling_bps_write {
149 args.push(format!("throttling.bps-write={}", v));
150 }
151 if let Some(v) = local.throttling_bps_total_max {
152 args.push(format!("throttling.bps-total-max={}", v));
153 }
154 if let Some(v) = local.bps_read_max {
155 args.push(format!("throttling.bps-read-max={}", v));
156 }
157 if let Some(v) = local.bps_write_max {
158 args.push(format!("throttling.bps-write-max={}", v));
159 }
160 if let Some(v) = local.throttling_iops_total {
161 args.push(format!("throttling.iops-total={}", v));
162 }
163 if let Some(v) = local.throttling_iops_read {
164 args.push(format!("throttling.iops-read={}", v));
165 }
166 if let Some(v) = local.throttling_iops_write {
167 args.push(format!("throttling.iops-write={}", v));
168 }
169 if let Some(v) = local.throttling_iops_total_max {
170 args.push(format!("throttling.iops-total-max={}", v));
171 }
172 if let Some(v) = local.throttling_iops_read_max {
173 args.push(format!("throttling.iops-read-max={}", v));
174 }
175 if let Some(v) = local.throttling_iops_write_max {
176 args.push(format!("throttling.iops-write-max={}", v));
177 }
178 if let Some(v) = local.throttling_iops_size {
179 args.push(format!("throttling.iops-size={}", v));
180 }
181 }
182 FsDev::Synth(synth) => {
183 args.push("synth".to_string());
184 args.push(format!("id={}", synth.id));
185 if synth.readonly.is_some() {
186 args.push("readonly=on".to_string());
187 }
188 if let Some(max_xattr) = synth.max_xattr {
189 args.push(format!("max_xattr={}", max_xattr));
190 }
191 }
192 }
193
194 vec![args.join(DELIM_COMMA)]
195 }
196}
197
198impl FromStr for FsDev {
199 type Err = String;
200
201 fn from_str(s: &str) -> Result<Self, Self::Err> {
202 let mut parts = s.split(DELIM_COMMA);
203 let backend = parts.next().ok_or_else(|| "empty fsdev argument".to_string())?;
204 match backend {
205 "local" => parse_local_fsdev(parts.collect()),
206 "synth" => parse_synth_fsdev(parts.collect()),
207 other => Err(format!("unsupported fsdev backend: {other}")),
208 }
209 }
210}
211
212fn parse_local_fsdev(parts: Vec<&str>) -> Result<FsDev, String> {
213 let mut id = None;
214 let mut path = None;
215 let mut security_model = None;
216 let mut writeout = None;
217 let mut readonly = None;
218 let mut fmode = None;
219 let mut dmode = None;
220 let mut max_xattr = None;
221 let mut throttling_bps_total = None;
222 let mut throttling_bps_read = None;
223 let mut throttling_bps_write = None;
224 let mut throttling_bps_total_max = None;
225 let mut bps_read_max = None;
226 let mut bps_write_max = None;
227 let mut throttling_iops_total = None;
228 let mut throttling_iops_read = None;
229 let mut throttling_iops_write = None;
230 let mut throttling_iops_total_max = None;
231 let mut throttling_iops_read_max = None;
232 let mut throttling_iops_write_max = None;
233 let mut throttling_iops_size = None;
234
235 for part in parts {
236 let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid fsdev local option: {part}"))?;
237 match key {
238 "id" => id = Some(value.to_string()),
239 "path" => path = Some(PathBuf::from(value)),
240 "security_model" => security_model = Some(value.parse::<SecurityModel>()?),
241 "writeout" => {
242 if value != "immediate" {
243 return Err(format!("invalid writeout value: {value}"));
244 }
245 writeout = Some(());
246 }
247 "readonly" => {
248 if value != "on" {
249 return Err(format!("invalid readonly value: {value}"));
250 }
251 readonly = Some(());
252 }
253 "fmode" => fmode = Some(value.to_string()),
254 "dmode" => dmode = Some(value.to_string()),
255 "max_xattr" => max_xattr = Some(value.parse::<u64>().map_err(|e| e.to_string())?),
256 "throttling.bps-total" => throttling_bps_total = Some(value.parse::<u64>().map_err(|e| e.to_string())?),
257 "throttling.bps-read" => throttling_bps_read = Some(value.parse::<u64>().map_err(|e| e.to_string())?),
258 "throttling.bps-write" => throttling_bps_write = Some(value.parse::<u64>().map_err(|e| e.to_string())?),
259 "throttling.bps-total-max" => throttling_bps_total_max = Some(value.parse::<u64>().map_err(|e| e.to_string())?),
260 "throttling.bps-read-max" => bps_read_max = Some(value.parse::<u64>().map_err(|e| e.to_string())?),
261 "throttling.bps-write-max" => bps_write_max = Some(value.parse::<u64>().map_err(|e| e.to_string())?),
262 "throttling.iops-total" => throttling_iops_total = Some(value.parse::<u64>().map_err(|e| e.to_string())?),
263 "throttling.iops-read" => throttling_iops_read = Some(value.parse::<u64>().map_err(|e| e.to_string())?),
264 "throttling.iops-write" => throttling_iops_write = Some(value.parse::<u64>().map_err(|e| e.to_string())?),
265 "throttling.iops-total-max" => throttling_iops_total_max = Some(value.parse::<u64>().map_err(|e| e.to_string())?),
266 "throttling.iops-read-max" => throttling_iops_read_max = Some(value.parse::<u64>().map_err(|e| e.to_string())?),
267 "throttling.iops-write-max" => throttling_iops_write_max = Some(value.parse::<u64>().map_err(|e| e.to_string())?),
268 "throttling.iops-size" => throttling_iops_size = Some(value.parse::<u64>().map_err(|e| e.to_string())?),
269 other => return Err(format!("unsupported fsdev local option: {other}")),
270 }
271 }
272
273 Ok(FsDev::Local(Box::new(FsDevLocal {
274 id: id.ok_or_else(|| "fsdev local requires id=".to_string())?,
275 path: path.ok_or_else(|| "fsdev local requires path=".to_string())?,
276 security_model: security_model.ok_or_else(|| "fsdev local requires security_model=".to_string())?,
277 writeout,
278 readonly,
279 fmode,
280 dmode,
281 max_xattr,
282 throttling_bps_total,
283 throttling_bps_read,
284 throttling_bps_write,
285 throttling_bps_total_max,
286 bps_read_max,
287 bps_write_max,
288 throttling_iops_total,
289 throttling_iops_read,
290 throttling_iops_write,
291 throttling_iops_total_max,
292 throttling_iops_read_max,
293 throttling_iops_write_max,
294 throttling_iops_size,
295 })))
296}
297
298fn parse_synth_fsdev(parts: Vec<&str>) -> Result<FsDev, String> {
299 let mut id = None;
300 let mut readonly = None;
301 let mut max_xattr = None;
302
303 for part in parts {
304 let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid fsdev synth option: {part}"))?;
305 match key {
306 "id" => id = Some(value.to_string()),
307 "readonly" => {
308 if value != "on" {
309 return Err(format!("invalid readonly value: {value}"));
310 }
311 readonly = Some(());
312 }
313 "max_xattr" => max_xattr = Some(value.parse::<u64>().map_err(|e| e.to_string())?),
314 other => return Err(format!("unsupported fsdev synth option: {other}")),
315 }
316 }
317
318 Ok(FsDev::Synth(FsDevSynth {
319 id: id.ok_or_else(|| "fsdev synth requires id=".to_string())?,
320 readonly,
321 max_xattr,
322 }))
323}