1 // Copyright 2021 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #[cfg(windows)]
6 use std::num::NonZeroU32;
7 use std::path::PathBuf;
8
9 use cros_async::ExecutorKind;
10 use serde::Deserialize;
11 use serde::Deserializer;
12 use serde::Serialize;
13 use serde::Serializer;
14
15 use crate::PciAddress;
16
17 pub mod asynchronous;
18 pub(crate) mod sys;
19
20 pub use asynchronous::BlockAsync;
21
block_option_sparse_default() -> bool22 fn block_option_sparse_default() -> bool {
23 true
24 }
block_option_lock_default() -> bool25 fn block_option_lock_default() -> bool {
26 true
27 }
block_option_block_size_default() -> u3228 fn block_option_block_size_default() -> u32 {
29 512
30 }
31 // TODO(b/237829580): Move to sys module once virtio block sys is refactored to
32 // match the style guide.
33 #[cfg(windows)]
block_option_io_concurrency_default() -> NonZeroU3234 fn block_option_io_concurrency_default() -> NonZeroU32 {
35 NonZeroU32::new(1).unwrap()
36 }
37
38 /// Maximum length of a `DiskOption` identifier.
39 ///
40 /// This is based on the virtio-block ID length limit.
41 pub const DISK_ID_LEN: usize = 20;
42
serialize_disk_id<S: Serializer>( id: &Option<[u8; DISK_ID_LEN]>, serializer: S, ) -> Result<S::Ok, S::Error>43 pub 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 // Find the first zero byte in the id.
51 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
deserialize_disk_id<'de, D: Deserializer<'de>>( deserializer: D, ) -> Result<Option<[u8; DISK_ID_LEN]>, D::Error>60 fn 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 {} or fewer characters",
71 DISK_ID_LEN
72 )));
73 }
74
75 let mut ret = [0u8; DISK_ID_LEN];
76 // Slicing id to value's length will never panic
77 // because we checked that value will fit into id above.
78 ret[..id.len()].copy_from_slice(id.as_bytes());
79 Ok(Some(ret))
80 }
81 }
82 }
83
84 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, serde_keyvalue::FromKeyValues)]
85 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
86 pub struct DiskOption {
87 pub path: PathBuf,
88 #[serde(default, rename = "ro")]
89 pub read_only: bool,
90 #[serde(default)]
91 /// Whether this disk should be the root device. Can only be set once. Only useful for adding
92 /// specific command-line options.
93 pub root: bool,
94 #[serde(default = "block_option_sparse_default")]
95 pub sparse: bool,
96 // camel_case variant allowed for backward compatibility.
97 #[serde(default, alias = "o_direct")]
98 pub direct: bool,
99 /// Whether to lock the disk files. Uses flock on Unix and FILE_SHARE_* flags on Windows.
100 #[serde(default = "block_option_lock_default")]
101 pub lock: bool,
102 // camel_case variant allowed for backward compatibility.
103 #[serde(default = "block_option_block_size_default", alias = "block_size")]
104 pub block_size: u32,
105 #[serde(
106 default,
107 serialize_with = "serialize_disk_id",
108 deserialize_with = "deserialize_disk_id"
109 )]
110 pub id: Option<[u8; DISK_ID_LEN]>,
111 // Deprecated: Use async_executor=overlapped[concurrency=N]"
112 // camel_case variant allowed for backward compatibility.
113 #[cfg(windows)]
114 #[serde(
115 default = "block_option_io_concurrency_default",
116 alias = "io_concurrency"
117 )]
118 pub io_concurrency: NonZeroU32,
119 #[serde(default)]
120 /// Experimental option to run multiple worker threads in parallel. If false, only single
121 /// thread runs by default. Note this option is not effective for vhost-user blk device.
122 pub multiple_workers: bool,
123 #[serde(default, alias = "async_executor")]
124 /// The async executor kind to simulate the block device with. This option takes
125 /// precedence over the async executor kind specified by the subcommand's option.
126 /// If None, the default or the specified by the subcommand's option would be used.
127 pub async_executor: Option<ExecutorKind>,
128 #[serde(default)]
129 //Option to choose virtqueue type. If true, use the packed virtqueue. If false
130 //or by default, use split virtqueue
131 pub packed_queue: bool,
132
133 /// Specify the boot index for this device that the BIOS will use when attempting to boot from
134 /// bootable devices. For example, if bootindex=2, then the BIOS will attempt to boot from the
135 /// device right after booting from the device with bootindex=1 fails.
136 pub bootindex: Option<usize>,
137
138 /// Specify PCI address will be used to attach this device
139 pub pci_address: Option<PciAddress>,
140 }
141
142 impl Default for DiskOption {
default() -> Self143 fn default() -> Self {
144 Self {
145 path: PathBuf::new(),
146 read_only: false,
147 root: false,
148 sparse: block_option_sparse_default(),
149 direct: false,
150 lock: block_option_lock_default(),
151 block_size: block_option_block_size_default(),
152 id: None,
153 #[cfg(windows)]
154 io_concurrency: block_option_io_concurrency_default(),
155 multiple_workers: false,
156 async_executor: None,
157 packed_queue: false,
158 bootindex: None,
159 pci_address: None,
160 }
161 }
162 }
163
164 #[cfg(test)]
165 mod tests {
166 #[cfg(any(target_os = "android", target_os = "linux"))]
167 use cros_async::sys::linux::ExecutorKindSys;
168 #[cfg(windows)]
169 use cros_async::sys::windows::ExecutorKindSys;
170 use serde_keyvalue::*;
171
172 use super::*;
173
from_block_arg(options: &str) -> Result<DiskOption, ParseError>174 fn from_block_arg(options: &str) -> Result<DiskOption, ParseError> {
175 from_key_values(options)
176 }
177
178 #[test]
check_default_matches_from_key_values()179 fn check_default_matches_from_key_values() {
180 let path = "/path/to/disk.img";
181 let disk = DiskOption {
182 path: PathBuf::from(path),
183 ..DiskOption::default()
184 };
185 assert_eq!(disk, from_key_values(path).unwrap());
186 }
187
188 #[test]
params_from_key_values()189 fn params_from_key_values() {
190 // Path argument is mandatory.
191 let err = from_block_arg("").unwrap_err();
192 assert_eq!(
193 err,
194 ParseError {
195 kind: ErrorKind::SerdeError("missing field `path`".into()),
196 pos: 0,
197 }
198 );
199
200 // Path is the default argument.
201 let params = from_block_arg("/path/to/disk.img").unwrap();
202 assert_eq!(
203 params,
204 DiskOption {
205 path: "/path/to/disk.img".into(),
206 read_only: false,
207 root: false,
208 sparse: true,
209 direct: false,
210 lock: true,
211 block_size: 512,
212 id: None,
213 #[cfg(windows)]
214 io_concurrency: NonZeroU32::new(1).unwrap(),
215 multiple_workers: false,
216 async_executor: None,
217 packed_queue: false,
218 bootindex: None,
219 pci_address: None,
220 }
221 );
222
223 // bootindex
224 let params = from_block_arg("/path/to/disk.img,bootindex=5").unwrap();
225 assert_eq!(
226 params,
227 DiskOption {
228 path: "/path/to/disk.img".into(),
229 read_only: false,
230 root: false,
231 sparse: true,
232 direct: false,
233 lock: true,
234 block_size: 512,
235 id: None,
236 #[cfg(windows)]
237 io_concurrency: NonZeroU32::new(1).unwrap(),
238 multiple_workers: false,
239 async_executor: None,
240 packed_queue: false,
241 bootindex: Some(5),
242 pci_address: None,
243 }
244 );
245
246 // Explicitly-specified path.
247 let params = from_block_arg("path=/path/to/disk.img").unwrap();
248 assert_eq!(
249 params,
250 DiskOption {
251 path: "/path/to/disk.img".into(),
252 read_only: false,
253 root: false,
254 sparse: true,
255 direct: false,
256 lock: true,
257 block_size: 512,
258 id: None,
259 #[cfg(windows)]
260 io_concurrency: NonZeroU32::new(1).unwrap(),
261 multiple_workers: false,
262 async_executor: None,
263 packed_queue: false,
264 bootindex: None,
265 pci_address: None,
266 }
267 );
268
269 // read_only
270 let params = from_block_arg("/some/path.img,ro").unwrap();
271 assert_eq!(
272 params,
273 DiskOption {
274 path: "/some/path.img".into(),
275 read_only: true,
276 root: false,
277 sparse: true,
278 direct: false,
279 lock: true,
280 block_size: 512,
281 id: None,
282 #[cfg(windows)]
283 io_concurrency: NonZeroU32::new(1).unwrap(),
284 multiple_workers: false,
285 async_executor: None,
286 packed_queue: false,
287 bootindex: None,
288 pci_address: None,
289 }
290 );
291
292 // root
293 let params = from_block_arg("/some/path.img,root").unwrap();
294 assert_eq!(
295 params,
296 DiskOption {
297 path: "/some/path.img".into(),
298 read_only: false,
299 root: true,
300 sparse: true,
301 direct: false,
302 lock: true,
303 block_size: 512,
304 id: None,
305 #[cfg(windows)]
306 io_concurrency: NonZeroU32::new(1).unwrap(),
307 multiple_workers: false,
308 async_executor: None,
309 packed_queue: false,
310 bootindex: None,
311 pci_address: None,
312 }
313 );
314
315 // sparse
316 let params = from_block_arg("/some/path.img,sparse").unwrap();
317 assert_eq!(
318 params,
319 DiskOption {
320 path: "/some/path.img".into(),
321 read_only: false,
322 root: false,
323 sparse: true,
324 direct: false,
325 lock: true,
326 block_size: 512,
327 id: None,
328 #[cfg(windows)]
329 io_concurrency: NonZeroU32::new(1).unwrap(),
330 multiple_workers: false,
331 async_executor: None,
332 packed_queue: false,
333 bootindex: None,
334 pci_address: None,
335 }
336 );
337 let params = from_block_arg("/some/path.img,sparse=false").unwrap();
338 assert_eq!(
339 params,
340 DiskOption {
341 path: "/some/path.img".into(),
342 read_only: false,
343 root: false,
344 sparse: false,
345 direct: false,
346 lock: true,
347 block_size: 512,
348 id: None,
349 #[cfg(windows)]
350 io_concurrency: NonZeroU32::new(1).unwrap(),
351 multiple_workers: false,
352 async_executor: None,
353 packed_queue: false,
354 bootindex: None,
355 pci_address: None,
356 }
357 );
358
359 // direct
360 let params = from_block_arg("/some/path.img,direct").unwrap();
361 assert_eq!(
362 params,
363 DiskOption {
364 path: "/some/path.img".into(),
365 read_only: false,
366 root: false,
367 sparse: true,
368 direct: true,
369 lock: true,
370 block_size: 512,
371 id: None,
372 #[cfg(windows)]
373 io_concurrency: NonZeroU32::new(1).unwrap(),
374 multiple_workers: false,
375 async_executor: None,
376 packed_queue: false,
377 bootindex: None,
378 pci_address: None,
379 }
380 );
381
382 // o_direct (deprecated, kept for backward compatibility)
383 let params = from_block_arg("/some/path.img,o_direct").unwrap();
384 assert_eq!(
385 params,
386 DiskOption {
387 path: "/some/path.img".into(),
388 read_only: false,
389 root: false,
390 sparse: true,
391 direct: true,
392 lock: true,
393 block_size: 512,
394 id: None,
395 #[cfg(windows)]
396 io_concurrency: NonZeroU32::new(1).unwrap(),
397 multiple_workers: false,
398 async_executor: None,
399 packed_queue: false,
400 bootindex: None,
401 pci_address: None,
402 }
403 );
404
405 // block-size
406 let params = from_block_arg("/some/path.img,block-size=128").unwrap();
407 assert_eq!(
408 params,
409 DiskOption {
410 path: "/some/path.img".into(),
411 read_only: false,
412 root: false,
413 sparse: true,
414 direct: false,
415 lock: true,
416 block_size: 128,
417 id: None,
418 #[cfg(windows)]
419 io_concurrency: NonZeroU32::new(1).unwrap(),
420 multiple_workers: false,
421 async_executor: None,
422 packed_queue: false,
423 bootindex: None,
424 pci_address: None,
425 }
426 );
427
428 // block_size (deprecated, kept for backward compatibility)
429 let params = from_block_arg("/some/path.img,block_size=128").unwrap();
430 assert_eq!(
431 params,
432 DiskOption {
433 path: "/some/path.img".into(),
434 read_only: false,
435 root: false,
436 sparse: true,
437 direct: false,
438 lock: true,
439 block_size: 128,
440 id: None,
441 async_executor: None,
442 #[cfg(windows)]
443 io_concurrency: NonZeroU32::new(1).unwrap(),
444 multiple_workers: false,
445 packed_queue: false,
446 bootindex: None,
447 pci_address: None,
448 }
449 );
450
451 // io_concurrency
452 #[cfg(windows)]
453 {
454 let params = from_block_arg("/some/path.img,io_concurrency=4").unwrap();
455 assert_eq!(
456 params,
457 DiskOption {
458 path: "/some/path.img".into(),
459 read_only: false,
460 root: false,
461 sparse: true,
462 direct: false,
463 lock: true,
464 block_size: 512,
465 id: None,
466 io_concurrency: NonZeroU32::new(4).unwrap(),
467 multiple_workers: false,
468 async_executor: None,
469 packed_queue: false,
470 bootindex: None,
471 pci_address: None,
472 }
473 );
474 let params = from_block_arg("/some/path.img,async-executor=overlapped").unwrap();
475 assert_eq!(
476 params,
477 DiskOption {
478 path: "/some/path.img".into(),
479 read_only: false,
480 root: false,
481 sparse: true,
482 direct: false,
483 lock: true,
484 block_size: 512,
485 id: None,
486 io_concurrency: NonZeroU32::new(1).unwrap(),
487 multiple_workers: false,
488 async_executor: Some(ExecutorKindSys::Overlapped { concurrency: None }.into()),
489 packed_queue: false,
490 bootindex: None,
491 pci_address: None,
492 }
493 );
494 let params =
495 from_block_arg("/some/path.img,async-executor=\"overlapped,concurrency=4\"")
496 .unwrap();
497 assert_eq!(
498 params,
499 DiskOption {
500 path: "/some/path.img".into(),
501 read_only: false,
502 root: false,
503 sparse: true,
504 direct: false,
505 lock: true,
506 block_size: 512,
507 id: None,
508 io_concurrency: NonZeroU32::new(1).unwrap(),
509 multiple_workers: false,
510 async_executor: Some(
511 ExecutorKindSys::Overlapped {
512 concurrency: Some(4)
513 }
514 .into()
515 ),
516 packed_queue: false,
517 bootindex: None,
518 pci_address: None,
519 }
520 );
521 }
522
523 // id
524 let params = from_block_arg("/some/path.img,id=DISK").unwrap();
525 assert_eq!(
526 params,
527 DiskOption {
528 path: "/some/path.img".into(),
529 read_only: false,
530 root: false,
531 sparse: true,
532 direct: false,
533 lock: true,
534 block_size: 512,
535 id: Some(*b"DISK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
536 #[cfg(windows)]
537 io_concurrency: NonZeroU32::new(1).unwrap(),
538 multiple_workers: false,
539 async_executor: None,
540 packed_queue: false,
541 bootindex: None,
542 pci_address: None,
543 }
544 );
545 let err = from_block_arg("/some/path.img,id=DISK_ID_IS_WAY_TOO_LONG").unwrap_err();
546 assert_eq!(
547 err,
548 ParseError {
549 kind: ErrorKind::SerdeError("disk id must be 20 or fewer characters".into()),
550 pos: 0,
551 }
552 );
553
554 // async-executor
555 #[cfg(windows)]
556 let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Handle.into(), "handle");
557 #[cfg(any(target_os = "android", target_os = "linux"))]
558 let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Fd.into(), "epoll");
559 let params =
560 from_block_arg(&format!("/some/path.img,async-executor={ex_kind_opt}")).unwrap();
561 assert_eq!(
562 params,
563 DiskOption {
564 path: "/some/path.img".into(),
565 read_only: false,
566 root: false,
567 sparse: true,
568 direct: false,
569 lock: true,
570 block_size: 512,
571 id: None,
572 #[cfg(windows)]
573 io_concurrency: NonZeroU32::new(1).unwrap(),
574 multiple_workers: false,
575 async_executor: Some(ex_kind),
576 packed_queue: false,
577 bootindex: None,
578 pci_address: None,
579 }
580 );
581
582 // packed queue
583 let params = from_block_arg("/path/to/disk.img,packed-queue").unwrap();
584 assert_eq!(
585 params,
586 DiskOption {
587 path: "/path/to/disk.img".into(),
588 read_only: false,
589 root: false,
590 sparse: true,
591 direct: false,
592 lock: true,
593 block_size: 512,
594 id: None,
595 #[cfg(windows)]
596 io_concurrency: NonZeroU32::new(1).unwrap(),
597 multiple_workers: false,
598 async_executor: None,
599 packed_queue: true,
600 bootindex: None,
601 pci_address: None,
602 }
603 );
604
605 // pci-address
606 let params = from_block_arg("/path/to/disk.img,pci-address=00:01.1").unwrap();
607 assert_eq!(
608 params,
609 DiskOption {
610 path: "/path/to/disk.img".into(),
611 read_only: false,
612 root: false,
613 sparse: true,
614 direct: false,
615 lock: true,
616 block_size: 512,
617 id: None,
618 #[cfg(windows)]
619 io_concurrency: NonZeroU32::new(1).unwrap(),
620 multiple_workers: false,
621 async_executor: None,
622 packed_queue: false,
623 bootindex: None,
624 pci_address: Some(PciAddress {
625 bus: 0,
626 dev: 1,
627 func: 1,
628 }),
629 }
630 );
631
632 // lock=true
633 let params = from_block_arg("/path/to/disk.img,lock=true").unwrap();
634 assert_eq!(
635 params,
636 DiskOption {
637 path: "/path/to/disk.img".into(),
638 read_only: false,
639 root: false,
640 sparse: true,
641 direct: false,
642 lock: true,
643 block_size: 512,
644 id: None,
645 #[cfg(windows)]
646 io_concurrency: NonZeroU32::new(1).unwrap(),
647 multiple_workers: false,
648 async_executor: None,
649 packed_queue: false,
650 bootindex: None,
651 pci_address: None,
652 }
653 );
654 // lock=false
655 let params = from_block_arg("/path/to/disk.img,lock=false").unwrap();
656 assert_eq!(
657 params,
658 DiskOption {
659 path: "/path/to/disk.img".into(),
660 read_only: false,
661 root: false,
662 sparse: true,
663 direct: false,
664 lock: false,
665 block_size: 512,
666 id: None,
667 #[cfg(windows)]
668 io_concurrency: NonZeroU32::new(1).unwrap(),
669 multiple_workers: false,
670 async_executor: None,
671 packed_queue: false,
672 bootindex: None,
673 pci_address: None,
674 }
675 );
676
677 // All together
678 let params = from_block_arg(&format!(
679 "/some/path.img,block_size=256,ro,root,sparse=false,id=DISK_LABEL\
680 ,direct,async-executor={ex_kind_opt},packed-queue=false,pci-address=00:01.1"
681 ))
682 .unwrap();
683 assert_eq!(
684 params,
685 DiskOption {
686 path: "/some/path.img".into(),
687 read_only: true,
688 root: true,
689 sparse: false,
690 direct: true,
691 lock: true,
692 block_size: 256,
693 id: Some(*b"DISK_LABEL\0\0\0\0\0\0\0\0\0\0"),
694 #[cfg(windows)]
695 io_concurrency: NonZeroU32::new(1).unwrap(),
696 multiple_workers: false,
697 async_executor: Some(ex_kind),
698 packed_queue: false,
699 bootindex: None,
700 pci_address: Some(PciAddress {
701 bus: 0,
702 dev: 1,
703 func: 1,
704 }),
705 }
706 );
707 }
708
709 #[test]
diskoption_serialize_deserialize()710 fn diskoption_serialize_deserialize() {
711 // With id == None
712 let original = DiskOption {
713 path: "./rootfs".into(),
714 read_only: false,
715 root: false,
716 sparse: true,
717 direct: false,
718 lock: true,
719 block_size: 512,
720 id: None,
721 #[cfg(windows)]
722 io_concurrency: NonZeroU32::new(1).unwrap(),
723 multiple_workers: false,
724 async_executor: None,
725 packed_queue: false,
726 bootindex: None,
727 pci_address: None,
728 };
729 let json = serde_json::to_string(&original).unwrap();
730 let deserialized = serde_json::from_str(&json).unwrap();
731 assert_eq!(original, deserialized);
732
733 // With id == Some
734 let original = DiskOption {
735 path: "./rootfs".into(),
736 read_only: false,
737 root: false,
738 sparse: true,
739 direct: false,
740 lock: true,
741 block_size: 512,
742 id: Some(*b"BLK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
743 #[cfg(windows)]
744 io_concurrency: NonZeroU32::new(1).unwrap(),
745 multiple_workers: false,
746 async_executor: Some(ExecutorKind::default()),
747 packed_queue: false,
748 bootindex: None,
749 pci_address: None,
750 };
751 let json = serde_json::to_string(&original).unwrap();
752 let deserialized = serde_json::from_str(&json).unwrap();
753 assert_eq!(original, deserialized);
754
755 // With id taking all the available space.
756 let original = DiskOption {
757 path: "./rootfs".into(),
758 read_only: false,
759 root: false,
760 sparse: true,
761 direct: false,
762 lock: true,
763 block_size: 512,
764 id: Some(*b"QWERTYUIOPASDFGHJKL:"),
765 #[cfg(windows)]
766 io_concurrency: NonZeroU32::new(1).unwrap(),
767 multiple_workers: false,
768 async_executor: Some(ExecutorKind::default()),
769 packed_queue: false,
770 bootindex: None,
771 pci_address: None,
772 };
773 let json = serde_json::to_string(&original).unwrap();
774 let deserialized = serde_json::from_str(&json).unwrap();
775 assert_eq!(original, deserialized);
776 }
777 }
778