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#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
278pub struct Drive {
279 pub file: Option<ShellPath>,
289
290 pub interface: Option<DriveInterface>,
294
295 pub bus: Option<usize>,
298 pub unit: Option<usize>,
299
300 pub index: Option<ShellString>,
304
305 pub media: Option<DriveMedia>,
307
308 pub snapshot: Option<OnOff>,
311
312 pub cache: Option<DriveCacheType>,
332
333 pub cache_direct: Option<OnOff>,
335
336 pub aio: Option<DriveAIOType>,
339
340 pub format: Option<ShellString>,
344
345 pub encrypt_format: Option<ShellString>,
347
348 pub encrypt_key_secret: Option<ShellString>,
350
351 pub rerror: Option<DriveErrorAction>,
358 pub werror: Option<DriveErrorAction>,
359
360 pub id: Option<ShellString>,
361
362 pub read_only: Option<OnOff>,
369
370 pub auto_read_only: Option<OnOff>,
372
373 pub copy_on_read: Option<OnOff>,
376
377 pub discard: Option<IgnoreUnmap>,
382
383 pub detect_zeroes: Option<OnOffUnmap>,
389
390 pub bps: Option<usize>,
395 pub bps_rd: Option<usize>,
396 pub bps_wr: Option<usize>,
397
398 pub bps_max: Option<usize>,
402 pub bps_rd_max: Option<usize>,
403 pub bps_wr_max: Option<usize>,
404
405 pub iops: Option<usize>,
408 pub iops_rd: Option<usize>,
409 pub iops_wr: Option<usize>,
410
411 pub iops_max: Option<usize>,
415 pub iops_rd_max: Option<usize>,
416 pub iops_wr_max: Option<usize>,
417
418 pub iops_size: Option<usize>,
422
423 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}