Skip to main content

qemu_command_builder/args/
drive.rs

1use crate::parsers::ARG_DRIVE;
2use std::fmt::{Display, Formatter};
3use std::str::FromStr;
4
5use bon::Builder;
6use proptest_derive::Arbitrary;
7
8use crate::common::{IgnoreUnmap, OnOff, OnOffUnmap};
9use crate::parsers::DELIM_COMMA;
10use crate::qao;
11use crate::shell_path::ShellPath;
12use crate::shell_string::ShellString;
13use crate::to_command::{ToArg, ToCommand};
14
15const KEY_FILE: &str = "file=";
16const KEY_INTERFACE: &str = "if=";
17const KEY_BUS: &str = "bus=";
18const KEY_UNIT: &str = "unit=";
19const KEY_INDEX: &str = "index=";
20const KEY_MEDIA: &str = "media=";
21const KEY_SNAPSHOT: &str = "snapshot=";
22const KEY_CACHE: &str = "cache=";
23const KEY_CACHE_DIRECT: &str = "cache.direct=";
24const KEY_ID: &str = "id=";
25const KEY_AIO: &str = "aio=";
26const KEY_FORMAT: &str = "format=";
27const KEY_ENCRYPT_FORMAT: &str = "encrypt.format=";
28const KEY_ENCRYPT_KEY_SECRET: &str = "encrypt.key-secret=";
29const KEY_RERROR: &str = "rerror=";
30const KEY_WERROR: &str = "werror=";
31const KEY_COPY_ON_READ: &str = "copy-on-read=";
32const KEY_BPS: &str = "bps=";
33const KEY_BPS_RD: &str = "bps_rd=";
34const KEY_BPS_WR: &str = "bps_wr=";
35const KEY_BPS_MAX: &str = "bps_max=";
36const KEY_BPS_RD_MAX: &str = "bps_rd_max=";
37const KEY_BPS_WR_MAX: &str = "bps_wr_max=";
38const KEY_IOPS: &str = "iops=";
39const KEY_IOPS_RD: &str = "iops_rd=";
40const KEY_IOPS_WR: &str = "iops_wr=";
41const KEY_IOPS_MAX: &str = "iops_max=";
42const KEY_IOPS_RD_MAX: &str = "iops_rd_max=";
43const KEY_IOPS_WR_MAX: &str = "iops_wr_max=";
44const KEY_IOPS_SIZE: &str = "iops_size=";
45const KEY_GROUP: &str = "group=";
46const KEY_THROTTLING_BPS_TOTAL: &str = "throttling.bps-total=";
47const KEY_THROTTLING_BPS_TOTAL_MAX: &str = "throttling.bps-total-max=";
48const KEY_THROTTLING_BPS_TOTAL_MAX_LENGTH: &str = "throttling.bps-total-max-length=";
49const KEY_THROTTLING_IOPS_TOTAL: &str = "throttling.iops-total=";
50const KEY_THROTTLING_IOPS_TOTAL_MAX: &str = "throttling.iops-total-max=";
51const KEY_THROTTLING_IOPS_TOTAL_MAX_LENGTH: &str = "throttling.iops-total-max-length=";
52const KEY_DISCARD: &str = "discard=";
53const KEY_READ_ONLY: &str = "readonly=";
54const KEY_AUTO_READ_ONLY: &str = "auto-read-only=";
55const KEY_DETECT_ZEROES: &str = "detect-zeroes=";
56
57#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
58pub enum DriveInterface {
59    Ide,
60    Scsi,
61    Sd,
62    Mtd,
63    Floppy,
64    Pflash,
65    Virtio,
66    None,
67}
68
69impl Display for DriveInterface {
70    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
71        match self {
72            DriveInterface::Ide => f.write_str("ide"),
73            DriveInterface::Scsi => f.write_str("scsi"),
74            DriveInterface::Sd => f.write_str("sd"),
75            DriveInterface::Mtd => f.write_str("mtd"),
76            DriveInterface::Floppy => f.write_str("floppy"),
77            DriveInterface::Pflash => f.write_str("pflash"),
78            DriveInterface::Virtio => f.write_str("virtio"),
79            DriveInterface::None => f.write_str("none"),
80        }
81    }
82}
83
84impl FromStr for DriveInterface {
85    type Err = ();
86    fn from_str(s: &str) -> Result<Self, Self::Err> {
87        match s {
88            "ide" => Ok(DriveInterface::Ide),
89            "scsi" => Ok(DriveInterface::Scsi),
90            "sd" => Ok(DriveInterface::Sd),
91            "mtd" => Ok(DriveInterface::Mtd),
92            "floppy" => Ok(DriveInterface::Floppy),
93            "pflash" => Ok(DriveInterface::Pflash),
94            "virtio" => Ok(DriveInterface::Virtio),
95            "none" => Ok(DriveInterface::None),
96            _ => Err(()),
97        }
98    }
99}
100impl ToArg for DriveInterface {
101    fn to_arg(&self) -> &str {
102        match self {
103            DriveInterface::Ide => "ide",
104            DriveInterface::Scsi => "scsi",
105            DriveInterface::Sd => "sd",
106            DriveInterface::Mtd => "mtd",
107            DriveInterface::Floppy => "floppy",
108            DriveInterface::Pflash => "pflash",
109            DriveInterface::Virtio => "virtio",
110            DriveInterface::None => "none",
111        }
112    }
113}
114
115#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
116pub enum DriveMedia {
117    Disk,
118    Cdrom,
119}
120
121impl Display for DriveMedia {
122    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
123        match self {
124            DriveMedia::Disk => f.write_str("disk"),
125            DriveMedia::Cdrom => f.write_str("cdrom"),
126        }
127    }
128}
129impl FromStr for DriveMedia {
130    type Err = ();
131    fn from_str(s: &str) -> Result<Self, Self::Err> {
132        match s {
133            "disk" => Ok(DriveMedia::Disk),
134            "cdrom" => Ok(DriveMedia::Cdrom),
135            _ => Err(()),
136        }
137    }
138}
139impl ToArg for DriveMedia {
140    fn to_arg(&self) -> &str {
141        match self {
142            DriveMedia::Disk => "disk",
143            DriveMedia::Cdrom => "cdrom",
144        }
145    }
146}
147
148#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Arbitrary)]
149pub enum DriveCacheType {
150    None,
151    #[default]
152    Writeback,
153    Writethrough,
154    Unsafe,
155    Directsync,
156}
157
158impl Display for DriveCacheType {
159    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
160        match self {
161            DriveCacheType::None => f.write_str("none"),
162            DriveCacheType::Writeback => f.write_str("writeback"),
163            DriveCacheType::Writethrough => f.write_str("writethrough"),
164            DriveCacheType::Unsafe => f.write_str("unsafe"),
165            DriveCacheType::Directsync => f.write_str("directsync"),
166        }
167    }
168}
169impl FromStr for DriveCacheType {
170    type Err = ();
171    fn from_str(s: &str) -> Result<Self, Self::Err> {
172        match s {
173            "none" => Ok(DriveCacheType::None),
174            "writeback" => Ok(DriveCacheType::Writeback),
175            "writethrough" => Ok(DriveCacheType::Writethrough),
176            "unsafe" => Ok(DriveCacheType::Unsafe),
177            "directsync" => Ok(DriveCacheType::Directsync),
178            _ => Err(()),
179        }
180    }
181}
182impl ToArg for DriveCacheType {
183    fn to_arg(&self) -> &str {
184        match self {
185            DriveCacheType::None => "none",
186            DriveCacheType::Writeback => "writeback",
187            DriveCacheType::Writethrough => "writethrough",
188            DriveCacheType::Unsafe => "unsafe",
189            DriveCacheType::Directsync => "directsync",
190        }
191    }
192}
193
194#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
195pub enum DriveAIOType {
196    Threads,
197    Native,
198    IoUring,
199}
200
201impl Display for DriveAIOType {
202    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
203        match self {
204            DriveAIOType::Threads => write!(f, "threads"),
205            DriveAIOType::Native => write!(f, "native"),
206            DriveAIOType::IoUring => write!(f, "io_uring"),
207        }
208    }
209}
210impl FromStr for DriveAIOType {
211    type Err = ();
212    fn from_str(s: &str) -> Result<Self, Self::Err> {
213        match s {
214            "threads" => Ok(DriveAIOType::Threads),
215            "native" => Ok(DriveAIOType::Native),
216            "io_uring" => Ok(DriveAIOType::IoUring),
217            _ => Err(()),
218        }
219    }
220}
221impl ToArg for DriveAIOType {
222    fn to_arg(&self) -> &str {
223        match self {
224            DriveAIOType::Threads => "threads",
225            DriveAIOType::Native => "native",
226            DriveAIOType::IoUring => "io_uring",
227        }
228    }
229}
230
231#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
232pub enum DriveErrorAction {
233    Ignore,
234    Stop,
235    Report,
236    Enospc,
237}
238
239impl Display for DriveErrorAction {
240    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
241        match self {
242            DriveErrorAction::Ignore => write!(f, "ignore"),
243            DriveErrorAction::Stop => write!(f, "stop"),
244            DriveErrorAction::Report => write!(f, "report"),
245            DriveErrorAction::Enospc => write!(f, "enospc"),
246        }
247    }
248}
249impl FromStr for DriveErrorAction {
250    type Err = ();
251    fn from_str(s: &str) -> Result<Self, Self::Err> {
252        match s {
253            "ignore" => Ok(DriveErrorAction::Ignore),
254            "stop" => Ok(DriveErrorAction::Stop),
255            "report" => Ok(DriveErrorAction::Report),
256            "enospc" => Ok(DriveErrorAction::Enospc),
257            _ => Err(()),
258        }
259    }
260}
261impl ToArg for DriveErrorAction {
262    fn to_arg(&self) -> &str {
263        match self {
264            DriveErrorAction::Ignore => "ignore",
265            DriveErrorAction::Stop => "stop",
266            DriveErrorAction::Report => "report",
267            DriveErrorAction::Enospc => "enospc",
268        }
269    }
270}
271
272/// Define a new drive. This includes creating a block driver node (the
273/// backend) as well as a guest device, and is mostly a shortcut for
274/// defining the corresponding ``-blockdev`` and ``-device`` options.
275///
276/// ``-drive`` accepts all options that are accepted by ``-blockdev``.
277#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
278pub struct Drive {
279    // -drive
280    /// This option defines which disk image (see the :ref:`disk images`
281    /// chapter in the System Emulation Users Guide) to use with this drive.
282    /// If the filename contains comma, you must double it (for instance,
283    /// "file=my,,file" to use file "my,file").
284    ///
285    /// Special files such as iSCSI devices can be specified using
286    /// protocol specific URLs. See the section for "Device URL Syntax"
287    /// for more information.
288    pub file: Option<ShellPath>,
289
290    /// This option defines on which type on interface the drive is
291    /// connected. Available types are: ide, scsi, sd, mtd, floppy,
292    /// pflash, virtio, none.
293    pub interface: Option<DriveInterface>,
294
295    /// These options define where is connected the drive by defining
296    /// the bus number and the unit id.
297    pub bus: Option<usize>,
298    pub unit: Option<usize>,
299
300    /// This option defines where the drive is connected by using an
301    /// index in the list of available connectors of a given interface
302    /// type.
303    pub index: Option<ShellString>,
304
305    /// This option defines the type of the media: disk or cdrom.
306    pub media: Option<DriveMedia>,
307
308    /// snapshot is "on" or "off" and controls snapshot mode for the
309    /// given drive (see ``-snapshot``).
310    pub snapshot: Option<OnOff>,
311
312    /// cache is "none", "writeback", "unsafe", "directsync" or
313    /// "writethrough" and controls how the host cache is used to access
314    /// block data. This is a shortcut that sets the ``cache.direct``
315    /// and ``cache.no-flush`` options (as in ``-blockdev``), and
316    /// additionally ``cache.writeback``, which provides a default for
317    /// the ``write-cache`` option of block guest devices (as in
318    /// ``-device``). The modes correspond to the following settings:
319    ///
320    /// =============  ===============   ============   ==============
321    /// \              cache.writeback   cache.direct   cache.no-flush
322    /// =============  ===============   ============   ==============
323    /// writeback      on                off            off
324    /// none           on                on             off
325    /// writethrough   off               off            off
326    /// directsync     off               on             off
327    /// unsafe         on                off            on
328    /// =============  ===============   ============   ==============
329    ///
330    /// The default mode is ``cache=writeback``.
331    pub cache: Option<DriveCacheType>,
332
333    /// Avoid the host page cache when enabled.
334    pub cache_direct: Option<OnOff>,
335
336    /// aio is "threads", "native", or "io_uring" and selects between pthread
337    /// based disk I/O, native Linux AIO, or Linux io_uring API.
338    pub aio: Option<DriveAIOType>,
339
340    /// Specify which disk format will be used rather than detecting the
341    /// format. Can be used to specify format=raw to avoid interpreting
342    /// an untrusted format header.
343    pub format: Option<ShellString>,
344
345    /// Encryption format passed through to the block layer.
346    pub encrypt_format: Option<ShellString>,
347
348    /// Secret object id used as the encryption key.
349    pub encrypt_key_secret: Option<ShellString>,
350
351    /// Specify which action to take on write and read errors. Valid
352    /// actions are: "ignore" (ignore the error and try to continue),
353    /// "stop" (pause QEMU), "report" (report the error to the guest),
354    /// "enospc" (pause QEMU only if the host disk is full; report the
355    /// error to the guest otherwise). The default setting is
356    /// ``werror=enospc`` and ``rerror=report``.
357    pub rerror: Option<DriveErrorAction>,
358    pub werror: Option<DriveErrorAction>,
359
360    pub id: Option<ShellString>,
361
362    /// Open the node read-only. Guest write attempts will fail.
363    ///
364    /// Note that some block drivers support only read-only access,
365    /// either generally or in certain configurations. In this case,
366    /// the default value ``readonly=off`` does not work and the
367    /// option must be specified explicitly.
368    pub read_only: Option<OnOff>,
369
370    /// Allow QEMU to fall back to read-only usage when requested access fails.
371    pub auto_read_only: Option<OnOff>,
372
373    /// `copy-on-read=` is `on` or `off` and enables whether to copy read
374    /// backing file sectors into the image file.
375    pub copy_on_read: Option<OnOff>,
376
377    /// discard is one of "ignore" (or "off") or "unmap" (or "on")
378    /// and controls whether ``discard`` (also known as ``trim`` or
379    /// ``unmap``) requests are ignored or passed to the filesystem.
380    /// Some machine types may not support discard requests.
381    pub discard: Option<IgnoreUnmap>,
382
383    /// detect-zeroes is "off", "on" or "unmap" and enables the
384    /// automatic conversion of plain zero writes by the OS to
385    /// driver specific optimized zero write commands. You may even
386    /// choose "unmap" if discard is set to "unmap" to allow a zero
387    /// write to be converted to an ``unmap`` operation.
388    pub detect_zeroes: Option<OnOffUnmap>,
389
390    /// Specify bandwidth throttling limits in bytes per second, either
391    /// for all request types or for reads or writes only. Small values
392    /// can lead to timeouts or hangs inside the guest. A safe minimum
393    /// for disks is 2 MB/s.
394    pub bps: Option<usize>,
395    pub bps_rd: Option<usize>,
396    pub bps_wr: Option<usize>,
397
398    /// Specify bursts in bytes per second, either for all request types
399    /// or for reads or writes only. Bursts allow the guest I/O to spike
400    /// above the limit temporarily.
401    pub bps_max: Option<usize>,
402    pub bps_rd_max: Option<usize>,
403    pub bps_wr_max: Option<usize>,
404
405    /// Specify request rate limits in requests per second, either for
406    /// all request types or for reads or writes only.
407    pub iops: Option<usize>,
408    pub iops_rd: Option<usize>,
409    pub iops_wr: Option<usize>,
410
411    /// Specify bursts in requests per second, either for all request
412    /// types or for reads or writes only. Bursts allow the guest I/O to
413    /// spike above the limit temporarily.
414    pub iops_max: Option<usize>,
415    pub iops_rd_max: Option<usize>,
416    pub iops_wr_max: Option<usize>,
417
418    /// Let every is bytes of a request count as a new request for iops
419    /// throttling purposes. Use this option to prevent guests from
420    /// circumventing iops limits by sending fewer but larger requests.
421    pub iops_size: Option<usize>,
422
423    /// Join a throttling quota group with given name g. All drives that
424    /// are members of the same group are accounted for together. Use
425    /// this option to prevent guests from circumventing throttling
426    /// limits by using many small disks instead of a single larger
427    /// disk.
428    pub group: Option<ShellString>,
429
430    pub throttling_bps_total: Option<usize>,
431    pub throttling_bps_total_max: Option<usize>,
432    pub throttling_bps_total_max_length: Option<usize>,
433    pub throttling_iops_total: Option<usize>,
434    pub throttling_iops_total_max: Option<usize>,
435    pub throttling_iops_total_max_length: Option<usize>,
436}
437
438impl ToCommand for Drive {
439    fn command(&self) -> String {
440        ARG_DRIVE.to_string()
441    }
442    fn has_args(&self) -> bool {
443        self.discard.is_some()
444            || self.read_only.is_some()
445            || self.detect_zeroes.is_some()
446            || self.file.is_some()
447            || self.interface.is_some()
448            || self.bus.is_some()
449            || self.unit.is_some()
450            || self.index.is_some()
451            || self.media.is_some()
452            || self.snapshot.is_some()
453            || self.cache.is_some()
454            || self.cache_direct.is_some()
455            || self.id.is_some()
456            || self.aio.is_some()
457            || self.format.is_some()
458            || self.encrypt_format.is_some()
459            || self.encrypt_key_secret.is_some()
460            || self.rerror.is_some()
461            || self.werror.is_some()
462            || self.copy_on_read.is_some()
463            || self.auto_read_only.is_some()
464            || self.bps.is_some()
465            || self.bps_rd.is_some()
466            || self.bps_wr.is_some()
467            || self.bps_max.is_some()
468            || self.bps_rd_max.is_some()
469            || self.bps_wr_max.is_some()
470            || self.iops.is_some()
471            || self.iops_rd.is_some()
472            || self.iops_wr.is_some()
473            || self.iops_max.is_some()
474            || self.iops_rd_max.is_some()
475            || self.iops_wr_max.is_some()
476            || self.iops_size.is_some()
477            || self.group.is_some()
478            || self.throttling_bps_total.is_some()
479            || self.throttling_bps_total_max.is_some()
480            || self.throttling_bps_total_max_length.is_some()
481            || self.throttling_iops_total.is_some()
482            || self.throttling_iops_total_max.is_some()
483            || self.throttling_iops_total_max_length.is_some()
484    }
485    fn to_args(&self) -> Vec<String> {
486        let mut args = vec![];
487
488        if let Some(file) = &self.file {
489            args.push(format!("{}{}", KEY_FILE, file.as_ref()));
490        }
491        qao!(&self.interface, args, KEY_INTERFACE);
492        qao!(&self.bus, args, KEY_BUS);
493        qao!(&self.unit, args, KEY_UNIT);
494        if let Some(index) = &self.index {
495            args.push(format!("{}{}", KEY_INDEX, index.as_ref()));
496        }
497        qao!(&self.media, args, KEY_MEDIA);
498        qao!(&self.snapshot, args, KEY_SNAPSHOT);
499        qao!(&self.cache, args, KEY_CACHE);
500        qao!(&self.cache_direct, args, KEY_CACHE_DIRECT);
501        qao!(&self.aio, args, KEY_AIO);
502        if let Some(format) = &self.format {
503            args.push(format!("{}{}", KEY_FORMAT, format.as_ref()));
504        }
505        if let Some(encrypt_format) = &self.encrypt_format {
506            args.push(format!("{}{}", KEY_ENCRYPT_FORMAT, encrypt_format.as_ref()));
507        }
508        if let Some(encrypt_key_secret) = &self.encrypt_key_secret {
509            args.push(format!("{}{}", KEY_ENCRYPT_KEY_SECRET, encrypt_key_secret.as_ref()));
510        }
511        qao!(&self.rerror, args, KEY_RERROR);
512        qao!(&self.werror, args, KEY_WERROR);
513        if let Some(id) = &self.id {
514            args.push(format!("{}{}", KEY_ID, id.as_ref()));
515        }
516        qao!(&self.read_only, args, KEY_READ_ONLY);
517        qao!(&self.auto_read_only, args, KEY_AUTO_READ_ONLY);
518        qao!(&self.copy_on_read, args, KEY_COPY_ON_READ);
519        qao!(&self.discard, args, KEY_DISCARD);
520        qao!(&self.detect_zeroes, args, KEY_DETECT_ZEROES);
521        qao!(&self.bps, args, KEY_BPS);
522        qao!(&self.bps_rd, args, KEY_BPS_RD);
523        qao!(&self.bps_wr, args, KEY_BPS_WR);
524        qao!(&self.bps_max, args, KEY_BPS_MAX);
525        qao!(&self.bps_rd_max, args, KEY_BPS_RD_MAX);
526        qao!(&self.bps_wr_max, args, KEY_BPS_WR_MAX);
527        qao!(&self.iops, args, KEY_IOPS);
528        qao!(&self.iops_rd, args, KEY_IOPS_RD);
529        qao!(&self.iops_wr, args, KEY_IOPS_WR);
530        qao!(&self.iops_max, args, KEY_IOPS_MAX);
531        qao!(&self.iops_rd_max, args, KEY_IOPS_RD_MAX);
532        qao!(&self.iops_wr_max, args, KEY_IOPS_WR_MAX);
533        qao!(&self.iops_size, args, KEY_IOPS_SIZE);
534        if let Some(group) = &self.group {
535            args.push(format!("{}{}", KEY_GROUP, group.as_ref()));
536        }
537        qao!(&self.throttling_bps_total, args, KEY_THROTTLING_BPS_TOTAL);
538        qao!(&self.throttling_bps_total_max, args, KEY_THROTTLING_BPS_TOTAL_MAX);
539        qao!(&self.throttling_bps_total_max_length, args, KEY_THROTTLING_BPS_TOTAL_MAX_LENGTH);
540        qao!(&self.throttling_iops_total, args, KEY_THROTTLING_IOPS_TOTAL);
541        qao!(&self.throttling_iops_total_max, args, KEY_THROTTLING_IOPS_TOTAL_MAX);
542        qao!(&self.throttling_iops_total_max_length, args, KEY_THROTTLING_IOPS_TOTAL_MAX_LENGTH);
543
544        vec![args.join(DELIM_COMMA)]
545    }
546}
547
548impl FromStr for Drive {
549    type Err = String;
550
551    fn from_str(s: &str) -> Result<Self, Self::Err> {
552        let mut drive = Drive::default();
553
554        for part in s.split(DELIM_COMMA) {
555            let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid drive option: {part}"))?;
556            match key {
557                "file" => drive.file = Some(ShellPath::from(value)),
558                "if" => drive.interface = Some(value.parse::<DriveInterface>().map_err(|_| format!("invalid if value: {value}"))?),
559                "bus" => drive.bus = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
560                "unit" => drive.unit = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
561                "index" => drive.index = Some(ShellString::from_str(value)?),
562                "media" => drive.media = Some(value.parse::<DriveMedia>().map_err(|_| format!("invalid media value: {value}"))?),
563                "snapshot" => drive.snapshot = Some(value.parse::<OnOff>().map_err(|_| format!("invalid snapshot value: {value}"))?),
564                "cache" => drive.cache = Some(value.parse::<DriveCacheType>().map_err(|_| format!("invalid cache value: {value}"))?),
565                "cache.direct" => drive.cache_direct = Some(value.parse::<OnOff>().map_err(|_| format!("invalid cache.direct value: {value}"))?),
566                "aio" => drive.aio = Some(value.parse::<DriveAIOType>().map_err(|_| format!("invalid aio value: {value}"))?),
567                "format" => drive.format = Some(ShellString::from_str(value)?),
568                "encrypt.format" => drive.encrypt_format = Some(ShellString::from_str(value)?),
569                "encrypt.key-secret" => drive.encrypt_key_secret = Some(ShellString::from_str(value)?),
570                "rerror" => drive.rerror = Some(value.parse::<DriveErrorAction>().map_err(|_| format!("invalid rerror value: {value}"))?),
571                "werror" => drive.werror = Some(value.parse::<DriveErrorAction>().map_err(|_| format!("invalid werror value: {value}"))?),
572                "id" => drive.id = Some(ShellString::from_str(value)?),
573                "readonly" | "read-only" => drive.read_only = Some(value.parse::<OnOff>().map_err(|_| format!("invalid readonly value: {value}"))?),
574                "auto-read-only" => drive.auto_read_only = Some(value.parse::<OnOff>().map_err(|_| format!("invalid auto-read-only value: {value}"))?),
575                "copy-on-read" => drive.copy_on_read = Some(value.parse::<OnOff>().map_err(|_| format!("invalid copy-on-read value: {value}"))?),
576                "discard" => drive.discard = Some(value.parse::<IgnoreUnmap>().map_err(|_| format!("invalid discard value: {value}"))?),
577                "detect-zeroes" => drive.detect_zeroes = Some(value.parse::<OnOffUnmap>().map_err(|_| format!("invalid detect-zeroes value: {value}"))?),
578                "bps" => drive.bps = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
579                "bps_rd" => drive.bps_rd = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
580                "bps_wr" => drive.bps_wr = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
581                "bps_max" => drive.bps_max = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
582                "bps_rd_max" => drive.bps_rd_max = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
583                "bps_wr_max" => drive.bps_wr_max = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
584                "iops" => drive.iops = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
585                "iops_rd" => drive.iops_rd = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
586                "iops_wr" => drive.iops_wr = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
587                "iops_max" => drive.iops_max = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
588                "iops_rd_max" => drive.iops_rd_max = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
589                "iops_wr_max" => drive.iops_wr_max = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
590                "iops_size" => drive.iops_size = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
591                "group" => drive.group = Some(ShellString::from_str(value)?),
592                "throttling.bps-total" => drive.throttling_bps_total = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
593                "throttling.bps-total-max" => drive.throttling_bps_total_max = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
594                "throttling.bps-total-max-length" => drive.throttling_bps_total_max_length = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
595                "throttling.iops-total" => drive.throttling_iops_total = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
596                "throttling.iops-total-max" => drive.throttling_iops_total_max = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
597                "throttling.iops-total-max-length" => drive.throttling_iops_total_max_length = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
598                other => return Err(format!("unsupported drive option: {other}")),
599            }
600        }
601
602        Ok(drive)
603    }
604}