xref: /aosp_15_r20/external/crosvm/devices/src/virtio/block/mod.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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