Skip to main content

qemu_command_builder/args/
blockdev.rs

1use crate::parsers::ARG_BLOCKDEV;
2use std::collections::BTreeMap;
3use std::str::FromStr;
4
5use bon::Builder;
6use proptest_derive::Arbitrary;
7
8use crate::common::{IgnoreUnmap, OnOff, OnOffUnmap};
9use crate::to_command::{ToArg, ToCommand};
10
11/// Define a new block driver node. Some of the options apply to all
12/// block drivers, other options are only accepted for a specific block
13/// driver. See below for a list of generic options and options for the
14/// most common block drivers.
15///
16/// Options that expect a reference to another node (e.g. ``file``) can
17/// be given in two ways. Either you specify the node name of an already
18/// existing node (file=node-name), or you define a new node inline,
19/// adding options for the referenced node after a dot
20/// (file.filename=path,file.aio=native).
21///
22/// A block driver node created with ``-blockdev`` can be used for a
23/// guest device by specifying its node name for the ``drive`` property
24/// in a ``-device`` argument that defines a block device.
25#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
26pub struct BlockDev {
27    /// Specifies the block driver to use for the given node.
28    pub driver: String,
29
30    /// This defines the name of the block driver node by which it
31    /// will be referenced later. The name must be unique, i.e. it
32    /// must not match the name of a different block driver node, or
33    /// (if you use ``-drive`` as well) the ID of a drive.
34    ///
35    /// If no node name is specified, it is automatically generated.
36    /// The generated node name is not intended to be predictable
37    /// and changes between QEMU invocations. For the top level, an
38    /// explicit node name must be specified.
39    pub node_name: Option<String>,
40
41    /// discard is one of "ignore" (or "off") or "unmap" (or "on")
42    /// and controls whether ``discard`` (also known as ``trim`` or
43    /// ``unmap``) requests are ignored or passed to the filesystem.
44    /// Some machine types may not support discard requests.
45    pub discard: Option<IgnoreUnmap>,
46
47    /// The host page cache can be avoided with ``cache.direct=on``.
48    /// This will attempt to do disk IO directly to the guest's
49    /// memory. QEMU may still perform an internal copy of the data.
50    pub cache_direct: Option<OnOff>,
51
52    /// In case you don't care about data integrity over host
53    /// failures, you can use ``cache.no-flush=on``. This option
54    /// tells QEMU that it never needs to write any data to the disk
55    /// but can instead keep things in cache. If anything goes
56    /// wrong, like your host losing power, the disk storage getting
57    /// disconnected accidentally, etc. your image will most
58    /// probably be rendered unusable.
59    pub cache_no_flush: Option<OnOff>,
60
61    /// Open the node read-only. Guest write attempts will fail.
62    ///
63    /// Note that some block drivers support only read-only access,
64    /// either generally or in certain configurations. In this case,
65    /// the default value ``read-only=off`` does not work and the
66    /// option must be specified explicitly.
67    pub read_only: Option<OnOff>,
68
69    /// If ``auto-read-only=on`` is set, QEMU may fall back to
70    /// read-only usage even when ``read-only=off`` is requested, or
71    /// even switch between modes as needed, e.g. depending on
72    /// whether the image file is writable or whether a writing user
73    /// is attached to the node.
74    pub auto_read_only: Option<OnOff>,
75
76    /// Override the image locking system of QEMU by forcing the
77    /// node to utilize weaker shared access for permissions where
78    /// it would normally request exclusive access. When there is
79    /// the potential for multiple instances to have the same file
80    /// open (whether this invocation of QEMU is the first or the
81    /// second instance), both instances must permit shared access
82    /// for the second instance to succeed at opening the file.
83    ///
84    /// Enabling ``force-share=on`` requires ``read-only=on``.
85    pub force_share: Option<OnOff>,
86
87    /// detect-zeroes is "off", "on" or "unmap" and enables the
88    /// automatic conversion of plain zero writes by the OS to
89    /// driver specific optimized zero write commands. You may even
90    /// choose "unmap" if discard is set to "unmap" to allow a zero
91    /// write to be converted to an ``unmap`` operation.
92    pub detect_zeroes: Option<OnOffUnmap>,
93
94    /// Driver-specific options such as `filename=...` or `file.driver=...`.
95    ///
96    /// These are emitted after the generic blockdev options in sorted key
97    /// order so parsing and formatting remain stable.
98    pub driver_opts: Option<BTreeMap<String, String>>,
99    json: Option<String>,
100}
101
102impl BlockDev {
103    /// Creates a block driver node for the given `driver=...` value.
104    pub fn new(driver: impl Into<String>) -> Self {
105        Self {
106            driver: driver.into(),
107            node_name: None,
108            discard: None,
109            cache_direct: None,
110            cache_no_flush: None,
111            read_only: None,
112            auto_read_only: None,
113            force_share: None,
114            detect_zeroes: None,
115            driver_opts: None,
116            json: None,
117        }
118    }
119    /// Creates a block node from QEMU's JSON command-line form.
120    pub fn from_json(json: impl Into<String>) -> Result<Self, String> {
121        let json = json.into();
122        if !json.trim().starts_with('{') || !json.trim().ends_with('}') {
123            return Err("-blockdev JSON must be a JSON object".to_string());
124        }
125        let mut value = Self::new(String::new());
126        value.json = Some(json);
127        Ok(value)
128    }
129
130    /// Adds a driver-specific `key=value` option.
131    pub fn add_driver_opt<K: AsRef<str>, V: AsRef<str>>(&mut self, key: K, value: V) -> &mut Self {
132        self.driver_opts.get_or_insert_with(BTreeMap::new).insert(key.as_ref().to_string(), value.as_ref().to_string());
133        self
134    }
135}
136
137impl ToCommand for BlockDev {
138    fn command(&self) -> String {
139        ARG_BLOCKDEV.to_string()
140    }
141    fn to_args(&self) -> Vec<String> {
142        if let Some(json) = &self.json {
143            return vec![json.clone()];
144        }
145        let mut args = vec![];
146
147        args.push(format!("driver={}", self.driver));
148        if let Some(node_name) = &self.node_name {
149            args.push(format!("node-name={}", node_name));
150        }
151        if let Some(discard) = &self.discard {
152            args.push(format!("discard={}", discard.to_arg()));
153        }
154        if let Some(cache_direct) = &self.cache_direct {
155            args.push(format!("cache.direct={}", cache_direct.to_arg()));
156        }
157        if let Some(cache_no_flush) = &self.cache_no_flush {
158            args.push(format!("cache.no-flush={}", cache_no_flush.to_arg()));
159        }
160        if let Some(read_only) = &self.read_only {
161            args.push(format!("read-only={}", read_only.to_arg()));
162        }
163        if let Some(auto_read_only) = &self.auto_read_only {
164            args.push(format!("auto-read-only={}", auto_read_only.to_arg()));
165        }
166        if let Some(force_share) = &self.force_share {
167            args.push(format!("force-share={}", force_share.to_arg()));
168        }
169        if let Some(detect_zeroes) = &self.detect_zeroes {
170            args.push(format!("detect-zeroes={}", detect_zeroes.to_arg()));
171        }
172        if let Some(driver_opts) = &self.driver_opts {
173            for (key, val) in driver_opts {
174                args.push(format!("{}={}", key, val));
175            }
176        }
177
178        vec![args.join(",")]
179    }
180}
181
182impl FromStr for BlockDev {
183    type Err = String;
184
185    fn from_str(s: &str) -> Result<Self, Self::Err> {
186        if s.trim().starts_with('{') {
187            return Self::from_json(s);
188        }
189        let mut parts = s.split(',');
190        let first = parts.next().ok_or_else(|| "empty blockdev argument".to_string())?;
191        let driver = first.strip_prefix("driver=").unwrap_or(first).to_string();
192        if driver.is_empty() {
193            return Err("missing blockdev driver".to_string());
194        }
195
196        let mut blockdev = BlockDev::new(driver);
197        let mut driver_opts = BTreeMap::new();
198
199        for part in parts {
200            let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid blockdev option: {part}"))?;
201            match key {
202                "node-name" => blockdev.node_name = Some(value.to_string()),
203                "discard" => blockdev.discard = Some(value.parse::<IgnoreUnmap>().map_err(|_| format!("invalid discard value: {value}"))?),
204                "cache.direct" => blockdev.cache_direct = Some(value.parse::<OnOff>().map_err(|_| format!("invalid cache.direct value: {value}"))?),
205                "cache.no-flush" => blockdev.cache_no_flush = Some(value.parse::<OnOff>().map_err(|_| format!("invalid cache.no-flush value: {value}"))?),
206                "read-only" => blockdev.read_only = Some(value.parse::<OnOff>().map_err(|_| format!("invalid read-only value: {value}"))?),
207                "auto-read-only" => blockdev.auto_read_only = Some(value.parse::<OnOff>().map_err(|_| format!("invalid auto-read-only value: {value}"))?),
208                "force-share" => blockdev.force_share = Some(value.parse::<OnOff>().map_err(|_| format!("invalid force-share value: {value}"))?),
209                "detect-zeroes" => blockdev.detect_zeroes = Some(value.parse::<OnOffUnmap>().map_err(|_| format!("invalid detect-zeroes value: {value}"))?),
210                other => {
211                    driver_opts.insert(other.to_string(), value.to_string());
212                }
213            }
214        }
215
216        if !driver_opts.is_empty() {
217            blockdev.driver_opts = Some(driver_opts);
218        }
219
220        Ok(blockdev)
221    }
222}