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}