device_virtio_block/
lib.rs

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)]
6use std::num::NonZeroU32;
7use std::path::PathBuf;
8
9use anyhow::Context;
10use base::Tube;
11use cros_async::ExecutorKind;
12use devices::virtio::VirtioDevice;
13use devices::PciAddress;
14use devices::VirtioDeviceArgs;
15use devices::VirtioDeviceModule;
16use serde::Deserialize;
17use serde::Deserializer;
18use serde::Serialize;
19use serde::Serializer;
20use vm_control::AnyControlTube;
21
22pub mod asynchronous;
23pub mod sys;
24pub mod vhost_user;
25
26pub use asynchronous::BlockAsync;
27pub use vhost_user::run_block_device;
28pub use vhost_user::Options as BlockOptions;
29
30fn block_option_sparse_default() -> bool {
31    true
32}
33fn block_option_lock_default() -> bool {
34    true
35}
36fn block_option_block_size_default() -> u32 {
37    512
38}
39// TODO(b/237829580): Move to sys module once virtio block sys is refactored to
40// match the style guide.
41#[cfg(windows)]
42fn block_option_io_concurrency_default() -> NonZeroU32 {
43    NonZeroU32::new(1).unwrap()
44}
45
46/// Maximum length of a `DiskOption` identifier.
47///
48/// This is based on the virtio-block ID length limit.
49pub const DISK_ID_LEN: usize = 20;
50
51pub fn serialize_disk_id<S: Serializer>(
52    id: &Option<[u8; DISK_ID_LEN]>,
53    serializer: S,
54) -> Result<S::Ok, S::Error> {
55    match id {
56        None => serializer.serialize_none(),
57        Some(id) => {
58            // Find the first zero byte in the id.
59            let len = id.iter().position(|v| *v == 0).unwrap_or(DISK_ID_LEN);
60            serializer.serialize_some(
61                std::str::from_utf8(&id[0..len])
62                    .map_err(|e| serde::ser::Error::custom(e.to_string()))?,
63            )
64        }
65    }
66}
67
68fn deserialize_disk_id<'de, D: Deserializer<'de>>(
69    deserializer: D,
70) -> Result<Option<[u8; DISK_ID_LEN]>, D::Error> {
71    let id = Option::<String>::deserialize(deserializer)?;
72
73    match id {
74        None => Ok(None),
75        Some(id) => {
76            if id.len() > DISK_ID_LEN {
77                return Err(serde::de::Error::custom(format!(
78                    "disk id must be {DISK_ID_LEN} or fewer characters"
79                )));
80            }
81
82            let mut ret = [0u8; DISK_ID_LEN];
83            // Slicing id to value's length will never panic
84            // because we checked that value will fit into id above.
85            ret[..id.len()].copy_from_slice(id.as_bytes());
86            Ok(Some(ret))
87        }
88    }
89}
90
91#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, serde_keyvalue::FromKeyValues)]
92#[serde(deny_unknown_fields, rename_all = "kebab-case")]
93// LINT.IfChange
94pub struct DiskOption {
95    pub path: PathBuf,
96    #[serde(default, rename = "ro")]
97    pub read_only: bool,
98    #[serde(default)]
99    /// Whether this disk should be the root device. Can only be set once. Only useful for adding
100    /// specific command-line options.
101    pub root: bool,
102    #[serde(default = "block_option_sparse_default")]
103    pub sparse: bool,
104    // camel_case variant allowed for backward compatibility.
105    #[serde(default, alias = "o_direct")]
106    pub direct: bool,
107    /// Whether to lock the disk files. Uses flock on Unix and FILE_SHARE_* flags on Windows.
108    #[serde(default = "block_option_lock_default")]
109    pub lock: bool,
110    // camel_case variant allowed for backward compatibility.
111    #[serde(default = "block_option_block_size_default", alias = "block_size")]
112    pub block_size: u32,
113    #[serde(
114        default,
115        serialize_with = "serialize_disk_id",
116        deserialize_with = "deserialize_disk_id"
117    )]
118    pub id: Option<[u8; DISK_ID_LEN]>,
119    // Deprecated: Use async_executor=overlapped[concurrency=N]"
120    // camel_case variant allowed for backward compatibility.
121    #[cfg(windows)]
122    #[serde(
123        default = "block_option_io_concurrency_default",
124        alias = "io_concurrency"
125    )]
126    pub io_concurrency: NonZeroU32,
127    #[serde(default)]
128    /// Experimental option to run multiple worker threads in parallel. If false, only single
129    /// thread runs by default. Note this option is not effective for vhost-user blk device.
130    pub multiple_workers: bool,
131    #[serde(default, alias = "async_executor")]
132    /// The async executor kind to simulate the block device with. This option takes
133    /// precedence over the async executor kind specified by the subcommand's option.
134    /// If None, the default or the specified by the subcommand's option would be used.
135    pub async_executor: Option<ExecutorKind>,
136    #[serde(default)]
137    //Option to choose virtqueue type. If true, use the packed virtqueue. If false
138    //or by default, use split virtqueue
139    pub packed_queue: bool,
140
141    /// Specify the boot index for this device that the BIOS will use when attempting to boot from
142    /// bootable devices. For example, if bootindex=2, then the BIOS will attempt to boot from the
143    /// device right after booting from the device with bootindex=1 fails.
144    pub bootindex: Option<usize>,
145
146    /// Specify PCI address will be used to attach this device
147    pub pci_address: Option<PciAddress>,
148    /// Specify whether to cache reads/writes.
149    #[serde(default)]
150    pub dontcache: bool,
151}
152// LINT.ThenChange(/src/crosvm/cmdline.rs)
153
154impl Default for DiskOption {
155    fn default() -> Self {
156        Self {
157            path: PathBuf::new(),
158            read_only: false,
159            root: false,
160            sparse: block_option_sparse_default(),
161            direct: false,
162            lock: block_option_lock_default(),
163            block_size: block_option_block_size_default(),
164            id: None,
165            #[cfg(windows)]
166            io_concurrency: block_option_io_concurrency_default(),
167            multiple_workers: false,
168            async_executor: None,
169            packed_queue: false,
170            bootindex: None,
171            pci_address: None,
172            dontcache: false,
173        }
174    }
175}
176
177impl VirtioDeviceModule for DiskOption {
178    fn sort_name(&self) -> &'static str {
179        "block"
180    }
181
182    fn create(&self, args: &mut VirtioDeviceArgs<'_>) -> anyhow::Result<Box<dyn VirtioDevice>> {
183        base::info!("Trying to attach block device: {}", self.path.display());
184
185        let (disk_host_tube, disk_device_tube) = Tube::pair().context("failed to create tube")?;
186        (args.add_control_tube)(AnyControlTube::Disk(disk_host_tube));
187
188        let dev = BlockAsync::new(
189            devices::virtio::base_features(args.protection_type),
190            self.open()?,
191            self,
192            Some(disk_device_tube),
193            None,
194            None,
195        )
196        .context("failed to create block device")?;
197
198        Ok(Box::new(dev))
199    }
200
201    #[cfg(any(target_os = "android", target_os = "linux"))]
202    fn create_jail(
203        &self,
204        jail_config: &jail::JailConfig,
205    ) -> anyhow::Result<Option<minijail::Minijail>> {
206        let jail = jail::simple_jail(
207            Some(jail_config),
208            &devices::virtio::VirtioDeviceType::Regular.seccomp_policy_file("block"),
209        )?;
210        Ok(jail)
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    #[cfg(any(target_os = "android", target_os = "linux"))]
217    use cros_async::sys::linux::ExecutorKindSys;
218    #[cfg(windows)]
219    use cros_async::sys::windows::ExecutorKindSys;
220    use serde_keyvalue::*;
221
222    use super::*;
223
224    fn from_block_arg(options: &str) -> Result<DiskOption, ParseError> {
225        from_key_values(options)
226    }
227
228    #[test]
229    fn check_default_matches_from_key_values() {
230        let path = "/path/to/disk.img";
231        let disk = DiskOption {
232            path: PathBuf::from(path),
233            ..Default::default()
234        };
235        assert_eq!(disk, from_key_values(path).unwrap());
236    }
237
238    #[test]
239    fn params_from_key_values() {
240        // Path argument is mandatory.
241        let err = from_block_arg("").unwrap_err();
242        assert_eq!(
243            err,
244            ParseError {
245                kind: ErrorKind::SerdeError("missing field `path`".into()),
246                pos: 0,
247            }
248        );
249
250        // Path is the default argument.
251        let params = from_block_arg("/path/to/disk.img").unwrap();
252        assert_eq!(
253            params,
254            DiskOption {
255                path: "/path/to/disk.img".into(),
256                ..Default::default()
257            }
258        );
259
260        // bootindex
261        let params = from_block_arg("/path/to/disk.img,bootindex=5").unwrap();
262        assert_eq!(
263            params,
264            DiskOption {
265                path: "/path/to/disk.img".into(),
266                bootindex: Some(5),
267                ..Default::default()
268            }
269        );
270
271        // Explicitly-specified path.
272        let params = from_block_arg("path=/path/to/disk.img").unwrap();
273        assert_eq!(
274            params,
275            DiskOption {
276                path: "/path/to/disk.img".into(),
277                ..Default::default()
278            }
279        );
280
281        // read_only
282        let params = from_block_arg("/some/path.img,ro").unwrap();
283        assert_eq!(
284            params,
285            DiskOption {
286                path: "/some/path.img".into(),
287                read_only: true,
288                ..Default::default()
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                root: true,
299                ..Default::default()
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                ..Default::default()
310            }
311        );
312        let params = from_block_arg("/some/path.img,sparse=false").unwrap();
313        assert_eq!(
314            params,
315            DiskOption {
316                path: "/some/path.img".into(),
317                sparse: false,
318                ..Default::default()
319            }
320        );
321
322        // direct
323        let params = from_block_arg("/some/path.img,direct").unwrap();
324        assert_eq!(
325            params,
326            DiskOption {
327                path: "/some/path.img".into(),
328                direct: true,
329                ..Default::default()
330            }
331        );
332
333        // o_direct (deprecated, kept for backward compatibility)
334        let params = from_block_arg("/some/path.img,o_direct").unwrap();
335        assert_eq!(
336            params,
337            DiskOption {
338                path: "/some/path.img".into(),
339                direct: true,
340                ..Default::default()
341            }
342        );
343
344        // block-size
345        let params = from_block_arg("/some/path.img,block-size=128").unwrap();
346        assert_eq!(
347            params,
348            DiskOption {
349                path: "/some/path.img".into(),
350                block_size: 128,
351                ..Default::default()
352            }
353        );
354
355        // block_size (deprecated, kept for backward compatibility)
356        let params = from_block_arg("/some/path.img,block_size=128").unwrap();
357        assert_eq!(
358            params,
359            DiskOption {
360                path: "/some/path.img".into(),
361                block_size: 128,
362                ..Default::default()
363            }
364        );
365
366        // io_concurrency
367        #[cfg(windows)]
368        {
369            let params = from_block_arg("/some/path.img,io_concurrency=4").unwrap();
370            assert_eq!(
371                params,
372                DiskOption {
373                    path: "/some/path.img".into(),
374                    io_concurrency: NonZeroU32::new(4).unwrap(),
375                    ..Default::default()
376                }
377            );
378            let params = from_block_arg("/some/path.img,async-executor=overlapped").unwrap();
379            assert_eq!(
380                params,
381                DiskOption {
382                    path: "/some/path.img".into(),
383                    async_executor: Some(ExecutorKindSys::Overlapped { concurrency: None }.into()),
384                    ..Default::default()
385                }
386            );
387            let params =
388                from_block_arg("/some/path.img,async-executor=\"overlapped,concurrency=4\"")
389                    .unwrap();
390            assert_eq!(
391                params,
392                DiskOption {
393                    path: "/some/path.img".into(),
394                    async_executor: Some(
395                        ExecutorKindSys::Overlapped {
396                            concurrency: Some(4)
397                        }
398                        .into()
399                    ),
400                    ..Default::default()
401                }
402            );
403        }
404
405        // id
406        let params = from_block_arg("/some/path.img,id=DISK").unwrap();
407        assert_eq!(
408            params,
409            DiskOption {
410                path: "/some/path.img".into(),
411                id: Some(*b"DISK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
412                ..Default::default()
413            }
414        );
415        let err = from_block_arg("/some/path.img,id=DISK_ID_IS_WAY_TOO_LONG").unwrap_err();
416        assert_eq!(
417            err,
418            ParseError {
419                kind: ErrorKind::SerdeError("disk id must be 20 or fewer characters".into()),
420                pos: 0,
421            }
422        );
423
424        // async-executor
425        #[cfg(windows)]
426        let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Handle.into(), "handle");
427        #[cfg(any(target_os = "android", target_os = "linux"))]
428        let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Fd.into(), "epoll");
429        let params =
430            from_block_arg(&format!("/some/path.img,async-executor={ex_kind_opt}")).unwrap();
431        assert_eq!(
432            params,
433            DiskOption {
434                path: "/some/path.img".into(),
435                async_executor: Some(ex_kind),
436                ..Default::default()
437            }
438        );
439
440        // packed queue
441        let params = from_block_arg("/path/to/disk.img,packed-queue").unwrap();
442        assert_eq!(
443            params,
444            DiskOption {
445                path: "/path/to/disk.img".into(),
446                packed_queue: true,
447                ..Default::default()
448            }
449        );
450
451        // pci-address
452        let params = from_block_arg("/path/to/disk.img,pci-address=00:01.1").unwrap();
453        assert_eq!(
454            params,
455            DiskOption {
456                path: "/path/to/disk.img".into(),
457                pci_address: Some(PciAddress {
458                    bus: 0,
459                    dev: 1,
460                    func: 1,
461                }),
462                ..Default::default()
463            }
464        );
465
466        // lock=true
467        let params = from_block_arg("/path/to/disk.img,lock=true").unwrap();
468        assert_eq!(
469            params,
470            DiskOption {
471                path: "/path/to/disk.img".into(),
472                ..Default::default()
473            }
474        );
475        // lock=false
476        let params = from_block_arg("/path/to/disk.img,lock=false").unwrap();
477        assert_eq!(
478            params,
479            DiskOption {
480                path: "/path/to/disk.img".into(),
481                lock: false,
482                ..Default::default()
483            }
484        );
485
486        // dontcache
487        let params = from_block_arg("/path/to/disk.img,dontcache").unwrap();
488        assert_eq!(
489            params,
490            DiskOption {
491                path: "/path/to/disk.img".into(),
492                dontcache: true,
493                ..Default::default()
494            }
495        );
496        let params = from_block_arg("/path/to/disk.img,dontcache=false").unwrap();
497        assert_eq!(
498            params,
499            DiskOption {
500                path: "/path/to/disk.img".into(),
501                dontcache: false,
502                ..Default::default()
503            }
504        );
505
506        // All together
507        let params = from_block_arg(&format!(
508            "/some/path.img,block_size=256,ro,root,sparse=false,id=DISK_LABEL\
509             ,direct,async-executor={ex_kind_opt},packed-queue=false,pci-address=00:01.1"
510        ))
511        .unwrap();
512        assert_eq!(
513            params,
514            DiskOption {
515                path: "/some/path.img".into(),
516                read_only: true,
517                root: true,
518                sparse: false,
519                direct: true,
520                block_size: 256,
521                id: Some(*b"DISK_LABEL\0\0\0\0\0\0\0\0\0\0"),
522                async_executor: Some(ex_kind),
523                pci_address: Some(PciAddress {
524                    bus: 0,
525                    dev: 1,
526                    func: 1,
527                }),
528                ..Default::default()
529            }
530        );
531    }
532
533    #[test]
534    fn diskoption_serialize_deserialize() {
535        // With id == None
536        let original = DiskOption {
537            path: "./rootfs".into(),
538            ..Default::default()
539        };
540        let json = serde_json::to_string(&original).unwrap();
541        let deserialized = serde_json::from_str(&json).unwrap();
542        assert_eq!(original, deserialized);
543
544        // With id == Some
545        let original = DiskOption {
546            path: "./rootfs".into(),
547            id: Some(*b"BLK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
548            async_executor: Some(ExecutorKind::default()),
549            ..Default::default()
550        };
551        let json = serde_json::to_string(&original).unwrap();
552        let deserialized = serde_json::from_str(&json).unwrap();
553        assert_eq!(original, deserialized);
554
555        // With id taking all the available space.
556        let original = DiskOption {
557            path: "./rootfs".into(),
558            id: Some(*b"QWERTYUIOPASDFGHJKL:"),
559            async_executor: Some(ExecutorKind::default()),
560            ..Default::default()
561        };
562        let json = serde_json::to_string(&original).unwrap();
563        let deserialized = serde_json::from_str(&json).unwrap();
564        assert_eq!(original, deserialized);
565    }
566}