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")]
93pub struct DiskOption {
94    pub path: PathBuf,
95    #[serde(default, rename = "ro")]
96    pub read_only: bool,
97    #[serde(default)]
98    /// Whether this disk should be the root device. Can only be set once. Only useful for adding
99    /// specific command-line options.
100    pub root: bool,
101    #[serde(default = "block_option_sparse_default")]
102    pub sparse: bool,
103    // camel_case variant allowed for backward compatibility.
104    #[serde(default, alias = "o_direct")]
105    pub direct: bool,
106    /// Whether to lock the disk files. Uses flock on Unix and FILE_SHARE_* flags on Windows.
107    #[serde(default = "block_option_lock_default")]
108    pub lock: bool,
109    // camel_case variant allowed for backward compatibility.
110    #[serde(default = "block_option_block_size_default", alias = "block_size")]
111    pub block_size: u32,
112    #[serde(
113        default,
114        serialize_with = "serialize_disk_id",
115        deserialize_with = "deserialize_disk_id"
116    )]
117    pub id: Option<[u8; DISK_ID_LEN]>,
118    // Deprecated: Use async_executor=overlapped[concurrency=N]"
119    // camel_case variant allowed for backward compatibility.
120    #[cfg(windows)]
121    #[serde(
122        default = "block_option_io_concurrency_default",
123        alias = "io_concurrency"
124    )]
125    pub io_concurrency: NonZeroU32,
126    #[serde(default)]
127    /// Experimental option to run multiple worker threads in parallel. If false, only single
128    /// thread runs by default. Note this option is not effective for vhost-user blk device.
129    pub multiple_workers: bool,
130    #[serde(default, alias = "async_executor")]
131    /// The async executor kind to simulate the block device with. This option takes
132    /// precedence over the async executor kind specified by the subcommand's option.
133    /// If None, the default or the specified by the subcommand's option would be used.
134    pub async_executor: Option<ExecutorKind>,
135    #[serde(default)]
136    //Option to choose virtqueue type. If true, use the packed virtqueue. If false
137    //or by default, use split virtqueue
138    pub packed_queue: bool,
139
140    /// Specify the boot index for this device that the BIOS will use when attempting to boot from
141    /// bootable devices. For example, if bootindex=2, then the BIOS will attempt to boot from the
142    /// device right after booting from the device with bootindex=1 fails.
143    pub bootindex: Option<usize>,
144
145    /// Specify PCI address will be used to attach this device
146    pub pci_address: Option<PciAddress>,
147    /// Specify whether to cache reads/writes.
148    #[serde(default)]
149    pub dontcache: bool,
150}
151
152impl Default for DiskOption {
153    fn default() -> Self {
154        Self {
155            path: PathBuf::new(),
156            read_only: false,
157            root: false,
158            sparse: block_option_sparse_default(),
159            direct: false,
160            lock: block_option_lock_default(),
161            block_size: block_option_block_size_default(),
162            id: None,
163            #[cfg(windows)]
164            io_concurrency: block_option_io_concurrency_default(),
165            multiple_workers: false,
166            async_executor: None,
167            packed_queue: false,
168            bootindex: None,
169            pci_address: None,
170            dontcache: false,
171        }
172    }
173}
174
175impl VirtioDeviceModule for DiskOption {
176    fn sort_name(&self) -> &'static str {
177        "block"
178    }
179
180    fn create(&self, args: &mut VirtioDeviceArgs<'_>) -> anyhow::Result<Box<dyn VirtioDevice>> {
181        base::info!("Trying to attach block device: {}", self.path.display());
182
183        let (disk_host_tube, disk_device_tube) = Tube::pair().context("failed to create tube")?;
184        (args.add_control_tube)(AnyControlTube::Disk(disk_host_tube));
185
186        let dev = BlockAsync::new(
187            devices::virtio::base_features(args.protection_type),
188            self.open()?,
189            self,
190            Some(disk_device_tube),
191            None,
192            None,
193        )
194        .context("failed to create block device")?;
195
196        Ok(Box::new(dev))
197    }
198
199    #[cfg(any(target_os = "android", target_os = "linux"))]
200    fn create_jail(
201        &self,
202        jail_config: &jail::JailConfig,
203    ) -> anyhow::Result<Option<minijail::Minijail>> {
204        let jail = jail::simple_jail(
205            Some(jail_config),
206            &devices::virtio::VirtioDeviceType::Regular.seccomp_policy_file("block"),
207        )?;
208        Ok(jail)
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    #[cfg(any(target_os = "android", target_os = "linux"))]
215    use cros_async::sys::linux::ExecutorKindSys;
216    #[cfg(windows)]
217    use cros_async::sys::windows::ExecutorKindSys;
218    use serde_keyvalue::*;
219
220    use super::*;
221
222    fn from_block_arg(options: &str) -> Result<DiskOption, ParseError> {
223        from_key_values(options)
224    }
225
226    #[test]
227    fn check_default_matches_from_key_values() {
228        let path = "/path/to/disk.img";
229        let disk = DiskOption {
230            path: PathBuf::from(path),
231            ..Default::default()
232        };
233        assert_eq!(disk, from_key_values(path).unwrap());
234    }
235
236    #[test]
237    fn params_from_key_values() {
238        // Path argument is mandatory.
239        let err = from_block_arg("").unwrap_err();
240        assert_eq!(
241            err,
242            ParseError {
243                kind: ErrorKind::SerdeError("missing field `path`".into()),
244                pos: 0,
245            }
246        );
247
248        // Path is the default argument.
249        let params = from_block_arg("/path/to/disk.img").unwrap();
250        assert_eq!(
251            params,
252            DiskOption {
253                path: "/path/to/disk.img".into(),
254                ..Default::default()
255            }
256        );
257
258        // bootindex
259        let params = from_block_arg("/path/to/disk.img,bootindex=5").unwrap();
260        assert_eq!(
261            params,
262            DiskOption {
263                path: "/path/to/disk.img".into(),
264                bootindex: Some(5),
265                ..Default::default()
266            }
267        );
268
269        // Explicitly-specified path.
270        let params = from_block_arg("path=/path/to/disk.img").unwrap();
271        assert_eq!(
272            params,
273            DiskOption {
274                path: "/path/to/disk.img".into(),
275                ..Default::default()
276            }
277        );
278
279        // read_only
280        let params = from_block_arg("/some/path.img,ro").unwrap();
281        assert_eq!(
282            params,
283            DiskOption {
284                path: "/some/path.img".into(),
285                read_only: true,
286                ..Default::default()
287            }
288        );
289
290        // root
291        let params = from_block_arg("/some/path.img,root").unwrap();
292        assert_eq!(
293            params,
294            DiskOption {
295                path: "/some/path.img".into(),
296                root: true,
297                ..Default::default()
298            }
299        );
300
301        // sparse
302        let params = from_block_arg("/some/path.img,sparse").unwrap();
303        assert_eq!(
304            params,
305            DiskOption {
306                path: "/some/path.img".into(),
307                ..Default::default()
308            }
309        );
310        let params = from_block_arg("/some/path.img,sparse=false").unwrap();
311        assert_eq!(
312            params,
313            DiskOption {
314                path: "/some/path.img".into(),
315                sparse: false,
316                ..Default::default()
317            }
318        );
319
320        // direct
321        let params = from_block_arg("/some/path.img,direct").unwrap();
322        assert_eq!(
323            params,
324            DiskOption {
325                path: "/some/path.img".into(),
326                direct: true,
327                ..Default::default()
328            }
329        );
330
331        // o_direct (deprecated, kept for backward compatibility)
332        let params = from_block_arg("/some/path.img,o_direct").unwrap();
333        assert_eq!(
334            params,
335            DiskOption {
336                path: "/some/path.img".into(),
337                direct: true,
338                ..Default::default()
339            }
340        );
341
342        // block-size
343        let params = from_block_arg("/some/path.img,block-size=128").unwrap();
344        assert_eq!(
345            params,
346            DiskOption {
347                path: "/some/path.img".into(),
348                block_size: 128,
349                ..Default::default()
350            }
351        );
352
353        // block_size (deprecated, kept for backward compatibility)
354        let params = from_block_arg("/some/path.img,block_size=128").unwrap();
355        assert_eq!(
356            params,
357            DiskOption {
358                path: "/some/path.img".into(),
359                block_size: 128,
360                ..Default::default()
361            }
362        );
363
364        // io_concurrency
365        #[cfg(windows)]
366        {
367            let params = from_block_arg("/some/path.img,io_concurrency=4").unwrap();
368            assert_eq!(
369                params,
370                DiskOption {
371                    path: "/some/path.img".into(),
372                    io_concurrency: NonZeroU32::new(4).unwrap(),
373                    ..Default::default()
374                }
375            );
376            let params = from_block_arg("/some/path.img,async-executor=overlapped").unwrap();
377            assert_eq!(
378                params,
379                DiskOption {
380                    path: "/some/path.img".into(),
381                    async_executor: Some(ExecutorKindSys::Overlapped { concurrency: None }.into()),
382                    ..Default::default()
383                }
384            );
385            let params =
386                from_block_arg("/some/path.img,async-executor=\"overlapped,concurrency=4\"")
387                    .unwrap();
388            assert_eq!(
389                params,
390                DiskOption {
391                    path: "/some/path.img".into(),
392                    async_executor: Some(
393                        ExecutorKindSys::Overlapped {
394                            concurrency: Some(4)
395                        }
396                        .into()
397                    ),
398                    ..Default::default()
399                }
400            );
401        }
402
403        // id
404        let params = from_block_arg("/some/path.img,id=DISK").unwrap();
405        assert_eq!(
406            params,
407            DiskOption {
408                path: "/some/path.img".into(),
409                id: Some(*b"DISK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
410                ..Default::default()
411            }
412        );
413        let err = from_block_arg("/some/path.img,id=DISK_ID_IS_WAY_TOO_LONG").unwrap_err();
414        assert_eq!(
415            err,
416            ParseError {
417                kind: ErrorKind::SerdeError("disk id must be 20 or fewer characters".into()),
418                pos: 0,
419            }
420        );
421
422        // async-executor
423        #[cfg(windows)]
424        let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Handle.into(), "handle");
425        #[cfg(any(target_os = "android", target_os = "linux"))]
426        let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Fd.into(), "epoll");
427        let params =
428            from_block_arg(&format!("/some/path.img,async-executor={ex_kind_opt}")).unwrap();
429        assert_eq!(
430            params,
431            DiskOption {
432                path: "/some/path.img".into(),
433                async_executor: Some(ex_kind),
434                ..Default::default()
435            }
436        );
437
438        // packed queue
439        let params = from_block_arg("/path/to/disk.img,packed-queue").unwrap();
440        assert_eq!(
441            params,
442            DiskOption {
443                path: "/path/to/disk.img".into(),
444                packed_queue: true,
445                ..Default::default()
446            }
447        );
448
449        // pci-address
450        let params = from_block_arg("/path/to/disk.img,pci-address=00:01.1").unwrap();
451        assert_eq!(
452            params,
453            DiskOption {
454                path: "/path/to/disk.img".into(),
455                pci_address: Some(PciAddress {
456                    bus: 0,
457                    dev: 1,
458                    func: 1,
459                }),
460                ..Default::default()
461            }
462        );
463
464        // lock=true
465        let params = from_block_arg("/path/to/disk.img,lock=true").unwrap();
466        assert_eq!(
467            params,
468            DiskOption {
469                path: "/path/to/disk.img".into(),
470                ..Default::default()
471            }
472        );
473        // lock=false
474        let params = from_block_arg("/path/to/disk.img,lock=false").unwrap();
475        assert_eq!(
476            params,
477            DiskOption {
478                path: "/path/to/disk.img".into(),
479                lock: false,
480                ..Default::default()
481            }
482        );
483
484        // dontcache
485        let params = from_block_arg("/path/to/disk.img,dontcache").unwrap();
486        assert_eq!(
487            params,
488            DiskOption {
489                path: "/path/to/disk.img".into(),
490                dontcache: true,
491                ..Default::default()
492            }
493        );
494        let params = from_block_arg("/path/to/disk.img,dontcache=false").unwrap();
495        assert_eq!(
496            params,
497            DiskOption {
498                path: "/path/to/disk.img".into(),
499                dontcache: false,
500                ..Default::default()
501            }
502        );
503
504        // All together
505        let params = from_block_arg(&format!(
506            "/some/path.img,block_size=256,ro,root,sparse=false,id=DISK_LABEL\
507             ,direct,async-executor={ex_kind_opt},packed-queue=false,pci-address=00:01.1"
508        ))
509        .unwrap();
510        assert_eq!(
511            params,
512            DiskOption {
513                path: "/some/path.img".into(),
514                read_only: true,
515                root: true,
516                sparse: false,
517                direct: true,
518                block_size: 256,
519                id: Some(*b"DISK_LABEL\0\0\0\0\0\0\0\0\0\0"),
520                async_executor: Some(ex_kind),
521                pci_address: Some(PciAddress {
522                    bus: 0,
523                    dev: 1,
524                    func: 1,
525                }),
526                ..Default::default()
527            }
528        );
529    }
530
531    #[test]
532    fn diskoption_serialize_deserialize() {
533        // With id == None
534        let original = DiskOption {
535            path: "./rootfs".into(),
536            ..Default::default()
537        };
538        let json = serde_json::to_string(&original).unwrap();
539        let deserialized = serde_json::from_str(&json).unwrap();
540        assert_eq!(original, deserialized);
541
542        // With id == Some
543        let original = DiskOption {
544            path: "./rootfs".into(),
545            id: Some(*b"BLK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
546            async_executor: Some(ExecutorKind::default()),
547            ..Default::default()
548        };
549        let json = serde_json::to_string(&original).unwrap();
550        let deserialized = serde_json::from_str(&json).unwrap();
551        assert_eq!(original, deserialized);
552
553        // With id taking all the available space.
554        let original = DiskOption {
555            path: "./rootfs".into(),
556            id: Some(*b"QWERTYUIOPASDFGHJKL:"),
557            async_executor: Some(ExecutorKind::default()),
558            ..Default::default()
559        };
560        let json = serde_json::to_string(&original).unwrap();
561        let deserialized = serde_json::from_str(&json).unwrap();
562        assert_eq!(original, deserialized);
563    }
564}