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}