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}
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 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 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 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 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 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 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 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 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 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 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 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 #[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 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 #[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 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 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 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 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 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 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 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 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}