• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 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)]
6 use std::num::NonZeroU32;
7 use std::path::PathBuf;
8 
9 use cros_async::ExecutorKind;
10 use serde::Deserialize;
11 use serde::Deserializer;
12 use serde::Serialize;
13 use serde::Serializer;
14 
block_option_sparse_default() -> bool15 fn block_option_sparse_default() -> bool {
16     true
17 }
block_option_block_size_default() -> u3218 fn block_option_block_size_default() -> u32 {
19     512
20 }
21 // TODO(b/237829580): Move to sys module once virtio block sys is refactored to
22 // match the style guide.
23 #[cfg(windows)]
block_option_io_concurrency_default() -> NonZeroU3224 fn block_option_io_concurrency_default() -> NonZeroU32 {
25     NonZeroU32::new(1).unwrap()
26 }
27 
28 /// Maximum length of a `DiskOption` identifier.
29 ///
30 /// This is based on the virtio-block ID length limit.
31 pub const DISK_ID_LEN: usize = 20;
32 
serialize_disk_id<S: Serializer>( id: &Option<[u8; DISK_ID_LEN]>, serializer: S, ) -> Result<S::Ok, S::Error>33 pub fn serialize_disk_id<S: Serializer>(
34     id: &Option<[u8; DISK_ID_LEN]>,
35     serializer: S,
36 ) -> Result<S::Ok, S::Error> {
37     match id {
38         None => serializer.serialize_none(),
39         Some(id) => {
40             // Find the first zero byte in the id.
41             let len = id.iter().position(|v| *v == 0).unwrap_or(DISK_ID_LEN);
42             serializer.serialize_some(
43                 std::str::from_utf8(&id[0..len])
44                     .map_err(|e| serde::ser::Error::custom(e.to_string()))?,
45             )
46         }
47     }
48 }
49 
deserialize_disk_id<'de, D: Deserializer<'de>>( deserializer: D, ) -> Result<Option<[u8; DISK_ID_LEN]>, D::Error>50 fn deserialize_disk_id<'de, D: Deserializer<'de>>(
51     deserializer: D,
52 ) -> Result<Option<[u8; DISK_ID_LEN]>, D::Error> {
53     let id = Option::<String>::deserialize(deserializer)?;
54 
55     match id {
56         None => Ok(None),
57         Some(id) => {
58             if id.len() > DISK_ID_LEN {
59                 return Err(serde::de::Error::custom(format!(
60                     "disk id must be {} or fewer characters",
61                     DISK_ID_LEN
62                 )));
63             }
64 
65             let mut ret = [0u8; DISK_ID_LEN];
66             // Slicing id to value's length will never panic
67             // because we checked that value will fit into id above.
68             ret[..id.len()].copy_from_slice(id.as_bytes());
69             Ok(Some(ret))
70         }
71     }
72 }
73 
74 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, serde_keyvalue::FromKeyValues)]
75 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
76 pub struct DiskOption {
77     pub path: PathBuf,
78     #[serde(default, rename = "ro")]
79     pub read_only: bool,
80     #[serde(default)]
81     /// Whether this disk should be the root device. Can only be set once. Only useful for adding
82     /// specific command-line options.
83     pub root: bool,
84     #[serde(default = "block_option_sparse_default")]
85     pub sparse: bool,
86     // camel_case variant allowed for backward compatibility.
87     #[serde(default, alias = "o_direct")]
88     pub direct: bool,
89     // camel_case variant allowed for backward compatibility.
90     #[serde(default = "block_option_block_size_default", alias = "block_size")]
91     pub block_size: u32,
92     #[serde(
93         default,
94         serialize_with = "serialize_disk_id",
95         deserialize_with = "deserialize_disk_id"
96     )]
97     pub id: Option<[u8; DISK_ID_LEN]>,
98     // camel_case variant allowed for backward compatibility.
99     #[cfg(windows)]
100     #[serde(
101         default = "block_option_io_concurrency_default",
102         alias = "io_concurrency"
103     )]
104     pub io_concurrency: NonZeroU32,
105     #[serde(default)]
106     /// Experimental option to run multiple worker threads in parallel. If false, only single thread
107     /// runs by default. Note this option is not effective for vhost-user blk device.
108     pub multiple_workers: bool,
109     #[serde(default, alias = "async_executor")]
110     /// The async executor kind to simulate the block device with. This option takes
111     /// precedence over the async executor kind specified by the subcommand's option.
112     /// If None, the default or the specified by the subcommand's option would be used.
113     pub async_executor: Option<ExecutorKind>,
114 }
115 
116 #[cfg(test)]
117 mod tests {
118     use serde_keyvalue::*;
119 
120     use super::*;
121 
from_block_arg(options: &str) -> Result<DiskOption, ParseError>122     fn from_block_arg(options: &str) -> Result<DiskOption, ParseError> {
123         from_key_values(options)
124     }
125 
126     #[test]
params_from_key_values()127     fn params_from_key_values() {
128         // Path argument is mandatory.
129         let err = from_block_arg("").unwrap_err();
130         assert_eq!(
131             err,
132             ParseError {
133                 kind: ErrorKind::SerdeError("missing field `path`".into()),
134                 pos: 0,
135             }
136         );
137 
138         // Path is the default argument.
139         let params = from_block_arg("/path/to/disk.img").unwrap();
140         assert_eq!(
141             params,
142             DiskOption {
143                 path: "/path/to/disk.img".into(),
144                 read_only: false,
145                 root: false,
146                 sparse: true,
147                 direct: false,
148                 block_size: 512,
149                 id: None,
150                 #[cfg(windows)]
151                 io_concurrency: NonZeroU32::new(1).unwrap(),
152                 multiple_workers: false,
153                 async_executor: None,
154             }
155         );
156 
157         // Explicitly-specified path.
158         let params = from_block_arg("path=/path/to/disk.img").unwrap();
159         assert_eq!(
160             params,
161             DiskOption {
162                 path: "/path/to/disk.img".into(),
163                 read_only: false,
164                 root: false,
165                 sparse: true,
166                 direct: false,
167                 block_size: 512,
168                 id: None,
169                 #[cfg(windows)]
170                 io_concurrency: NonZeroU32::new(1).unwrap(),
171                 multiple_workers: false,
172                 async_executor: None,
173             }
174         );
175 
176         // read_only
177         let params = from_block_arg("/some/path.img,ro").unwrap();
178         assert_eq!(
179             params,
180             DiskOption {
181                 path: "/some/path.img".into(),
182                 read_only: true,
183                 root: false,
184                 sparse: true,
185                 direct: false,
186                 block_size: 512,
187                 id: None,
188                 #[cfg(windows)]
189                 io_concurrency: NonZeroU32::new(1).unwrap(),
190                 multiple_workers: false,
191                 async_executor: None,
192             }
193         );
194 
195         // root
196         let params = from_block_arg("/some/path.img,root").unwrap();
197         assert_eq!(
198             params,
199             DiskOption {
200                 path: "/some/path.img".into(),
201                 read_only: false,
202                 root: true,
203                 sparse: true,
204                 direct: false,
205                 block_size: 512,
206                 id: None,
207                 #[cfg(windows)]
208                 io_concurrency: NonZeroU32::new(1).unwrap(),
209                 multiple_workers: false,
210                 async_executor: None,
211             }
212         );
213 
214         // sparse
215         let params = from_block_arg("/some/path.img,sparse").unwrap();
216         assert_eq!(
217             params,
218             DiskOption {
219                 path: "/some/path.img".into(),
220                 read_only: false,
221                 root: false,
222                 sparse: true,
223                 direct: false,
224                 block_size: 512,
225                 id: None,
226                 #[cfg(windows)]
227                 io_concurrency: NonZeroU32::new(1).unwrap(),
228                 multiple_workers: false,
229                 async_executor: None,
230             }
231         );
232         let params = from_block_arg("/some/path.img,sparse=false").unwrap();
233         assert_eq!(
234             params,
235             DiskOption {
236                 path: "/some/path.img".into(),
237                 read_only: false,
238                 root: false,
239                 sparse: false,
240                 direct: false,
241                 block_size: 512,
242                 id: None,
243                 #[cfg(windows)]
244                 io_concurrency: NonZeroU32::new(1).unwrap(),
245                 multiple_workers: false,
246                 async_executor: None,
247             }
248         );
249 
250         // direct
251         let params = from_block_arg("/some/path.img,direct").unwrap();
252         assert_eq!(
253             params,
254             DiskOption {
255                 path: "/some/path.img".into(),
256                 read_only: false,
257                 root: false,
258                 sparse: true,
259                 direct: true,
260                 block_size: 512,
261                 id: None,
262                 #[cfg(windows)]
263                 io_concurrency: NonZeroU32::new(1).unwrap(),
264                 multiple_workers: false,
265                 async_executor: None,
266             }
267         );
268 
269         // o_direct (deprecated, kept for backward compatibility)
270         let params = from_block_arg("/some/path.img,o_direct").unwrap();
271         assert_eq!(
272             params,
273             DiskOption {
274                 path: "/some/path.img".into(),
275                 read_only: false,
276                 root: false,
277                 sparse: true,
278                 direct: true,
279                 block_size: 512,
280                 id: None,
281                 #[cfg(windows)]
282                 io_concurrency: NonZeroU32::new(1).unwrap(),
283                 multiple_workers: false,
284                 async_executor: None,
285             }
286         );
287 
288         // block-size
289         let params = from_block_arg("/some/path.img,block-size=128").unwrap();
290         assert_eq!(
291             params,
292             DiskOption {
293                 path: "/some/path.img".into(),
294                 read_only: false,
295                 root: false,
296                 sparse: true,
297                 direct: false,
298                 block_size: 128,
299                 id: None,
300                 #[cfg(windows)]
301                 io_concurrency: NonZeroU32::new(1).unwrap(),
302                 multiple_workers: false,
303                 async_executor: None,
304             }
305         );
306 
307         // block_size (deprecated, kept for backward compatibility)
308         let params = from_block_arg("/some/path.img,block_size=128").unwrap();
309         assert_eq!(
310             params,
311             DiskOption {
312                 path: "/some/path.img".into(),
313                 read_only: false,
314                 root: false,
315                 sparse: true,
316                 direct: false,
317                 block_size: 128,
318                 id: None,
319                 async_executor: None,
320                 #[cfg(windows)]
321                 io_concurrency: NonZeroU32::new(1).unwrap(),
322                 multiple_workers: false,
323             }
324         );
325 
326         // io_concurrency
327         #[cfg(windows)]
328         {
329             let params = from_block_arg("/some/path.img,io_concurrency=4").unwrap();
330             assert_eq!(
331                 params,
332                 DiskOption {
333                     path: "/some/path.img".into(),
334                     read_only: false,
335                     root: false,
336                     sparse: true,
337                     direct: false,
338                     block_size: 512,
339                     id: None,
340                     io_concurrency: NonZeroU32::new(4).unwrap(),
341                     multiple_workers: false,
342                     async_executor: None,
343                 }
344             );
345         }
346 
347         // id
348         let params = from_block_arg("/some/path.img,id=DISK").unwrap();
349         assert_eq!(
350             params,
351             DiskOption {
352                 path: "/some/path.img".into(),
353                 read_only: false,
354                 root: false,
355                 sparse: true,
356                 direct: false,
357                 block_size: 512,
358                 id: Some(*b"DISK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
359                 #[cfg(windows)]
360                 io_concurrency: NonZeroU32::new(1).unwrap(),
361                 multiple_workers: false,
362                 async_executor: None,
363             }
364         );
365         let err = from_block_arg("/some/path.img,id=DISK_ID_IS_WAY_TOO_LONG").unwrap_err();
366         assert_eq!(
367             err,
368             ParseError {
369                 kind: ErrorKind::SerdeError("disk id must be 20 or fewer characters".into()),
370                 pos: 0,
371             }
372         );
373 
374         // async-executor
375         #[cfg(windows)]
376         let (ex_kind, ex_kind_opt) = (ExecutorKind::Handle, "handle");
377         #[cfg(unix)]
378         let (ex_kind, ex_kind_opt) = (ExecutorKind::Fd, "epoll");
379         let params =
380             from_block_arg(&format!("/some/path.img,async-executor={ex_kind_opt}")).unwrap();
381         assert_eq!(
382             params,
383             DiskOption {
384                 path: "/some/path.img".into(),
385                 read_only: false,
386                 root: false,
387                 sparse: true,
388                 direct: false,
389                 block_size: 512,
390                 id: None,
391                 #[cfg(windows)]
392                 io_concurrency: NonZeroU32::new(1).unwrap(),
393                 multiple_workers: false,
394                 async_executor: Some(ex_kind),
395             }
396         );
397 
398         // All together
399         let params = from_block_arg(&format!(
400             "/some/path.img,block_size=256,ro,root,sparse=false,id=DISK_LABEL\
401             ,direct,async-executor={ex_kind_opt}"
402         ))
403         .unwrap();
404         assert_eq!(
405             params,
406             DiskOption {
407                 path: "/some/path.img".into(),
408                 read_only: true,
409                 root: true,
410                 sparse: false,
411                 direct: true,
412                 block_size: 256,
413                 id: Some(*b"DISK_LABEL\0\0\0\0\0\0\0\0\0\0"),
414                 #[cfg(windows)]
415                 io_concurrency: NonZeroU32::new(1).unwrap(),
416                 multiple_workers: false,
417                 async_executor: Some(ex_kind),
418             }
419         );
420     }
421 
422     #[test]
diskoption_serialize_deserialize()423     fn diskoption_serialize_deserialize() {
424         // With id == None
425         let original = DiskOption {
426             path: "./rootfs".into(),
427             read_only: false,
428             root: false,
429             sparse: true,
430             direct: false,
431             block_size: 512,
432             id: None,
433             #[cfg(windows)]
434             io_concurrency: NonZeroU32::new(1).unwrap(),
435             multiple_workers: false,
436             async_executor: None,
437         };
438         let json = serde_json::to_string(&original).unwrap();
439         let deserialized = serde_json::from_str(&json).unwrap();
440         assert_eq!(original, deserialized);
441 
442         // With id == Some
443         let original = DiskOption {
444             path: "./rootfs".into(),
445             read_only: false,
446             root: false,
447             sparse: true,
448             direct: false,
449             block_size: 512,
450             id: Some(*b"BLK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
451             #[cfg(windows)]
452             io_concurrency: NonZeroU32::new(1).unwrap(),
453             multiple_workers: false,
454             async_executor: Some(ExecutorKind::default()),
455         };
456         let json = serde_json::to_string(&original).unwrap();
457         let deserialized = serde_json::from_str(&json).unwrap();
458         assert_eq!(original, deserialized);
459 
460         // With id taking all the available space.
461         let original = DiskOption {
462             path: "./rootfs".into(),
463             read_only: false,
464             root: false,
465             sparse: true,
466             direct: false,
467             block_size: 512,
468             id: Some(*b"QWERTYUIOPASDFGHJKL:"),
469             #[cfg(windows)]
470             io_concurrency: NonZeroU32::new(1).unwrap(),
471             multiple_workers: false,
472             async_executor: Some(ExecutorKind::default()),
473         };
474         let json = serde_json::to_string(&original).unwrap();
475         let deserialized = serde_json::from_str(&json).unwrap();
476         assert_eq!(original, deserialized);
477     }
478 }
479