• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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