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