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 {
95 pub path: PathBuf,
96 #[serde(default, rename = "ro")]
97 pub read_only: bool,
98 #[serde(default)]
99 pub root: bool,
102 #[serde(default = "block_option_sparse_default")]
103 pub sparse: bool,
104 #[serde(default, alias = "o_direct")]
106 pub direct: bool,
107 #[serde(default = "block_option_lock_default")]
109 pub lock: bool,
110 #[serde(default = "block_option_block_size_default", alias = "block_size")]
112 pub block_size: u32,
113 #[serde(
114 default,
115 serialize_with = "serialize_disk_id",
116 deserialize_with = "deserialize_disk_id"
117 )]
118 pub id: Option<[u8; DISK_ID_LEN]>,
119 #[cfg(windows)]
122 #[serde(
123 default = "block_option_io_concurrency_default",
124 alias = "io_concurrency"
125 )]
126 pub io_concurrency: NonZeroU32,
127 #[serde(default)]
128 pub multiple_workers: bool,
131 #[serde(default, alias = "async_executor")]
132 pub async_executor: Option<ExecutorKind>,
136 #[serde(default)]
137 pub packed_queue: bool,
140
141 pub bootindex: Option<usize>,
145
146 pub pci_address: Option<PciAddress>,
148 #[serde(default)]
150 pub dontcache: bool,
151}
152impl Default for DiskOption {
155 fn default() -> Self {
156 Self {
157 path: PathBuf::new(),
158 read_only: false,
159 root: false,
160 sparse: block_option_sparse_default(),
161 direct: false,
162 lock: block_option_lock_default(),
163 block_size: block_option_block_size_default(),
164 id: None,
165 #[cfg(windows)]
166 io_concurrency: block_option_io_concurrency_default(),
167 multiple_workers: false,
168 async_executor: None,
169 packed_queue: false,
170 bootindex: None,
171 pci_address: None,
172 dontcache: false,
173 }
174 }
175}
176
177impl VirtioDeviceModule for DiskOption {
178 fn sort_name(&self) -> &'static str {
179 "block"
180 }
181
182 fn create(&self, args: &mut VirtioDeviceArgs<'_>) -> anyhow::Result<Box<dyn VirtioDevice>> {
183 base::info!("Trying to attach block device: {}", self.path.display());
184
185 let (disk_host_tube, disk_device_tube) = Tube::pair().context("failed to create tube")?;
186 (args.add_control_tube)(AnyControlTube::Disk(disk_host_tube));
187
188 let dev = BlockAsync::new(
189 devices::virtio::base_features(args.protection_type),
190 self.open()?,
191 self,
192 Some(disk_device_tube),
193 None,
194 None,
195 )
196 .context("failed to create block device")?;
197
198 Ok(Box::new(dev))
199 }
200
201 #[cfg(any(target_os = "android", target_os = "linux"))]
202 fn create_jail(
203 &self,
204 jail_config: &jail::JailConfig,
205 ) -> anyhow::Result<Option<minijail::Minijail>> {
206 let jail = jail::simple_jail(
207 Some(jail_config),
208 &devices::virtio::VirtioDeviceType::Regular.seccomp_policy_file("block"),
209 )?;
210 Ok(jail)
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 #[cfg(any(target_os = "android", target_os = "linux"))]
217 use cros_async::sys::linux::ExecutorKindSys;
218 #[cfg(windows)]
219 use cros_async::sys::windows::ExecutorKindSys;
220 use serde_keyvalue::*;
221
222 use super::*;
223
224 fn from_block_arg(options: &str) -> Result<DiskOption, ParseError> {
225 from_key_values(options)
226 }
227
228 #[test]
229 fn check_default_matches_from_key_values() {
230 let path = "/path/to/disk.img";
231 let disk = DiskOption {
232 path: PathBuf::from(path),
233 ..Default::default()
234 };
235 assert_eq!(disk, from_key_values(path).unwrap());
236 }
237
238 #[test]
239 fn params_from_key_values() {
240 let err = from_block_arg("").unwrap_err();
242 assert_eq!(
243 err,
244 ParseError {
245 kind: ErrorKind::SerdeError("missing field `path`".into()),
246 pos: 0,
247 }
248 );
249
250 let params = from_block_arg("/path/to/disk.img").unwrap();
252 assert_eq!(
253 params,
254 DiskOption {
255 path: "/path/to/disk.img".into(),
256 ..Default::default()
257 }
258 );
259
260 let params = from_block_arg("/path/to/disk.img,bootindex=5").unwrap();
262 assert_eq!(
263 params,
264 DiskOption {
265 path: "/path/to/disk.img".into(),
266 bootindex: Some(5),
267 ..Default::default()
268 }
269 );
270
271 let params = from_block_arg("path=/path/to/disk.img").unwrap();
273 assert_eq!(
274 params,
275 DiskOption {
276 path: "/path/to/disk.img".into(),
277 ..Default::default()
278 }
279 );
280
281 let params = from_block_arg("/some/path.img,ro").unwrap();
283 assert_eq!(
284 params,
285 DiskOption {
286 path: "/some/path.img".into(),
287 read_only: true,
288 ..Default::default()
289 }
290 );
291
292 let params = from_block_arg("/some/path.img,root").unwrap();
294 assert_eq!(
295 params,
296 DiskOption {
297 path: "/some/path.img".into(),
298 root: true,
299 ..Default::default()
300 }
301 );
302
303 let params = from_block_arg("/some/path.img,sparse").unwrap();
305 assert_eq!(
306 params,
307 DiskOption {
308 path: "/some/path.img".into(),
309 ..Default::default()
310 }
311 );
312 let params = from_block_arg("/some/path.img,sparse=false").unwrap();
313 assert_eq!(
314 params,
315 DiskOption {
316 path: "/some/path.img".into(),
317 sparse: false,
318 ..Default::default()
319 }
320 );
321
322 let params = from_block_arg("/some/path.img,direct").unwrap();
324 assert_eq!(
325 params,
326 DiskOption {
327 path: "/some/path.img".into(),
328 direct: true,
329 ..Default::default()
330 }
331 );
332
333 let params = from_block_arg("/some/path.img,o_direct").unwrap();
335 assert_eq!(
336 params,
337 DiskOption {
338 path: "/some/path.img".into(),
339 direct: true,
340 ..Default::default()
341 }
342 );
343
344 let params = from_block_arg("/some/path.img,block-size=128").unwrap();
346 assert_eq!(
347 params,
348 DiskOption {
349 path: "/some/path.img".into(),
350 block_size: 128,
351 ..Default::default()
352 }
353 );
354
355 let params = from_block_arg("/some/path.img,block_size=128").unwrap();
357 assert_eq!(
358 params,
359 DiskOption {
360 path: "/some/path.img".into(),
361 block_size: 128,
362 ..Default::default()
363 }
364 );
365
366 #[cfg(windows)]
368 {
369 let params = from_block_arg("/some/path.img,io_concurrency=4").unwrap();
370 assert_eq!(
371 params,
372 DiskOption {
373 path: "/some/path.img".into(),
374 io_concurrency: NonZeroU32::new(4).unwrap(),
375 ..Default::default()
376 }
377 );
378 let params = from_block_arg("/some/path.img,async-executor=overlapped").unwrap();
379 assert_eq!(
380 params,
381 DiskOption {
382 path: "/some/path.img".into(),
383 async_executor: Some(ExecutorKindSys::Overlapped { concurrency: None }.into()),
384 ..Default::default()
385 }
386 );
387 let params =
388 from_block_arg("/some/path.img,async-executor=\"overlapped,concurrency=4\"")
389 .unwrap();
390 assert_eq!(
391 params,
392 DiskOption {
393 path: "/some/path.img".into(),
394 async_executor: Some(
395 ExecutorKindSys::Overlapped {
396 concurrency: Some(4)
397 }
398 .into()
399 ),
400 ..Default::default()
401 }
402 );
403 }
404
405 let params = from_block_arg("/some/path.img,id=DISK").unwrap();
407 assert_eq!(
408 params,
409 DiskOption {
410 path: "/some/path.img".into(),
411 id: Some(*b"DISK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
412 ..Default::default()
413 }
414 );
415 let err = from_block_arg("/some/path.img,id=DISK_ID_IS_WAY_TOO_LONG").unwrap_err();
416 assert_eq!(
417 err,
418 ParseError {
419 kind: ErrorKind::SerdeError("disk id must be 20 or fewer characters".into()),
420 pos: 0,
421 }
422 );
423
424 #[cfg(windows)]
426 let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Handle.into(), "handle");
427 #[cfg(any(target_os = "android", target_os = "linux"))]
428 let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Fd.into(), "epoll");
429 let params =
430 from_block_arg(&format!("/some/path.img,async-executor={ex_kind_opt}")).unwrap();
431 assert_eq!(
432 params,
433 DiskOption {
434 path: "/some/path.img".into(),
435 async_executor: Some(ex_kind),
436 ..Default::default()
437 }
438 );
439
440 let params = from_block_arg("/path/to/disk.img,packed-queue").unwrap();
442 assert_eq!(
443 params,
444 DiskOption {
445 path: "/path/to/disk.img".into(),
446 packed_queue: true,
447 ..Default::default()
448 }
449 );
450
451 let params = from_block_arg("/path/to/disk.img,pci-address=00:01.1").unwrap();
453 assert_eq!(
454 params,
455 DiskOption {
456 path: "/path/to/disk.img".into(),
457 pci_address: Some(PciAddress {
458 bus: 0,
459 dev: 1,
460 func: 1,
461 }),
462 ..Default::default()
463 }
464 );
465
466 let params = from_block_arg("/path/to/disk.img,lock=true").unwrap();
468 assert_eq!(
469 params,
470 DiskOption {
471 path: "/path/to/disk.img".into(),
472 ..Default::default()
473 }
474 );
475 let params = from_block_arg("/path/to/disk.img,lock=false").unwrap();
477 assert_eq!(
478 params,
479 DiskOption {
480 path: "/path/to/disk.img".into(),
481 lock: false,
482 ..Default::default()
483 }
484 );
485
486 let params = from_block_arg("/path/to/disk.img,dontcache").unwrap();
488 assert_eq!(
489 params,
490 DiskOption {
491 path: "/path/to/disk.img".into(),
492 dontcache: true,
493 ..Default::default()
494 }
495 );
496 let params = from_block_arg("/path/to/disk.img,dontcache=false").unwrap();
497 assert_eq!(
498 params,
499 DiskOption {
500 path: "/path/to/disk.img".into(),
501 dontcache: false,
502 ..Default::default()
503 }
504 );
505
506 let params = from_block_arg(&format!(
508 "/some/path.img,block_size=256,ro,root,sparse=false,id=DISK_LABEL\
509 ,direct,async-executor={ex_kind_opt},packed-queue=false,pci-address=00:01.1"
510 ))
511 .unwrap();
512 assert_eq!(
513 params,
514 DiskOption {
515 path: "/some/path.img".into(),
516 read_only: true,
517 root: true,
518 sparse: false,
519 direct: true,
520 block_size: 256,
521 id: Some(*b"DISK_LABEL\0\0\0\0\0\0\0\0\0\0"),
522 async_executor: Some(ex_kind),
523 pci_address: Some(PciAddress {
524 bus: 0,
525 dev: 1,
526 func: 1,
527 }),
528 ..Default::default()
529 }
530 );
531 }
532
533 #[test]
534 fn diskoption_serialize_deserialize() {
535 let original = DiskOption {
537 path: "./rootfs".into(),
538 ..Default::default()
539 };
540 let json = serde_json::to_string(&original).unwrap();
541 let deserialized = serde_json::from_str(&json).unwrap();
542 assert_eq!(original, deserialized);
543
544 let original = DiskOption {
546 path: "./rootfs".into(),
547 id: Some(*b"BLK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
548 async_executor: Some(ExecutorKind::default()),
549 ..Default::default()
550 };
551 let json = serde_json::to_string(&original).unwrap();
552 let deserialized = serde_json::from_str(&json).unwrap();
553 assert_eq!(original, deserialized);
554
555 let original = DiskOption {
557 path: "./rootfs".into(),
558 id: Some(*b"QWERTYUIOPASDFGHJKL:"),
559 async_executor: Some(ExecutorKind::default()),
560 ..Default::default()
561 };
562 let json = serde_json::to_string(&original).unwrap();
563 let deserialized = serde_json::from_str(&json).unwrap();
564 assert_eq!(original, deserialized);
565 }
566}