Skip to main content

qemu_command_builder/args/
virtfs.rs

1use crate::args::fsdev::SecurityModel;
2use crate::parsers::{ARG_VIRTFS, DELIM_COMMA};
3use crate::to_command::{ToArg, ToCommand};
4use bon::Builder;
5use proptest_derive::Arbitrary;
6use std::path::PathBuf;
7use std::str::FromStr;
8
9#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
10pub enum RemapForbidWarn {
11    Remap,
12    Forbid,
13    Warn,
14}
15
16impl ToArg for RemapForbidWarn {
17    fn to_arg(&self) -> &str {
18        match self {
19            RemapForbidWarn::Remap => "remap",
20            RemapForbidWarn::Forbid => "forbid",
21            RemapForbidWarn::Warn => "warn",
22        }
23    }
24}
25
26#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
27pub struct Local {
28    path: PathBuf,
29    mount_tag: String,
30    security_mode: SecurityModel,
31    id: Option<String>,
32    writeout: Option<()>,
33    readonly: Option<bool>,
34    fmode: Option<String>,
35    dmode: Option<String>,
36    multidevs: Option<RemapForbidWarn>,
37}
38
39#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
40pub struct Synth {
41    mount_tag: String,
42    id: Option<String>,
43    readonly: Option<bool>,
44}
45
46#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
47pub enum Virtfs {
48    Local(Local),
49    Synth(Synth),
50}
51impl ToCommand for Virtfs {
52    fn command(&self) -> String {
53        ARG_VIRTFS.to_string()
54    }
55    fn to_args(&self) -> Vec<String> {
56        let mut args = vec![];
57
58        match self {
59            Virtfs::Local(local) => {
60                args.push("local".to_string());
61                args.push(format!("path={}", local.path.display()));
62                args.push(format!("mount_tag={}", local.mount_tag));
63                args.push(format!("security_model={}", local.security_mode.to_arg()));
64                if let Some(id) = &local.id {
65                    args.push(format!("id={}", id));
66                }
67                if local.writeout.is_some() {
68                    args.push("writeout=immediate".to_string());
69                }
70                if let Some(readonly) = &local.readonly
71                    && *readonly
72                {
73                    args.push("readonly=on".to_string());
74                }
75                if let Some(fmode) = &local.fmode {
76                    args.push(format!("fmode={}", fmode));
77                }
78                if let Some(dmode) = &local.dmode {
79                    args.push(format!("dmode={}", dmode));
80                }
81                if let Some(multidevs) = &local.multidevs {
82                    args.push(format!("multidevs={}", multidevs.to_arg()));
83                }
84            }
85            Virtfs::Synth(synth) => {
86                args.push("synth".to_string());
87                args.push(format!("mount_tag={}", synth.mount_tag));
88                if let Some(id) = &synth.id {
89                    args.push(format!("id={}", id));
90                }
91                if let Some(readonly) = &synth.readonly
92                    && *readonly
93                {
94                    args.push("readonly=on".to_string());
95                }
96            }
97        }
98
99        vec![args.join(DELIM_COMMA)]
100    }
101}
102
103impl FromStr for Virtfs {
104    type Err = String;
105
106    fn from_str(s: &str) -> Result<Self, Self::Err> {
107        let mut parts = s.split(DELIM_COMMA);
108        let backend = parts.next().ok_or_else(|| "empty -virtfs argument".to_string())?;
109        match backend {
110            "local" => {
111                let mut path = None;
112                let mut mount_tag = None;
113                let mut security_mode = None;
114                let mut id = None;
115                let mut writeout = None;
116                let mut readonly = None;
117                let mut fmode = None;
118                let mut dmode = None;
119                let mut multidevs = None;
120
121                for part in parts {
122                    let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid virtfs local option: {part}"))?;
123                    match key {
124                        "path" => path = Some(PathBuf::from(value)),
125                        "mount_tag" => mount_tag = Some(value.to_string()),
126                        "security_model" => security_mode = Some(value.parse::<SecurityModel>().map_err(|e| e.to_string())?),
127                        "id" => id = Some(value.to_string()),
128                        "writeout" => {
129                            if value != "immediate" {
130                                return Err(format!("invalid writeout value: {value}"));
131                            }
132                            writeout = Some(());
133                        }
134                        "readonly" => {
135                            if value != "on" {
136                                return Err(format!("invalid readonly value: {value}"));
137                            }
138                            readonly = Some(true);
139                        }
140                        "fmode" => fmode = Some(value.to_string()),
141                        "dmode" => dmode = Some(value.to_string()),
142                        "multidevs" => {
143                            multidevs = Some(match value {
144                                "remap" => RemapForbidWarn::Remap,
145                                "forbid" => RemapForbidWarn::Forbid,
146                                "warn" => RemapForbidWarn::Warn,
147                                _ => return Err(format!("invalid multidevs value: {value}")),
148                            })
149                        }
150                        other => return Err(format!("unsupported virtfs local option: {other}")),
151                    }
152                }
153
154                Ok(Self::Local(Local {
155                    path: path.ok_or_else(|| "virtfs local requires path=".to_string())?,
156                    mount_tag: mount_tag.ok_or_else(|| "virtfs local requires mount_tag=".to_string())?,
157                    security_mode: security_mode.ok_or_else(|| "virtfs local requires security_model=".to_string())?,
158                    id,
159                    writeout,
160                    readonly,
161                    fmode,
162                    dmode,
163                    multidevs,
164                }))
165            }
166            "synth" => {
167                let mut mount_tag = None;
168                let mut id = None;
169                let mut readonly = None;
170                for part in parts {
171                    let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid virtfs synth option: {part}"))?;
172                    match key {
173                        "mount_tag" => mount_tag = Some(value.to_string()),
174                        "id" => id = Some(value.to_string()),
175                        "readonly" => {
176                            if value != "on" {
177                                return Err(format!("invalid readonly value: {value}"));
178                            }
179                            readonly = Some(true);
180                        }
181                        other => return Err(format!("unsupported virtfs synth option: {other}")),
182                    }
183                }
184                Ok(Self::Synth(Synth {
185                    mount_tag: mount_tag.ok_or_else(|| "virtfs synth requires mount_tag=".to_string())?,
186                    id,
187                    readonly,
188                }))
189            }
190            other => Err(format!("unsupported virtfs backend: {other}")),
191        }
192    }
193}