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 throttling_bps_total: Option<usize>,
70 throttling_bps_read: Option<usize>,
71 throttling_bps_write: Option<usize>,
72
73 throttling_bps_total_max: Option<usize>,
75 bps_read_max: Option<usize>,
76 bps_write_max: Option<usize>,
77
78 throttling_iops_total: Option<usize>,
80 throttling_iops_read: Option<usize>,
81 throttling_iops_write: Option<usize>,
82
83 throttling_iops_total_max: Option<usize>,
85 throttling_iops_read_max: Option<usize>,
86 throttling_iops_write_max: Option<usize>,
87
88 throttling_iops_size: Option<usize>,
90}
91
92#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
94pub struct FsDevSynth {
95 id: String,
97 readonly: Option<()>,
99}
100
101#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
103pub enum FsDev {
104 Local(Box<FsDevLocal>),
105 Synth(FsDevSynth),
106}
107
108impl ToCommand for FsDev {
109 fn command(&self) -> String {
110 ARG_FSDEV.to_string()
111 }
112
113 fn to_args(&self) -> Vec<String> {
114 let mut args = vec![];
115 match self {
116 FsDev::Local(local) => {
117 args.push("local".to_string());
118 args.push(format!("id={}", local.id));
119 args.push(format!("path={}", local.path.display()));
120 args.push(format!("security_model={}", local.security_model.to_arg()));
121
122 if local.writeout.is_some() {
123 args.push("writeout=immediate".to_string());
124 }
125 if local.readonly.is_some() {
126 args.push("readonly=on".to_string());
127 }
128 if let Some(fmode) = &local.fmode {
129 args.push(format!("fmode={}", fmode));
130 }
131 if let Some(dmode) = &local.dmode {
132 args.push(format!("dmode={}", dmode));
133 }
134 if let Some(v) = local.throttling_bps_total {
135 args.push(format!("throttling.bps-total={}", v));
136 }
137 if let Some(v) = local.throttling_bps_read {
138 args.push(format!("throttling.bps-read={}", v));
139 }
140 if let Some(v) = local.throttling_bps_write {
141 args.push(format!("throttling.bps-write={}", v));
142 }
143 if let Some(v) = local.throttling_bps_total_max {
144 args.push(format!("throttling.bps-total-max={}", v));
145 }
146 if let Some(v) = local.bps_read_max {
147 args.push(format!("throttling.bps-read-max={}", v));
148 }
149 if let Some(v) = local.bps_write_max {
150 args.push(format!("throttling.bps-write-max={}", v));
151 }
152 if let Some(v) = local.throttling_iops_total {
153 args.push(format!("throttling.iops-total={}", v));
154 }
155 if let Some(v) = local.throttling_iops_read {
156 args.push(format!("throttling.iops-read={}", v));
157 }
158 if let Some(v) = local.throttling_iops_write {
159 args.push(format!("throttling.iops-write={}", v));
160 }
161 if let Some(v) = local.throttling_iops_total_max {
162 args.push(format!("throttling.iops-total-max={}", v));
163 }
164 if let Some(v) = local.throttling_iops_read_max {
165 args.push(format!("throttling.iops-read-max={}", v));
166 }
167 if let Some(v) = local.throttling_iops_write_max {
168 args.push(format!("throttling.iops-write-max={}", v));
169 }
170 if let Some(v) = local.throttling_iops_size {
171 args.push(format!("throttling.iops-size={}", v));
172 }
173 }
174 FsDev::Synth(synth) => {
175 args.push("synth".to_string());
176 args.push(format!("id={}", synth.id));
177 if synth.readonly.is_some() {
178 args.push("readonly=on".to_string());
179 }
180 }
181 }
182
183 vec![args.join(DELIM_COMMA)]
184 }
185}
186
187impl FromStr for FsDev {
188 type Err = String;
189
190 fn from_str(s: &str) -> Result<Self, Self::Err> {
191 let mut parts = s.split(DELIM_COMMA);
192 let backend = parts.next().ok_or_else(|| "empty fsdev argument".to_string())?;
193 match backend {
194 "local" => parse_local_fsdev(parts.collect()),
195 "synth" => parse_synth_fsdev(parts.collect()),
196 other => Err(format!("unsupported fsdev backend: {other}")),
197 }
198 }
199}
200
201fn parse_local_fsdev(parts: Vec<&str>) -> Result<FsDev, String> {
202 let mut id = None;
203 let mut path = None;
204 let mut security_model = None;
205 let mut writeout = None;
206 let mut readonly = None;
207 let mut fmode = None;
208 let mut dmode = None;
209 let mut throttling_bps_total = None;
210 let mut throttling_bps_read = None;
211 let mut throttling_bps_write = None;
212 let mut throttling_bps_total_max = None;
213 let mut bps_read_max = None;
214 let mut bps_write_max = None;
215 let mut throttling_iops_total = None;
216 let mut throttling_iops_read = None;
217 let mut throttling_iops_write = None;
218 let mut throttling_iops_total_max = None;
219 let mut throttling_iops_read_max = None;
220 let mut throttling_iops_write_max = None;
221 let mut throttling_iops_size = None;
222
223 for part in parts {
224 let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid fsdev local option: {part}"))?;
225 match key {
226 "id" => id = Some(value.to_string()),
227 "path" => path = Some(PathBuf::from(value)),
228 "security_model" => security_model = Some(value.parse::<SecurityModel>()?),
229 "writeout" => {
230 if value != "immediate" {
231 return Err(format!("invalid writeout value: {value}"));
232 }
233 writeout = Some(());
234 }
235 "readonly" => {
236 if value != "on" {
237 return Err(format!("invalid readonly value: {value}"));
238 }
239 readonly = Some(());
240 }
241 "fmode" => fmode = Some(value.to_string()),
242 "dmode" => dmode = Some(value.to_string()),
243 "throttling.bps-total" => throttling_bps_total = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
244 "throttling.bps-read" => throttling_bps_read = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
245 "throttling.bps-write" => throttling_bps_write = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
246 "throttling.bps-total-max" => throttling_bps_total_max = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
247 "throttling.bps-read-max" => bps_read_max = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
248 "throttling.bps-write-max" => bps_write_max = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
249 "throttling.iops-total" => throttling_iops_total = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
250 "throttling.iops-read" => throttling_iops_read = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
251 "throttling.iops-write" => throttling_iops_write = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
252 "throttling.iops-total-max" => throttling_iops_total_max = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
253 "throttling.iops-read-max" => throttling_iops_read_max = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
254 "throttling.iops-write-max" => throttling_iops_write_max = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
255 "throttling.iops-size" => throttling_iops_size = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
256 other => return Err(format!("unsupported fsdev local option: {other}")),
257 }
258 }
259
260 Ok(FsDev::Local(Box::new(FsDevLocal {
261 id: id.ok_or_else(|| "fsdev local requires id=".to_string())?,
262 path: path.ok_or_else(|| "fsdev local requires path=".to_string())?,
263 security_model: security_model.ok_or_else(|| "fsdev local requires security_model=".to_string())?,
264 writeout,
265 readonly,
266 fmode,
267 dmode,
268 throttling_bps_total,
269 throttling_bps_read,
270 throttling_bps_write,
271 throttling_bps_total_max,
272 bps_read_max,
273 bps_write_max,
274 throttling_iops_total,
275 throttling_iops_read,
276 throttling_iops_write,
277 throttling_iops_total_max,
278 throttling_iops_read_max,
279 throttling_iops_write_max,
280 throttling_iops_size,
281 })))
282}
283
284fn parse_synth_fsdev(parts: Vec<&str>) -> Result<FsDev, String> {
285 let mut id = None;
286 let mut readonly = None;
287
288 for part in parts {
289 let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid fsdev synth option: {part}"))?;
290 match key {
291 "id" => id = Some(value.to_string()),
292 "readonly" => {
293 if value != "on" {
294 return Err(format!("invalid readonly value: {value}"));
295 }
296 readonly = Some(());
297 }
298 other => return Err(format!("unsupported fsdev synth option: {other}")),
299 }
300 }
301
302 Ok(FsDev::Synth(FsDevSynth {
303 id: id.ok_or_else(|| "fsdev synth requires id=".to_string())?,
304 readonly,
305 }))
306}