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}
140
141impl Default for DiskOption {
142    fn default() -> Self {
143        Self {
144            path: PathBuf::new(),
145            read_only: false,
146            root: false,
147            sparse: block_option_sparse_default(),
148            direct: false,
149            lock: block_option_lock_default(),
150            block_size: block_option_block_size_default(),
151            id: None,
152            #[cfg(windows)]
153            io_concurrency: block_option_io_concurrency_default(),
154            multiple_workers: false,
155            async_executor: None,
156            packed_queue: false,
157            bootindex: None,
158            pci_address: None,
159        }
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    #[cfg(any(target_os = "android", target_os = "linux"))]
166    use cros_async::sys::linux::ExecutorKindSys;
167    #[cfg(windows)]
168    use cros_async::sys::windows::ExecutorKindSys;
169    use serde_keyvalue::*;
170
171    use super::*;
172
173    fn from_block_arg(options: &str) -> Result<DiskOption, ParseError> {
174        from_key_values(options)
175    }
176
177    #[test]
178    fn check_default_matches_from_key_values() {
179        let path = "/path/to/disk.img";
180        let disk = DiskOption {
181            path: PathBuf::from(path),
182            ..Default::default()
183        };
184        assert_eq!(disk, from_key_values(path).unwrap());
185    }
186
187    #[test]
188    fn params_from_key_values() {
189        // Path argument is mandatory.
190        let err = from_block_arg("").unwrap_err();
191        assert_eq!(
192            err,
193            ParseError {
194                kind: ErrorKind::SerdeError("missing field `path`".into()),
195                pos: 0,
196            }
197        );
198
199        // Path is the default argument.
200        let params = from_block_arg("/path/to/disk.img").unwrap();
201        assert_eq!(
202            params,
203            DiskOption {
204                path: "/path/to/disk.img".into(),
205                ..Default::default()
206            }
207        );
208
209        // bootindex
210        let params = from_block_arg("/path/to/disk.img,bootindex=5").unwrap();
211        assert_eq!(
212            params,
213            DiskOption {
214                path: "/path/to/disk.img".into(),
215                bootindex: Some(5),
216                ..Default::default()
217            }
218        );
219
220        // Explicitly-specified path.
221        let params = from_block_arg("path=/path/to/disk.img").unwrap();
222        assert_eq!(
223            params,
224            DiskOption {
225                path: "/path/to/disk.img".into(),
226                ..Default::default()
227            }
228        );
229
230        // read_only
231        let params = from_block_arg("/some/path.img,ro").unwrap();
232        assert_eq!(
233            params,
234            DiskOption {
235                path: "/some/path.img".into(),
236                read_only: true,
237                ..Default::default()
238            }
239        );
240
241        // root
242        let params = from_block_arg("/some/path.img,root").unwrap();
243        assert_eq!(
244            params,
245            DiskOption {
246                path: "/some/path.img".into(),
247                root: true,
248                ..Default::default()
249            }
250        );
251
252        // sparse
253        let params = from_block_arg("/some/path.img,sparse").unwrap();
254        assert_eq!(
255            params,
256            DiskOption {
257                path: "/some/path.img".into(),
258                ..Default::default()
259            }
260        );
261        let params = from_block_arg("/some/path.img,sparse=false").unwrap();
262        assert_eq!(
263            params,
264            DiskOption {
265                path: "/some/path.img".into(),
266                sparse: false,
267                ..Default::default()
268            }
269        );
270
271        // direct
272        let params = from_block_arg("/some/path.img,direct").unwrap();
273        assert_eq!(
274            params,
275            DiskOption {
276                path: "/some/path.img".into(),
277                direct: true,
278                ..Default::default()
279            }
280        );
281
282        // o_direct (deprecated, kept for backward compatibility)
283        let params = from_block_arg("/some/path.img,o_direct").unwrap();
284        assert_eq!(
285            params,
286            DiskOption {
287                path: "/some/path.img".into(),
288                direct: true,
289                ..Default::default()
290            }
291        );
292
293        // block-size
294        let params = from_block_arg("/some/path.img,block-size=128").unwrap();
295        assert_eq!(
296            params,
297            DiskOption {
298                path: "/some/path.img".into(),
299                block_size: 128,
300                ..Default::default()
301            }
302        );
303
304        // block_size (deprecated, kept for backward compatibility)
305        let params = from_block_arg("/some/path.img,block_size=128").unwrap();
306        assert_eq!(
307            params,
308            DiskOption {
309                path: "/some/path.img".into(),
310                block_size: 128,
311                ..Default::default()
312            }
313        );
314
315        // io_concurrency
316        #[cfg(windows)]
317        {
318            let params = from_block_arg("/some/path.img,io_concurrency=4").unwrap();
319            assert_eq!(
320                params,
321                DiskOption {
322                    path: "/some/path.img".into(),
323                    io_concurrency: NonZeroU32::new(4).unwrap(),
324                    ..Default::default()
325                }
326            );
327            let params = from_block_arg("/some/path.img,async-executor=overlapped").unwrap();
328            assert_eq!(
329                params,
330                DiskOption {
331                    path: "/some/path.img".into(),
332                    async_executor: Some(ExecutorKindSys::Overlapped { concurrency: None }.into()),
333                    ..Default::default()
334                }
335            );
336            let params =
337                from_block_arg("/some/path.img,async-executor=\"overlapped,concurrency=4\"")
338                    .unwrap();
339            assert_eq!(
340                params,
341                DiskOption {
342                    path: "/some/path.img".into(),
343                    async_executor: Some(
344                        ExecutorKindSys::Overlapped {
345                            concurrency: Some(4)
346                        }
347                        .into()
348                    ),
349                    ..Default::default()
350                }
351            );
352        }
353
354        // id
355        let params = from_block_arg("/some/path.img,id=DISK").unwrap();
356        assert_eq!(
357            params,
358            DiskOption {
359                path: "/some/path.img".into(),
360                id: Some(*b"DISK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
361                ..Default::default()
362            }
363        );
364        let err = from_block_arg("/some/path.img,id=DISK_ID_IS_WAY_TOO_LONG").unwrap_err();
365        assert_eq!(
366            err,
367            ParseError {
368                kind: ErrorKind::SerdeError("disk id must be 20 or fewer characters".into()),
369                pos: 0,
370            }
371        );
372
373        // async-executor
374        #[cfg(windows)]
375        let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Handle.into(), "handle");
376        #[cfg(any(target_os = "android", target_os = "linux"))]
377        let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Fd.into(), "epoll");
378        let params =
379            from_block_arg(&format!("/some/path.img,async-executor={ex_kind_opt}")).unwrap();
380        assert_eq!(
381            params,
382            DiskOption {
383                path: "/some/path.img".into(),
384                async_executor: Some(ex_kind),
385                ..Default::default()
386            }
387        );
388
389        // packed queue
390        let params = from_block_arg("/path/to/disk.img,packed-queue").unwrap();
391        assert_eq!(
392            params,
393            DiskOption {
394                path: "/path/to/disk.img".into(),
395                packed_queue: true,
396                ..Default::default()
397            }
398        );
399
400        // pci-address
401        let params = from_block_arg("/path/to/disk.img,pci-address=00:01.1").unwrap();
402        assert_eq!(
403            params,
404            DiskOption {
405                path: "/path/to/disk.img".into(),
406                pci_address: Some(PciAddress {
407                    bus: 0,
408                    dev: 1,
409                    func: 1,
410                }),
411                ..Default::default()
412            }
413        );
414
415        // lock=true
416        let params = from_block_arg("/path/to/disk.img,lock=true").unwrap();
417        assert_eq!(
418            params,
419            DiskOption {
420                path: "/path/to/disk.img".into(),
421                ..Default::default()
422            }
423        );
424        // lock=false
425        let params = from_block_arg("/path/to/disk.img,lock=false").unwrap();
426        assert_eq!(
427            params,
428            DiskOption {
429                path: "/path/to/disk.img".into(),
430                lock: false,
431                ..Default::default()
432            }
433        );
434
435        // All together
436        let params = from_block_arg(&format!(
437            "/some/path.img,block_size=256,ro,root,sparse=false,id=DISK_LABEL\
438            ,direct,async-executor={ex_kind_opt},packed-queue=false,pci-address=00:01.1"
439        ))
440        .unwrap();
441        assert_eq!(
442            params,
443            DiskOption {
444                path: "/some/path.img".into(),
445                read_only: true,
446                root: true,
447                sparse: false,
448                direct: true,
449                block_size: 256,
450                id: Some(*b"DISK_LABEL\0\0\0\0\0\0\0\0\0\0"),
451                async_executor: Some(ex_kind),
452                pci_address: Some(PciAddress {
453                    bus: 0,
454                    dev: 1,
455                    func: 1,
456                }),
457                ..Default::default()
458            }
459        );
460    }
461
462    #[test]
463    fn diskoption_serialize_deserialize() {
464        // With id == None
465        let original = DiskOption {
466            path: "./rootfs".into(),
467            ..Default::default()
468        };
469        let json = serde_json::to_string(&original).unwrap();
470        let deserialized = serde_json::from_str(&json).unwrap();
471        assert_eq!(original, deserialized);
472
473        // With id == Some
474        let original = DiskOption {
475            path: "./rootfs".into(),
476            id: Some(*b"BLK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
477            async_executor: Some(ExecutorKind::default()),
478            ..Default::default()
479        };
480        let json = serde_json::to_string(&original).unwrap();
481        let deserialized = serde_json::from_str(&json).unwrap();
482        assert_eq!(original, deserialized);
483
484        // With id taking all the available space.
485        let original = DiskOption {
486            path: "./rootfs".into(),
487            id: Some(*b"QWERTYUIOPASDFGHJKL:"),
488            async_executor: Some(ExecutorKind::default()),
489            ..Default::default()
490        };
491        let json = serde_json::to_string(&original).unwrap();
492        let deserialized = serde_json::from_str(&json).unwrap();
493        assert_eq!(original, deserialized);
494    }
495}