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}