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