1#[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#[cfg(windows)]
34fn block_option_io_concurrency_default() -> NonZeroU32 {
35 NonZeroU32::new(1).unwrap()
36}
37
38pub 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 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 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 pub root: bool,
93 #[serde(default = "block_option_sparse_default")]
94 pub sparse: bool,
95 #[serde(default, alias = "o_direct")]
97 pub direct: bool,
98 #[serde(default = "block_option_lock_default")]
100 pub lock: bool,
101 #[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 #[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 pub multiple_workers: bool,
122 #[serde(default, alias = "async_executor")]
123 pub async_executor: Option<ExecutorKind>,
127 #[serde(default)]
128 pub packed_queue: bool,
131
132 pub bootindex: Option<usize>,
136
137 pub pci_address: Option<PciAddress>,
139 #[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 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 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 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 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 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 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 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 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 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 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 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 #[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 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 #[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 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 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 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 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 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 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 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 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 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}