1#[cfg(windows)]
6use std::num::NonZeroU32;
7use std::path::PathBuf;
8
9use cros_async::ExecutorKind;
10use serde::Deserialize;
11use serde::Deserializer;
12use serde::Serialize;
13use serde::Serializer;
14
15use crate::PciAddress;
16
17pub mod asynchronous;
18pub(crate) mod sys;
19
20pub use asynchronous::BlockAsync;
21
22fn block_option_sparse_default() -> bool {
23 true
24}
25fn block_option_lock_default() -> bool {
26 true
27}
28fn block_option_block_size_default() -> u32 {
29 512
30}
31#[cfg(windows)]
34fn block_option_io_concurrency_default() -> NonZeroU32 {
35 NonZeroU32::new(1).unwrap()
36}
37
38pub const DISK_ID_LEN: usize = 20;
42
43pub fn serialize_disk_id<S: Serializer>(
44 id: &Option<[u8; DISK_ID_LEN]>,
45 serializer: S,
46) -> Result<S::Ok, S::Error> {
47 match id {
48 None => serializer.serialize_none(),
49 Some(id) => {
50 let len = id.iter().position(|v| *v == 0).unwrap_or(DISK_ID_LEN);
52 serializer.serialize_some(
53 std::str::from_utf8(&id[0..len])
54 .map_err(|e| serde::ser::Error::custom(e.to_string()))?,
55 )
56 }
57 }
58}
59
60fn deserialize_disk_id<'de, D: Deserializer<'de>>(
61 deserializer: D,
62) -> Result<Option<[u8; DISK_ID_LEN]>, D::Error> {
63 let id = Option::<String>::deserialize(deserializer)?;
64
65 match id {
66 None => Ok(None),
67 Some(id) => {
68 if id.len() > DISK_ID_LEN {
69 return Err(serde::de::Error::custom(format!(
70 "disk id must be {DISK_ID_LEN} or fewer characters"
71 )));
72 }
73
74 let mut ret = [0u8; DISK_ID_LEN];
75 ret[..id.len()].copy_from_slice(id.as_bytes());
78 Ok(Some(ret))
79 }
80 }
81}
82
83#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, serde_keyvalue::FromKeyValues)]
84#[serde(deny_unknown_fields, rename_all = "kebab-case")]
85pub struct DiskOption {
86 pub path: PathBuf,
87 #[serde(default, rename = "ro")]
88 pub read_only: bool,
89 #[serde(default)]
90 pub root: bool,
93 #[serde(default = "block_option_sparse_default")]
94 pub sparse: bool,
95 #[serde(default, alias = "o_direct")]
97 pub direct: bool,
98 #[serde(default = "block_option_lock_default")]
100 pub lock: bool,
101 #[serde(default = "block_option_block_size_default", alias = "block_size")]
103 pub block_size: u32,
104 #[serde(
105 default,
106 serialize_with = "serialize_disk_id",
107 deserialize_with = "deserialize_disk_id"
108 )]
109 pub id: Option<[u8; DISK_ID_LEN]>,
110 #[cfg(windows)]
113 #[serde(
114 default = "block_option_io_concurrency_default",
115 alias = "io_concurrency"
116 )]
117 pub io_concurrency: NonZeroU32,
118 #[serde(default)]
119 pub multiple_workers: bool,
122 #[serde(default, alias = "async_executor")]
123 pub async_executor: Option<ExecutorKind>,
127 #[serde(default)]
128 pub packed_queue: bool,
131
132 pub bootindex: Option<usize>,
136
137 pub pci_address: Option<PciAddress>,
139}
140
141impl Default for DiskOption {
142 fn default() -> Self {
143 Self {
144 path: PathBuf::new(),
145 read_only: false,
146 root: false,
147 sparse: block_option_sparse_default(),
148 direct: false,
149 lock: block_option_lock_default(),
150 block_size: block_option_block_size_default(),
151 id: None,
152 #[cfg(windows)]
153 io_concurrency: block_option_io_concurrency_default(),
154 multiple_workers: false,
155 async_executor: None,
156 packed_queue: false,
157 bootindex: None,
158 pci_address: None,
159 }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 #[cfg(any(target_os = "android", target_os = "linux"))]
166 use cros_async::sys::linux::ExecutorKindSys;
167 #[cfg(windows)]
168 use cros_async::sys::windows::ExecutorKindSys;
169 use serde_keyvalue::*;
170
171 use super::*;
172
173 fn from_block_arg(options: &str) -> Result<DiskOption, ParseError> {
174 from_key_values(options)
175 }
176
177 #[test]
178 fn check_default_matches_from_key_values() {
179 let path = "/path/to/disk.img";
180 let disk = DiskOption {
181 path: PathBuf::from(path),
182 ..DiskOption::default()
183 };
184 assert_eq!(disk, from_key_values(path).unwrap());
185 }
186
187 #[test]
188 fn params_from_key_values() {
189 let err = from_block_arg("").unwrap_err();
191 assert_eq!(
192 err,
193 ParseError {
194 kind: ErrorKind::SerdeError("missing field `path`".into()),
195 pos: 0,
196 }
197 );
198
199 let params = from_block_arg("/path/to/disk.img").unwrap();
201 assert_eq!(
202 params,
203 DiskOption {
204 path: "/path/to/disk.img".into(),
205 read_only: false,
206 root: false,
207 sparse: true,
208 direct: false,
209 lock: true,
210 block_size: 512,
211 id: None,
212 #[cfg(windows)]
213 io_concurrency: NonZeroU32::new(1).unwrap(),
214 multiple_workers: false,
215 async_executor: None,
216 packed_queue: false,
217 bootindex: None,
218 pci_address: None,
219 }
220 );
221
222 let params = from_block_arg("/path/to/disk.img,bootindex=5").unwrap();
224 assert_eq!(
225 params,
226 DiskOption {
227 path: "/path/to/disk.img".into(),
228 read_only: false,
229 root: false,
230 sparse: true,
231 direct: false,
232 lock: true,
233 block_size: 512,
234 id: None,
235 #[cfg(windows)]
236 io_concurrency: NonZeroU32::new(1).unwrap(),
237 multiple_workers: false,
238 async_executor: None,
239 packed_queue: false,
240 bootindex: Some(5),
241 pci_address: None,
242 }
243 );
244
245 let params = from_block_arg("path=/path/to/disk.img").unwrap();
247 assert_eq!(
248 params,
249 DiskOption {
250 path: "/path/to/disk.img".into(),
251 read_only: false,
252 root: false,
253 sparse: true,
254 direct: false,
255 lock: true,
256 block_size: 512,
257 id: None,
258 #[cfg(windows)]
259 io_concurrency: NonZeroU32::new(1).unwrap(),
260 multiple_workers: false,
261 async_executor: None,
262 packed_queue: false,
263 bootindex: None,
264 pci_address: None,
265 }
266 );
267
268 let params = from_block_arg("/some/path.img,ro").unwrap();
270 assert_eq!(
271 params,
272 DiskOption {
273 path: "/some/path.img".into(),
274 read_only: true,
275 root: false,
276 sparse: true,
277 direct: false,
278 lock: true,
279 block_size: 512,
280 id: None,
281 #[cfg(windows)]
282 io_concurrency: NonZeroU32::new(1).unwrap(),
283 multiple_workers: false,
284 async_executor: None,
285 packed_queue: false,
286 bootindex: None,
287 pci_address: None,
288 }
289 );
290
291 let params = from_block_arg("/some/path.img,root").unwrap();
293 assert_eq!(
294 params,
295 DiskOption {
296 path: "/some/path.img".into(),
297 read_only: false,
298 root: true,
299 sparse: true,
300 direct: false,
301 lock: true,
302 block_size: 512,
303 id: None,
304 #[cfg(windows)]
305 io_concurrency: NonZeroU32::new(1).unwrap(),
306 multiple_workers: false,
307 async_executor: None,
308 packed_queue: false,
309 bootindex: None,
310 pci_address: None,
311 }
312 );
313
314 let params = from_block_arg("/some/path.img,sparse").unwrap();
316 assert_eq!(
317 params,
318 DiskOption {
319 path: "/some/path.img".into(),
320 read_only: false,
321 root: false,
322 sparse: true,
323 direct: false,
324 lock: true,
325 block_size: 512,
326 id: None,
327 #[cfg(windows)]
328 io_concurrency: NonZeroU32::new(1).unwrap(),
329 multiple_workers: false,
330 async_executor: None,
331 packed_queue: false,
332 bootindex: None,
333 pci_address: None,
334 }
335 );
336 let params = from_block_arg("/some/path.img,sparse=false").unwrap();
337 assert_eq!(
338 params,
339 DiskOption {
340 path: "/some/path.img".into(),
341 read_only: false,
342 root: false,
343 sparse: false,
344 direct: false,
345 lock: true,
346 block_size: 512,
347 id: None,
348 #[cfg(windows)]
349 io_concurrency: NonZeroU32::new(1).unwrap(),
350 multiple_workers: false,
351 async_executor: None,
352 packed_queue: false,
353 bootindex: None,
354 pci_address: None,
355 }
356 );
357
358 let params = from_block_arg("/some/path.img,direct").unwrap();
360 assert_eq!(
361 params,
362 DiskOption {
363 path: "/some/path.img".into(),
364 read_only: false,
365 root: false,
366 sparse: true,
367 direct: true,
368 lock: true,
369 block_size: 512,
370 id: None,
371 #[cfg(windows)]
372 io_concurrency: NonZeroU32::new(1).unwrap(),
373 multiple_workers: false,
374 async_executor: None,
375 packed_queue: false,
376 bootindex: None,
377 pci_address: None,
378 }
379 );
380
381 let params = from_block_arg("/some/path.img,o_direct").unwrap();
383 assert_eq!(
384 params,
385 DiskOption {
386 path: "/some/path.img".into(),
387 read_only: false,
388 root: false,
389 sparse: true,
390 direct: true,
391 lock: true,
392 block_size: 512,
393 id: None,
394 #[cfg(windows)]
395 io_concurrency: NonZeroU32::new(1).unwrap(),
396 multiple_workers: false,
397 async_executor: None,
398 packed_queue: false,
399 bootindex: None,
400 pci_address: None,
401 }
402 );
403
404 let params = from_block_arg("/some/path.img,block-size=128").unwrap();
406 assert_eq!(
407 params,
408 DiskOption {
409 path: "/some/path.img".into(),
410 read_only: false,
411 root: false,
412 sparse: true,
413 direct: false,
414 lock: true,
415 block_size: 128,
416 id: None,
417 #[cfg(windows)]
418 io_concurrency: NonZeroU32::new(1).unwrap(),
419 multiple_workers: false,
420 async_executor: None,
421 packed_queue: false,
422 bootindex: None,
423 pci_address: None,
424 }
425 );
426
427 let params = from_block_arg("/some/path.img,block_size=128").unwrap();
429 assert_eq!(
430 params,
431 DiskOption {
432 path: "/some/path.img".into(),
433 read_only: false,
434 root: false,
435 sparse: true,
436 direct: false,
437 lock: true,
438 block_size: 128,
439 id: None,
440 async_executor: None,
441 #[cfg(windows)]
442 io_concurrency: NonZeroU32::new(1).unwrap(),
443 multiple_workers: false,
444 packed_queue: false,
445 bootindex: None,
446 pci_address: None,
447 }
448 );
449
450 #[cfg(windows)]
452 {
453 let params = from_block_arg("/some/path.img,io_concurrency=4").unwrap();
454 assert_eq!(
455 params,
456 DiskOption {
457 path: "/some/path.img".into(),
458 read_only: false,
459 root: false,
460 sparse: true,
461 direct: false,
462 lock: true,
463 block_size: 512,
464 id: None,
465 io_concurrency: NonZeroU32::new(4).unwrap(),
466 multiple_workers: false,
467 async_executor: None,
468 packed_queue: false,
469 bootindex: None,
470 pci_address: None,
471 }
472 );
473 let params = from_block_arg("/some/path.img,async-executor=overlapped").unwrap();
474 assert_eq!(
475 params,
476 DiskOption {
477 path: "/some/path.img".into(),
478 read_only: false,
479 root: false,
480 sparse: true,
481 direct: false,
482 lock: true,
483 block_size: 512,
484 id: None,
485 io_concurrency: NonZeroU32::new(1).unwrap(),
486 multiple_workers: false,
487 async_executor: Some(ExecutorKindSys::Overlapped { concurrency: None }.into()),
488 packed_queue: false,
489 bootindex: None,
490 pci_address: None,
491 }
492 );
493 let params =
494 from_block_arg("/some/path.img,async-executor=\"overlapped,concurrency=4\"")
495 .unwrap();
496 assert_eq!(
497 params,
498 DiskOption {
499 path: "/some/path.img".into(),
500 read_only: false,
501 root: false,
502 sparse: true,
503 direct: false,
504 lock: true,
505 block_size: 512,
506 id: None,
507 io_concurrency: NonZeroU32::new(1).unwrap(),
508 multiple_workers: false,
509 async_executor: Some(
510 ExecutorKindSys::Overlapped {
511 concurrency: Some(4)
512 }
513 .into()
514 ),
515 packed_queue: false,
516 bootindex: None,
517 pci_address: None,
518 }
519 );
520 }
521
522 let params = from_block_arg("/some/path.img,id=DISK").unwrap();
524 assert_eq!(
525 params,
526 DiskOption {
527 path: "/some/path.img".into(),
528 read_only: false,
529 root: false,
530 sparse: true,
531 direct: false,
532 lock: true,
533 block_size: 512,
534 id: Some(*b"DISK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
535 #[cfg(windows)]
536 io_concurrency: NonZeroU32::new(1).unwrap(),
537 multiple_workers: false,
538 async_executor: None,
539 packed_queue: false,
540 bootindex: None,
541 pci_address: None,
542 }
543 );
544 let err = from_block_arg("/some/path.img,id=DISK_ID_IS_WAY_TOO_LONG").unwrap_err();
545 assert_eq!(
546 err,
547 ParseError {
548 kind: ErrorKind::SerdeError("disk id must be 20 or fewer characters".into()),
549 pos: 0,
550 }
551 );
552
553 #[cfg(windows)]
555 let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Handle.into(), "handle");
556 #[cfg(any(target_os = "android", target_os = "linux"))]
557 let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Fd.into(), "epoll");
558 let params =
559 from_block_arg(&format!("/some/path.img,async-executor={ex_kind_opt}")).unwrap();
560 assert_eq!(
561 params,
562 DiskOption {
563 path: "/some/path.img".into(),
564 read_only: false,
565 root: false,
566 sparse: true,
567 direct: false,
568 lock: true,
569 block_size: 512,
570 id: None,
571 #[cfg(windows)]
572 io_concurrency: NonZeroU32::new(1).unwrap(),
573 multiple_workers: false,
574 async_executor: Some(ex_kind),
575 packed_queue: false,
576 bootindex: None,
577 pci_address: None,
578 }
579 );
580
581 let params = from_block_arg("/path/to/disk.img,packed-queue").unwrap();
583 assert_eq!(
584 params,
585 DiskOption {
586 path: "/path/to/disk.img".into(),
587 read_only: false,
588 root: false,
589 sparse: true,
590 direct: false,
591 lock: true,
592 block_size: 512,
593 id: None,
594 #[cfg(windows)]
595 io_concurrency: NonZeroU32::new(1).unwrap(),
596 multiple_workers: false,
597 async_executor: None,
598 packed_queue: true,
599 bootindex: None,
600 pci_address: None,
601 }
602 );
603
604 let params = from_block_arg("/path/to/disk.img,pci-address=00:01.1").unwrap();
606 assert_eq!(
607 params,
608 DiskOption {
609 path: "/path/to/disk.img".into(),
610 read_only: false,
611 root: false,
612 sparse: true,
613 direct: false,
614 lock: true,
615 block_size: 512,
616 id: None,
617 #[cfg(windows)]
618 io_concurrency: NonZeroU32::new(1).unwrap(),
619 multiple_workers: false,
620 async_executor: None,
621 packed_queue: false,
622 bootindex: None,
623 pci_address: Some(PciAddress {
624 bus: 0,
625 dev: 1,
626 func: 1,
627 }),
628 }
629 );
630
631 let params = from_block_arg("/path/to/disk.img,lock=true").unwrap();
633 assert_eq!(
634 params,
635 DiskOption {
636 path: "/path/to/disk.img".into(),
637 read_only: false,
638 root: false,
639 sparse: true,
640 direct: false,
641 lock: true,
642 block_size: 512,
643 id: None,
644 #[cfg(windows)]
645 io_concurrency: NonZeroU32::new(1).unwrap(),
646 multiple_workers: false,
647 async_executor: None,
648 packed_queue: false,
649 bootindex: None,
650 pci_address: None,
651 }
652 );
653 let params = from_block_arg("/path/to/disk.img,lock=false").unwrap();
655 assert_eq!(
656 params,
657 DiskOption {
658 path: "/path/to/disk.img".into(),
659 read_only: false,
660 root: false,
661 sparse: true,
662 direct: false,
663 lock: false,
664 block_size: 512,
665 id: None,
666 #[cfg(windows)]
667 io_concurrency: NonZeroU32::new(1).unwrap(),
668 multiple_workers: false,
669 async_executor: None,
670 packed_queue: false,
671 bootindex: None,
672 pci_address: None,
673 }
674 );
675
676 let params = from_block_arg(&format!(
678 "/some/path.img,block_size=256,ro,root,sparse=false,id=DISK_LABEL\
679 ,direct,async-executor={ex_kind_opt},packed-queue=false,pci-address=00:01.1"
680 ))
681 .unwrap();
682 assert_eq!(
683 params,
684 DiskOption {
685 path: "/some/path.img".into(),
686 read_only: true,
687 root: true,
688 sparse: false,
689 direct: true,
690 lock: true,
691 block_size: 256,
692 id: Some(*b"DISK_LABEL\0\0\0\0\0\0\0\0\0\0"),
693 #[cfg(windows)]
694 io_concurrency: NonZeroU32::new(1).unwrap(),
695 multiple_workers: false,
696 async_executor: Some(ex_kind),
697 packed_queue: false,
698 bootindex: None,
699 pci_address: Some(PciAddress {
700 bus: 0,
701 dev: 1,
702 func: 1,
703 }),
704 }
705 );
706 }
707
708 #[test]
709 fn diskoption_serialize_deserialize() {
710 let original = DiskOption {
712 path: "./rootfs".into(),
713 read_only: false,
714 root: false,
715 sparse: true,
716 direct: false,
717 lock: true,
718 block_size: 512,
719 id: None,
720 #[cfg(windows)]
721 io_concurrency: NonZeroU32::new(1).unwrap(),
722 multiple_workers: false,
723 async_executor: None,
724 packed_queue: false,
725 bootindex: None,
726 pci_address: None,
727 };
728 let json = serde_json::to_string(&original).unwrap();
729 let deserialized = serde_json::from_str(&json).unwrap();
730 assert_eq!(original, deserialized);
731
732 let original = DiskOption {
734 path: "./rootfs".into(),
735 read_only: false,
736 root: false,
737 sparse: true,
738 direct: false,
739 lock: true,
740 block_size: 512,
741 id: Some(*b"BLK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
742 #[cfg(windows)]
743 io_concurrency: NonZeroU32::new(1).unwrap(),
744 multiple_workers: false,
745 async_executor: Some(ExecutorKind::default()),
746 packed_queue: false,
747 bootindex: None,
748 pci_address: None,
749 };
750 let json = serde_json::to_string(&original).unwrap();
751 let deserialized = serde_json::from_str(&json).unwrap();
752 assert_eq!(original, deserialized);
753
754 let original = DiskOption {
756 path: "./rootfs".into(),
757 read_only: false,
758 root: false,
759 sparse: true,
760 direct: false,
761 lock: true,
762 block_size: 512,
763 id: Some(*b"QWERTYUIOPASDFGHJKL:"),
764 #[cfg(windows)]
765 io_concurrency: NonZeroU32::new(1).unwrap(),
766 multiple_workers: false,
767 async_executor: Some(ExecutorKind::default()),
768 packed_queue: false,
769 bootindex: None,
770 pci_address: None,
771 };
772 let json = serde_json::to_string(&original).unwrap();
773 let deserialized = serde_json::from_str(&json).unwrap();
774 assert_eq!(original, deserialized);
775 }
776}