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