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