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