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