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}
100
101impl BlockDev {
102    /// Creates a block driver node for the given `driver=...` value.
103    pub fn new(driver: impl Into<String>) -> Self {
104        Self {
105            driver: driver.into(),
106            node_name: None,
107            discard: None,
108            cache_direct: None,
109            cache_no_flush: None,
110            read_only: None,
111            auto_read_only: None,
112            force_share: None,
113            detect_zeroes: None,
114            driver_opts: None,
115        }
116    }
117
118    /// Adds a driver-specific `key=value` option.
119    pub fn add_driver_opt<K: AsRef<str>, V: AsRef<str>>(&mut self, key: K, value: V) -> &mut Self {
120        self.driver_opts.get_or_insert_with(BTreeMap::new).insert(key.as_ref().to_string(), value.as_ref().to_string());
121        self
122    }
123}
124
125impl ToCommand for BlockDev {
126    fn command(&self) -> String {
127        ARG_BLOCKDEV.to_string()
128    }
129    fn to_args(&self) -> Vec<String> {
130        let mut args = vec![];
131
132        args.push(format!("driver={}", self.driver));
133        if let Some(node_name) = &self.node_name {
134            args.push(format!("node-name={}", node_name));
135        }
136        if let Some(discard) = &self.discard {
137            args.push(format!("discard={}", discard.to_arg()));
138        }
139        if let Some(cache_direct) = &self.cache_direct {
140            args.push(format!("cache.direct={}", cache_direct.to_arg()));
141        }
142        if let Some(cache_no_flush) = &self.cache_no_flush {
143            args.push(format!("cache.no-flush={}", cache_no_flush.to_arg()));
144        }
145        if let Some(read_only) = &self.read_only {
146            args.push(format!("read-only={}", read_only.to_arg()));
147        }
148        if let Some(auto_read_only) = &self.auto_read_only {
149            args.push(format!("auto-read-only={}", auto_read_only.to_arg()));
150        }
151        if let Some(force_share) = &self.force_share {
152            args.push(format!("force-share={}", force_share.to_arg()));
153        }
154        if let Some(detect_zeroes) = &self.detect_zeroes {
155            args.push(format!("detect-zeroes={}", detect_zeroes.to_arg()));
156        }
157        if let Some(driver_opts) = &self.driver_opts {
158            for (key, val) in driver_opts {
159                args.push(format!("{}={}", key, val));
160            }
161        }
162
163        vec![args.join(",")]
164    }
165}
166
167impl FromStr for BlockDev {
168    type Err = String;
169
170    fn from_str(s: &str) -> Result<Self, Self::Err> {
171        let mut parts = s.split(',');
172        let first = parts.next().ok_or_else(|| "empty blockdev argument".to_string())?;
173        let driver = first.strip_prefix("driver=").unwrap_or(first).to_string();
174        if driver.is_empty() {
175            return Err("missing blockdev driver".to_string());
176        }
177
178        let mut blockdev = BlockDev::new(driver);
179        let mut driver_opts = BTreeMap::new();
180
181        for part in parts {
182            let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid blockdev option: {part}"))?;
183            match key {
184                "node-name" => blockdev.node_name = Some(value.to_string()),
185                "discard" => blockdev.discard = Some(value.parse::<IgnoreUnmap>().map_err(|_| format!("invalid discard value: {value}"))?),
186                "cache.direct" => blockdev.cache_direct = Some(value.parse::<OnOff>().map_err(|_| format!("invalid cache.direct value: {value}"))?),
187                "cache.no-flush" => blockdev.cache_no_flush = Some(value.parse::<OnOff>().map_err(|_| format!("invalid cache.no-flush value: {value}"))?),
188                "read-only" => blockdev.read_only = Some(value.parse::<OnOff>().map_err(|_| format!("invalid read-only value: {value}"))?),
189                "auto-read-only" => blockdev.auto_read_only = Some(value.parse::<OnOff>().map_err(|_| format!("invalid auto-read-only value: {value}"))?),
190                "force-share" => blockdev.force_share = Some(value.parse::<OnOff>().map_err(|_| format!("invalid force-share value: {value}"))?),
191                "detect-zeroes" => blockdev.detect_zeroes = Some(value.parse::<OnOffUnmap>().map_err(|_| format!("invalid detect-zeroes value: {value}"))?),
192                other => {
193                    driver_opts.insert(other.to_string(), value.to_string());
194                }
195            }
196        }
197
198        if !driver_opts.is_empty() {
199            blockdev.driver_opts = Some(driver_opts);
200        }
201
202        Ok(blockdev)
203    }
204}