qemu_command_builder/drive.rs
1use std::path::PathBuf;
2
3use bon::Builder;
4
5use crate::common::{IgnoreUnmap, OnOff, OnOffUnmap};
6use crate::to_command::{ToArg, ToCommand};
7
8#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
9pub enum DriveInterface {
10 Ide,
11 Scsi,
12 Sd,
13 Mtd,
14 Floppy,
15 Pflash,
16 Virtio,
17 None,
18}
19
20impl ToArg for DriveInterface {
21 fn to_arg(&self) -> &str {
22 match self {
23 DriveInterface::Ide => "ide",
24 DriveInterface::Scsi => "scsi",
25 DriveInterface::Sd => "sd",
26 DriveInterface::Mtd => "mtd",
27 DriveInterface::Floppy => "floppy",
28 DriveInterface::Pflash => "pflash",
29 DriveInterface::Virtio => "virtio",
30 DriveInterface::None => "none",
31 }
32 }
33}
34
35#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
36pub enum DriveMedia {
37 Disk,
38 Cdrom,
39}
40
41impl ToArg for DriveMedia {
42 fn to_arg(&self) -> &str {
43 match self {
44 DriveMedia::Disk => "disk",
45 DriveMedia::Cdrom => "cdrom",
46 }
47 }
48}
49
50#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default)]
51pub enum DriveCacheType {
52 None,
53 #[default]
54 Writeback,
55 Writethrough,
56 Unsafe,
57 Directsync,
58}
59
60impl ToArg for DriveCacheType {
61 fn to_arg(&self) -> &str {
62 match self {
63 DriveCacheType::None => "none",
64 DriveCacheType::Writeback => "writeback",
65 DriveCacheType::Writethrough => "unsafe",
66 DriveCacheType::Unsafe => "directsync",
67 DriveCacheType::Directsync => "writethrough",
68 }
69 }
70}
71
72#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
73pub enum DriveAIOType {
74 Threads,
75 Native,
76 IOUring,
77}
78
79impl ToArg for DriveAIOType {
80 fn to_arg(&self) -> &str {
81 match self {
82 DriveAIOType::Threads => "threads",
83 DriveAIOType::Native => "native",
84 DriveAIOType::IOUring => "io_uring",
85 }
86 }
87}
88
89#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
90pub enum DriveErrorAction {
91 Ignore,
92 Stop,
93 Report,
94 Enospc,
95}
96
97impl ToArg for DriveErrorAction {
98 fn to_arg(&self) -> &str {
99 match self {
100 DriveErrorAction::Ignore => "ignore",
101 DriveErrorAction::Stop => "stop",
102 DriveErrorAction::Report => "report",
103 DriveErrorAction::Enospc => "enospc",
104 }
105 }
106}
107
108/// Define a new drive. This includes creating a block driver node (the
109/// backend) as well as a guest device, and is mostly a shortcut for
110/// defining the corresponding ``-blockdev`` and ``-device`` options.
111///
112/// ``-drive`` accepts all options that are accepted by ``-blockdev``.
113#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder)]
114pub struct Drive {
115 // -blockdev
116 /// This defines the name of the block driver node by which it
117 /// will be referenced later. The name must be unique, i.e. it
118 /// must not match the name of a different block driver node, or
119 /// (if you use ``-drive`` as well) the ID of a drive.
120 ///
121 /// If no node name is specified, it is automatically generated.
122 /// The generated node name is not intended to be predictable
123 /// and changes between QEMU invocations. For the top level, an
124 /// explicit node name must be specified.
125 pub node_name: Option<String>,
126
127 /// discard is one of "ignore" (or "off") or "unmap" (or "on")
128 /// and controls whether ``discard`` (also known as ``trim`` or
129 /// ``unmap``) requests are ignored or passed to the filesystem.
130 /// Some machine types may not support discard requests.
131 pub discard: Option<IgnoreUnmap>,
132
133 /// The host page cache can be avoided with ``cache.direct=on``.
134 /// This will attempt to do disk IO directly to the guest's
135 /// memory. QEMU may still perform an internal copy of the data.
136 pub cache_direct: Option<OnOff>,
137
138 /// In case you don't care about data integrity over host
139 /// failures, you can use ``cache.no-flush=on``. This option
140 /// tells QEMU that it never needs to write any data to the disk
141 /// but can instead keep things in cache. If anything goes
142 /// wrong, like your host losing power, the disk storage getting
143 /// disconnected accidentally, etc. your image will most
144 /// probably be rendered unusable.
145 pub cache_no_flush: Option<OnOff>,
146
147 /// Open the node read-only. Guest write attempts will fail.
148 ///
149 /// Note that some block drivers support only read-only access,
150 /// either generally or in certain configurations. In this case,
151 /// the default value ``read-only=off`` does not work and the
152 /// option must be specified explicitly.
153 pub read_only: Option<OnOff>,
154
155 /// If ``auto-read-only=on`` is set, QEMU may fall back to
156 /// read-only usage even when ``read-only=off`` is requested, or
157 /// even switch between modes as needed, e.g. depending on
158 /// whether the image file is writable or whether a writing user
159 /// is attached to the node.
160 pub auto_read_only: Option<OnOff>,
161
162 /// Override the image locking system of QEMU by forcing the
163 /// node to utilize weaker shared access for permissions where
164 /// it would normally request exclusive access. When there is
165 /// the potential for multiple instances to have the same file
166 /// open (whether this invocation of QEMU is the first or the
167 /// second instance), both instances must permit shared access
168 /// for the second instance to succeed at opening the file.
169 ///
170 /// Enabling ``force-share=on`` requires ``read-only=on``.
171 pub force_share: Option<OnOff>,
172
173 /// detect-zeroes is "off", "on" or "unmap" and enables the
174 /// automatic conversion of plain zero writes by the OS to
175 /// driver specific optimized zero write commands. You may even
176 /// choose "unmap" if discard is set to "unmap" to allow a zero
177 /// write to be converted to an ``unmap`` operation.
178 pub detect_zeroes: Option<OnOffUnmap>,
179
180 // -drive
181 /// This option defines which disk image (see the :ref:`disk images`
182 /// chapter in the System Emulation Users Guide) to use with this drive.
183 /// If the filename contains comma, you must double it (for instance,
184 /// "file=my,,file" to use file "my,file").
185 ///
186 /// Special files such as iSCSI devices can be specified using
187 /// protocol specific URLs. See the section for "Device URL Syntax"
188 /// for more information.
189 pub file: Option<PathBuf>,
190
191 /// This option defines on which type on interface the drive is
192 /// connected. Available types are: ide, scsi, sd, mtd, floppy,
193 /// pflash, virtio, none.
194 pub interface: Option<DriveInterface>,
195
196 /// These options define where is connected the drive by defining
197 /// the bus number and the unit id.
198 pub bus: Option<usize>,
199 pub unit: Option<usize>,
200
201 /// This option defines where the drive is connected by using an
202 /// index in the list of available connectors of a given interface
203 /// type.
204 pub index: Option<String>,
205
206 /// This option defines the type of the media: disk or cdrom.
207 pub media: Option<DriveMedia>,
208
209 /// snapshot is "on" or "off" and controls snapshot mode for the
210 /// given drive (see ``-snapshot``).
211 pub snapshot: Option<OnOff>,
212
213 /// cache is "none", "writeback", "unsafe", "directsync" or
214 /// "writethrough" and controls how the host cache is used to access
215 /// block data. This is a shortcut that sets the ``cache.direct``
216 /// and ``cache.no-flush`` options (as in ``-blockdev``), and
217 /// additionally ``cache.writeback``, which provides a default for
218 /// the ``write-cache`` option of block guest devices (as in
219 /// ``-device``). The modes correspond to the following settings:
220 ///
221 /// ============= =============== ============ ==============
222 /// \ cache.writeback cache.direct cache.no-flush
223 /// ============= =============== ============ ==============
224 /// writeback on off off
225 /// none on on off
226 /// writethrough off off off
227 /// directsync off on off
228 /// unsafe on off on
229 /// ============= =============== ============ ==============
230 ///
231 /// The default mode is ``cache=writeback``.
232 pub cache: Option<DriveCacheType>,
233
234 pub id: Option<String>,
235
236 /// aio is "threads", "native", or "io_uring" and selects between pthread
237 /// based disk I/O, native Linux AIO, or Linux io_uring API.
238 pub aio: Option<DriveAIOType>,
239
240 /// Specify which disk format will be used rather than detecting the
241 /// format. Can be used to specify format=raw to avoid interpreting
242 /// an untrusted format header.
243 pub format: Option<String>,
244
245 /// Specify which action to take on write and read errors. Valid
246 /// actions are: "ignore" (ignore the error and try to continue),
247 /// "stop" (pause QEMU), "report" (report the error to the guest),
248 /// "enospc" (pause QEMU only if the host disk is full; report the
249 /// error to the guest otherwise). The default setting is
250 /// ``werror=enospc`` and ``rerror=report``.
251 pub rerror: Option<DriveErrorAction>,
252 pub werror: Option<DriveErrorAction>,
253
254 /// copy-on-read is "on" or "off" and enables whether to copy read
255 /// backing file sectors into the image file.
256 pub copy_on_ready: Option<OnOff>,
257
258 /// Specify bandwidth throttling limits in bytes per second, either
259 /// for all request types or for reads or writes only. Small values
260 /// can lead to timeouts or hangs inside the guest. A safe minimum
261 /// for disks is 2 MB/s.
262 pub bps: Option<usize>,
263 pub bps_rd: Option<usize>,
264 pub bps_wr: Option<usize>,
265
266 /// Specify bursts in bytes per second, either for all request types
267 /// or for reads or writes only. Bursts allow the guest I/O to spike
268 /// above the limit temporarily.
269 pub bps_max: Option<usize>,
270 pub bps_rd_max: Option<usize>,
271 pub bps_wr_max: Option<usize>,
272
273 /// Specify request rate limits in requests per second, either for
274 /// all request types or for reads or writes only.
275 pub iops: Option<usize>,
276 pub iops_rd: Option<usize>,
277 pub iops_wr: Option<usize>,
278
279 /// Specify bursts in requests per second, either for all request
280 /// types or for reads or writes only. Bursts allow the guest I/O to
281 /// spike above the limit temporarily.
282 pub iops_max: Option<usize>,
283 pub iops_rd_max: Option<usize>,
284 pub iops_wr_max: Option<usize>,
285
286 /// Let every is bytes of a request count as a new request for iops
287 /// throttling purposes. Use this option to prevent guests from
288 /// circumventing iops limits by sending fewer but larger requests.
289 pub iops_size: Option<usize>,
290
291 /// Join a throttling quota group with given name g. All drives that
292 /// are members of the same group are accounted for together. Use
293 /// this option to prevent guests from circumventing throttling
294 /// limits by using many small disks instead of a single larger
295 /// disk.
296 pub group: Option<String>,
297}
298
299impl ToCommand for Drive {
300 fn to_command(&self) -> Vec<String> {
301 let mut cmd = vec![];
302
303 cmd.push("-drive".to_string());
304
305 let mut args = vec![];
306
307 // -drive
308 if let Some(file) = &self.file {
309 args.push(format!("file={}", file.display()));
310 }
311 if let Some(interface) = &self.interface {
312 args.push(format!("if={}", interface.to_arg()));
313 }
314 if let Some(bus) = &self.bus {
315 args.push(format!("bus={}", bus));
316 }
317 if let Some(unit) = &self.unit {
318 args.push(format!("unit={}", unit));
319 }
320 if let Some(index) = &self.index {
321 args.push(format!("index={}", index));
322 }
323 if let Some(media) = &self.media {
324 args.push(format!("media={}", media.to_arg()));
325 }
326 if let Some(snapshot) = &self.snapshot {
327 args.push(format!("snapshot={}", snapshot.to_arg()));
328 }
329 if let Some(cache) = &self.cache {
330 args.push(format!("cache={}", cache.to_arg()));
331 }
332 if let Some(id) = &self.id {
333 args.push(format!("id={}", id));
334 }
335 if let Some(aio) = &self.aio {
336 args.push(format!("aio={}", aio.to_arg()));
337 }
338 if let Some(format) = &self.format {
339 args.push(format!("format={}", format));
340 }
341 if let Some(rerror) = &self.rerror {
342 args.push(format!("rerror={}", rerror.to_arg()));
343 }
344 if let Some(werror) = &self.werror {
345 args.push(format!("werror={}", werror.to_arg()));
346 }
347 if let Some(copy_on_ready) = &self.copy_on_ready {
348 args.push(format!("copy-on-ready={}", copy_on_ready.to_arg()));
349 }
350 if let Some(bps) = &self.bps {
351 args.push(format!("bps={}", bps));
352 }
353 if let Some(bps_rd) = &self.bps_rd {
354 args.push(format!("bps_rd={}", bps_rd));
355 }
356 if let Some(bps_wr) = &self.bps_wr {
357 args.push(format!("bps_wr={}", bps_wr));
358 }
359 if let Some(bps_max) = &self.bps_max {
360 args.push(format!("bps_max={}", bps_max));
361 }
362 if let Some(bps_rd_max) = &self.bps_rd_max {
363 args.push(format!("bps_rd_max={}", bps_rd_max));
364 }
365 if let Some(bps_wr_max) = &self.bps_wr_max {
366 args.push(format!("bps_wr_max={}", bps_wr_max));
367 }
368 if let Some(iops) = self.iops {
369 args.push(format!("iops={}", iops));
370 }
371 if let Some(iops_rd) = &self.iops_rd {
372 args.push(format!("iops_rd={}", iops_rd));
373 }
374 if let Some(iops_wr) = &self.iops_wr {
375 args.push(format!("iops_wr={}", iops_wr));
376 }
377 if let Some(iops_max) = &self.iops_max {
378 args.push(format!("iops_max={}", iops_max));
379 }
380 if let Some(iops_rd_max) = &self.iops_rd_max {
381 args.push(format!("iops_rd_max={}", iops_rd_max));
382 }
383 if let Some(iops_wr_max) = &self.iops_wr_max {
384 args.push(format!("iops_wr_max={}", iops_wr_max));
385 }
386 if let Some(iops_size) = &self.iops_size {
387 args.push(format!("iops_size={}", iops_size));
388 }
389 if let Some(group) = &self.group {
390 args.push(format!("group={}", group));
391 }
392
393 // -blockdev
394 if let Some(node_name) = &self.node_name {
395 args.push(format!("node-name={}", node_name));
396 }
397 if let Some(discard) = &self.discard {
398 args.push(format!("discard={}", discard.to_arg()));
399 }
400 if let Some(cache_direct) = &self.cache_direct {
401 args.push(format!("cache.direct={}", cache_direct.to_arg()));
402 }
403 if let Some(cache_no_flush) = &self.cache_no_flush {
404 args.push(format!("cache.no-flush={}", cache_no_flush.to_arg()));
405 }
406 if let Some(read_only) = &self.read_only {
407 args.push(format!("read-only={}", read_only.to_arg()));
408 }
409 if let Some(auto_read_only) = &self.auto_read_only {
410 args.push(format!("auto-read-only={}", auto_read_only.to_arg()));
411 }
412 if let Some(force_share) = &self.force_share {
413 args.push(format!("force-share={}", force_share.to_arg()));
414 }
415 if let Some(detect_zeroes) = &self.detect_zeroes {
416 args.push(format!("detect-zeroes={}", detect_zeroes.to_arg()));
417 }
418
419 cmd.push(args.join(","));
420
421 cmd
422 }
423}