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