1#[cfg(windows)]
6use std::num::NonZeroU32;
7use std::path::PathBuf;
8
9use anyhow::Context;
10use base::Tube;
11use cros_async::ExecutorKind;
12use devices::virtio::VirtioDevice;
13use devices::PciAddress;
14use devices::VirtioDeviceArgs;
15use devices::VirtioDeviceModule;
16use serde::Deserialize;
17use serde::Deserializer;
18use serde::Serialize;
19use serde::Serializer;
20use vm_control::AnyControlTube;
21
22pub mod asynchronous;
23pub mod sys;
24pub mod vhost_user;
25
26pub use asynchronous::BlockAsync;
27pub use vhost_user::run_block_device;
28pub use vhost_user::Options as BlockOptions;
29
30fn block_option_sparse_default() -> bool {
31 true
32}
33fn block_option_lock_default() -> bool {
34 true
35}
36fn block_option_block_size_default() -> u32 {
37 512
38}
39#[cfg(windows)]
42fn block_option_io_concurrency_default() -> NonZeroU32 {
43 NonZeroU32::new(1).unwrap()
44}
45
46pub const DISK_ID_LEN: usize = 20;
50
51pub fn serialize_disk_id<S: Serializer>(
52 id: &Option<[u8; DISK_ID_LEN]>,
53 serializer: S,
54) -> Result<S::Ok, S::Error> {
55 match id {
56 None => serializer.serialize_none(),
57 Some(id) => {
58 let len = id.iter().position(|v| *v == 0).unwrap_or(DISK_ID_LEN);
60 serializer.serialize_some(
61 std::str::from_utf8(&id[0..len])
62 .map_err(|e| serde::ser::Error::custom(e.to_string()))?,
63 )
64 }
65 }
66}
67
68fn deserialize_disk_id<'de, D: Deserializer<'de>>(
69 deserializer: D,
70) -> Result<Option<[u8; DISK_ID_LEN]>, D::Error> {
71 let id = Option::<String>::deserialize(deserializer)?;
72
73 match id {
74 None => Ok(None),
75 Some(id) => {
76 if id.len() > DISK_ID_LEN {
77 return Err(serde::de::Error::custom(format!(
78 "disk id must be {DISK_ID_LEN} or fewer characters"
79 )));
80 }
81
82 let mut ret = [0u8; DISK_ID_LEN];
83 ret[..id.len()].copy_from_slice(id.as_bytes());
86 Ok(Some(ret))
87 }
88 }
89}
90
91#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, serde_keyvalue::FromKeyValues)]
92#[serde(deny_unknown_fields, rename_all = "kebab-case")]
93pub struct DiskOption {
94 pub path: PathBuf,
95 #[serde(default, rename = "ro")]
96 pub read_only: bool,
97 #[serde(default)]
98 pub root: bool,
101 #[serde(default = "block_option_sparse_default")]
102 pub sparse: bool,
103 #[serde(default, alias = "o_direct")]
105 pub direct: bool,
106 #[serde(default = "block_option_lock_default")]
108 pub lock: bool,
109 #[serde(default = "block_option_block_size_default", alias = "block_size")]
111 pub block_size: u32,
112 #[serde(
113 default,
114 serialize_with = "serialize_disk_id",
115 deserialize_with = "deserialize_disk_id"
116 )]
117 pub id: Option<[u8; DISK_ID_LEN]>,
118 #[cfg(windows)]
121 #[serde(
122 default = "block_option_io_concurrency_default",
123 alias = "io_concurrency"
124 )]
125 pub io_concurrency: NonZeroU32,
126 #[serde(default)]
127 pub multiple_workers: bool,
130 #[serde(default, alias = "async_executor")]
131 pub async_executor: Option<ExecutorKind>,
135 #[serde(default)]
136 pub packed_queue: bool,
139
140 pub bootindex: Option<usize>,
144
145 pub pci_address: Option<PciAddress>,
147 #[serde(default)]
149 pub dontcache: bool,
150}
151
152impl Default for DiskOption {
153 fn default() -> Self {
154 Self {
155 path: PathBuf::new(),
156 read_only: false,
157 root: false,
158 sparse: block_option_sparse_default(),
159 direct: false,
160 lock: block_option_lock_default(),
161 block_size: block_option_block_size_default(),
162 id: None,
163 #[cfg(windows)]
164 io_concurrency: block_option_io_concurrency_default(),
165 multiple_workers: false,
166 async_executor: None,
167 packed_queue: false,
168 bootindex: None,
169 pci_address: None,
170 dontcache: false,
171 }
172 }
173}
174
175impl VirtioDeviceModule for DiskOption {
176 fn sort_name(&self) -> &'static str {
177 "block"
178 }
179
180 fn create(&self, args: &mut VirtioDeviceArgs<'_>) -> anyhow::Result<Box<dyn VirtioDevice>> {
181 base::info!("Trying to attach block device: {}", self.path.display());
182
183 let (disk_host_tube, disk_device_tube) = Tube::pair().context("failed to create tube")?;
184 (args.add_control_tube)(AnyControlTube::Disk(disk_host_tube));
185
186 let dev = BlockAsync::new(
187 devices::virtio::base_features(args.protection_type),
188 self.open()?,
189 self,
190 Some(disk_device_tube),
191 None,
192 None,
193 )
194 .context("failed to create block device")?;
195
196 Ok(Box::new(dev))
197 }
198
199 #[cfg(any(target_os = "android", target_os = "linux"))]
200 fn create_jail(
201 &self,
202 jail_config: &jail::JailConfig,
203 ) -> anyhow::Result<Option<minijail::Minijail>> {
204 let jail = jail::simple_jail(
205 Some(jail_config),
206 &devices::virtio::VirtioDeviceType::Regular.seccomp_policy_file("block"),
207 )?;
208 Ok(jail)
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 #[cfg(any(target_os = "android", target_os = "linux"))]
215 use cros_async::sys::linux::ExecutorKindSys;
216 #[cfg(windows)]
217 use cros_async::sys::windows::ExecutorKindSys;
218 use serde_keyvalue::*;
219
220 use super::*;
221
222 fn from_block_arg(options: &str) -> Result<DiskOption, ParseError> {
223 from_key_values(options)
224 }
225
226 #[test]
227 fn check_default_matches_from_key_values() {
228 let path = "/path/to/disk.img";
229 let disk = DiskOption {
230 path: PathBuf::from(path),
231 ..Default::default()
232 };
233 assert_eq!(disk, from_key_values(path).unwrap());
234 }
235
236 #[test]
237 fn params_from_key_values() {
238 let err = from_block_arg("").unwrap_err();
240 assert_eq!(
241 err,
242 ParseError {
243 kind: ErrorKind::SerdeError("missing field `path`".into()),
244 pos: 0,
245 }
246 );
247
248 let params = from_block_arg("/path/to/disk.img").unwrap();
250 assert_eq!(
251 params,
252 DiskOption {
253 path: "/path/to/disk.img".into(),
254 ..Default::default()
255 }
256 );
257
258 let params = from_block_arg("/path/to/disk.img,bootindex=5").unwrap();
260 assert_eq!(
261 params,
262 DiskOption {
263 path: "/path/to/disk.img".into(),
264 bootindex: Some(5),
265 ..Default::default()
266 }
267 );
268
269 let params = from_block_arg("path=/path/to/disk.img").unwrap();
271 assert_eq!(
272 params,
273 DiskOption {
274 path: "/path/to/disk.img".into(),
275 ..Default::default()
276 }
277 );
278
279 let params = from_block_arg("/some/path.img,ro").unwrap();
281 assert_eq!(
282 params,
283 DiskOption {
284 path: "/some/path.img".into(),
285 read_only: true,
286 ..Default::default()
287 }
288 );
289
290 let params = from_block_arg("/some/path.img,root").unwrap();
292 assert_eq!(
293 params,
294 DiskOption {
295 path: "/some/path.img".into(),
296 root: true,
297 ..Default::default()
298 }
299 );
300
301 let params = from_block_arg("/some/path.img,sparse").unwrap();
303 assert_eq!(
304 params,
305 DiskOption {
306 path: "/some/path.img".into(),
307 ..Default::default()
308 }
309 );
310 let params = from_block_arg("/some/path.img,sparse=false").unwrap();
311 assert_eq!(
312 params,
313 DiskOption {
314 path: "/some/path.img".into(),
315 sparse: false,
316 ..Default::default()
317 }
318 );
319
320 let params = from_block_arg("/some/path.img,direct").unwrap();
322 assert_eq!(
323 params,
324 DiskOption {
325 path: "/some/path.img".into(),
326 direct: true,
327 ..Default::default()
328 }
329 );
330
331 let params = from_block_arg("/some/path.img,o_direct").unwrap();
333 assert_eq!(
334 params,
335 DiskOption {
336 path: "/some/path.img".into(),
337 direct: true,
338 ..Default::default()
339 }
340 );
341
342 let params = from_block_arg("/some/path.img,block-size=128").unwrap();
344 assert_eq!(
345 params,
346 DiskOption {
347 path: "/some/path.img".into(),
348 block_size: 128,
349 ..Default::default()
350 }
351 );
352
353 let params = from_block_arg("/some/path.img,block_size=128").unwrap();
355 assert_eq!(
356 params,
357 DiskOption {
358 path: "/some/path.img".into(),
359 block_size: 128,
360 ..Default::default()
361 }
362 );
363
364 #[cfg(windows)]
366 {
367 let params = from_block_arg("/some/path.img,io_concurrency=4").unwrap();
368 assert_eq!(
369 params,
370 DiskOption {
371 path: "/some/path.img".into(),
372 io_concurrency: NonZeroU32::new(4).unwrap(),
373 ..Default::default()
374 }
375 );
376 let params = from_block_arg("/some/path.img,async-executor=overlapped").unwrap();
377 assert_eq!(
378 params,
379 DiskOption {
380 path: "/some/path.img".into(),
381 async_executor: Some(ExecutorKindSys::Overlapped { concurrency: None }.into()),
382 ..Default::default()
383 }
384 );
385 let params =
386 from_block_arg("/some/path.img,async-executor=\"overlapped,concurrency=4\"")
387 .unwrap();
388 assert_eq!(
389 params,
390 DiskOption {
391 path: "/some/path.img".into(),
392 async_executor: Some(
393 ExecutorKindSys::Overlapped {
394 concurrency: Some(4)
395 }
396 .into()
397 ),
398 ..Default::default()
399 }
400 );
401 }
402
403 let params = from_block_arg("/some/path.img,id=DISK").unwrap();
405 assert_eq!(
406 params,
407 DiskOption {
408 path: "/some/path.img".into(),
409 id: Some(*b"DISK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
410 ..Default::default()
411 }
412 );
413 let err = from_block_arg("/some/path.img,id=DISK_ID_IS_WAY_TOO_LONG").unwrap_err();
414 assert_eq!(
415 err,
416 ParseError {
417 kind: ErrorKind::SerdeError("disk id must be 20 or fewer characters".into()),
418 pos: 0,
419 }
420 );
421
422 #[cfg(windows)]
424 let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Handle.into(), "handle");
425 #[cfg(any(target_os = "android", target_os = "linux"))]
426 let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Fd.into(), "epoll");
427 let params =
428 from_block_arg(&format!("/some/path.img,async-executor={ex_kind_opt}")).unwrap();
429 assert_eq!(
430 params,
431 DiskOption {
432 path: "/some/path.img".into(),
433 async_executor: Some(ex_kind),
434 ..Default::default()
435 }
436 );
437
438 let params = from_block_arg("/path/to/disk.img,packed-queue").unwrap();
440 assert_eq!(
441 params,
442 DiskOption {
443 path: "/path/to/disk.img".into(),
444 packed_queue: true,
445 ..Default::default()
446 }
447 );
448
449 let params = from_block_arg("/path/to/disk.img,pci-address=00:01.1").unwrap();
451 assert_eq!(
452 params,
453 DiskOption {
454 path: "/path/to/disk.img".into(),
455 pci_address: Some(PciAddress {
456 bus: 0,
457 dev: 1,
458 func: 1,
459 }),
460 ..Default::default()
461 }
462 );
463
464 let params = from_block_arg("/path/to/disk.img,lock=true").unwrap();
466 assert_eq!(
467 params,
468 DiskOption {
469 path: "/path/to/disk.img".into(),
470 ..Default::default()
471 }
472 );
473 let params = from_block_arg("/path/to/disk.img,lock=false").unwrap();
475 assert_eq!(
476 params,
477 DiskOption {
478 path: "/path/to/disk.img".into(),
479 lock: false,
480 ..Default::default()
481 }
482 );
483
484 let params = from_block_arg("/path/to/disk.img,dontcache").unwrap();
486 assert_eq!(
487 params,
488 DiskOption {
489 path: "/path/to/disk.img".into(),
490 dontcache: true,
491 ..Default::default()
492 }
493 );
494 let params = from_block_arg("/path/to/disk.img,dontcache=false").unwrap();
495 assert_eq!(
496 params,
497 DiskOption {
498 path: "/path/to/disk.img".into(),
499 dontcache: false,
500 ..Default::default()
501 }
502 );
503
504 let params = from_block_arg(&format!(
506 "/some/path.img,block_size=256,ro,root,sparse=false,id=DISK_LABEL\
507 ,direct,async-executor={ex_kind_opt},packed-queue=false,pci-address=00:01.1"
508 ))
509 .unwrap();
510 assert_eq!(
511 params,
512 DiskOption {
513 path: "/some/path.img".into(),
514 read_only: true,
515 root: true,
516 sparse: false,
517 direct: true,
518 block_size: 256,
519 id: Some(*b"DISK_LABEL\0\0\0\0\0\0\0\0\0\0"),
520 async_executor: Some(ex_kind),
521 pci_address: Some(PciAddress {
522 bus: 0,
523 dev: 1,
524 func: 1,
525 }),
526 ..Default::default()
527 }
528 );
529 }
530
531 #[test]
532 fn diskoption_serialize_deserialize() {
533 let original = DiskOption {
535 path: "./rootfs".into(),
536 ..Default::default()
537 };
538 let json = serde_json::to_string(&original).unwrap();
539 let deserialized = serde_json::from_str(&json).unwrap();
540 assert_eq!(original, deserialized);
541
542 let original = DiskOption {
544 path: "./rootfs".into(),
545 id: Some(*b"BLK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
546 async_executor: Some(ExecutorKind::default()),
547 ..Default::default()
548 };
549 let json = serde_json::to_string(&original).unwrap();
550 let deserialized = serde_json::from_str(&json).unwrap();
551 assert_eq!(original, deserialized);
552
553 let original = DiskOption {
555 path: "./rootfs".into(),
556 id: Some(*b"QWERTYUIOPASDFGHJKL:"),
557 async_executor: Some(ExecutorKind::default()),
558 ..Default::default()
559 };
560 let json = serde_json::to_string(&original).unwrap();
561 let deserialized = serde_json::from_str(&json).unwrap();
562 assert_eq!(original, deserialized);
563 }
564}