1use std::collections::VecDeque;
5use std::io;
6use std::sync::{Arc, Mutex};
7
8use serde::{Deserialize, Serialize};
9
10use super::RateLimiterConfig;
11use crate::VmmError;
12use crate::devices::virtio::block::device::Block;
13pub use crate::devices::virtio::block::virtio::device::FileEngineType;
14use crate::devices::virtio::block::{BlockError, CacheType};
15
16#[derive(Debug, thiserror::Error, displaydoc::Display)]
18pub enum DriveError {
19 AddingSecondRootDevice,
21 CreateBlockDevice(BlockError),
23 CreateRateLimiter(io::Error),
25 DeviceUpdate(VmmError),
27 RootBlockDeviceAlreadyAdded,
29}
30
31#[derive(Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
33#[serde(deny_unknown_fields)]
34pub struct BlockDeviceConfig {
35 pub drive_id: String,
37 pub partuuid: Option<String>,
40 pub is_root_device: bool,
44 #[serde(default)]
47 pub cache_type: CacheType,
48
49 pub is_read_only: Option<bool>,
53 pub path_on_host: Option<String>,
55 pub rate_limiter: Option<RateLimiterConfig>,
57 #[serde(rename = "io_engine")]
62 pub file_engine_type: Option<FileEngineType>,
63
64 pub socket: Option<String>,
67}
68
69#[derive(Debug, Default, PartialEq, Eq, Deserialize)]
72#[serde(deny_unknown_fields)]
73pub struct BlockDeviceUpdateConfig {
74 pub drive_id: String,
76
77 pub path_on_host: Option<String>,
80 pub rate_limiter: Option<RateLimiterConfig>,
82}
83
84#[derive(Debug, Default)]
86pub struct BlockBuilder {
87 pub devices: VecDeque<Arc<Mutex<Block>>>,
93}
94
95impl BlockBuilder {
96 pub fn new() -> Self {
98 Self {
99 devices: Default::default(),
100 }
101 }
102
103 pub fn has_root_device(&self) -> bool {
105 if let Some(block) = self.devices.front() {
107 block.lock().expect("Poisoned lock").root_device()
108 } else {
109 false
110 }
111 }
112
113 fn get_index_of_drive_id(&self, drive_id: &str) -> Option<usize> {
115 self.devices
116 .iter()
117 .position(|b| b.lock().expect("Poisoned lock").id().eq(drive_id))
118 }
119
120 pub fn add_virtio_device(&mut self, block_device: Arc<Mutex<Block>>) {
122 if block_device.lock().expect("Poisoned lock").root_device() {
123 self.devices.push_front(block_device);
124 } else {
125 self.devices.push_back(block_device);
126 }
127 }
128
129 pub fn insert(
133 &mut self,
134 config: BlockDeviceConfig,
135 has_pmem_root: bool,
136 ) -> Result<(), DriveError> {
137 let position = self.get_index_of_drive_id(&config.drive_id);
138 let has_root_device = self.has_root_device();
139 let configured_as_root = config.is_root_device;
140
141 if configured_as_root && has_pmem_root {
142 return Err(DriveError::AddingSecondRootDevice);
143 }
144
145 if configured_as_root && has_root_device && position != Some(0) {
148 return Err(DriveError::RootBlockDeviceAlreadyAdded);
149 }
150
151 let block_dev = Arc::new(Mutex::new(
152 Block::new(config).map_err(DriveError::CreateBlockDevice)?,
153 ));
154
155 match position {
157 None => {
159 if configured_as_root {
160 self.devices.push_front(block_dev);
161 } else {
162 self.devices.push_back(block_dev);
163 }
164 }
165 Some(index) => {
167 self.devices[index] = block_dev;
169 if index != 0 && configured_as_root {
171 self.devices.swap(0, index);
173 }
174 }
175 }
176 Ok(())
177 }
178
179 pub fn configs(&self) -> Vec<BlockDeviceConfig> {
181 self.devices
182 .iter()
183 .map(|b| b.lock().unwrap().config())
184 .collect()
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use vmm_sys_util::tempfile::TempFile;
191
192 use super::*;
193 use crate::devices::virtio::block::virtio::VirtioBlockError;
194
195 impl PartialEq for DriveError {
196 fn eq(&self, other: &DriveError) -> bool {
197 self.to_string() == other.to_string()
198 }
199 }
200
201 impl Clone for BlockDeviceConfig {
204 fn clone(&self) -> Self {
205 BlockDeviceConfig {
206 drive_id: self.drive_id.clone(),
207 partuuid: self.partuuid.clone(),
208 is_root_device: self.is_root_device,
209 is_read_only: self.is_read_only,
210 cache_type: self.cache_type,
211
212 path_on_host: self.path_on_host.clone(),
213 rate_limiter: self.rate_limiter,
214 file_engine_type: self.file_engine_type,
215
216 socket: self.socket.clone(),
217 }
218 }
219 }
220
221 #[test]
222 fn test_create_block_devs() {
223 let block_devs = BlockBuilder::new();
224 assert_eq!(block_devs.devices.len(), 0);
225 }
226
227 #[test]
228 fn test_add_non_root_block_device() {
229 let dummy_file = TempFile::new().unwrap();
230 let dummy_path = dummy_file.as_path().to_str().unwrap().to_string();
231 let dummy_id = String::from("1");
232 let dummy_block_device = BlockDeviceConfig {
233 drive_id: dummy_id.clone(),
234 partuuid: None,
235 is_root_device: false,
236 cache_type: CacheType::Writeback,
237
238 is_read_only: Some(false),
239 path_on_host: Some(dummy_path),
240 rate_limiter: None,
241 file_engine_type: None,
242
243 socket: None,
244 };
245
246 let mut block_devs = BlockBuilder::new();
247 block_devs
248 .insert(dummy_block_device.clone(), false)
249 .unwrap();
250
251 assert!(!block_devs.has_root_device());
252 assert_eq!(block_devs.devices.len(), 1);
253 assert_eq!(block_devs.get_index_of_drive_id(&dummy_id), Some(0));
254
255 let block = block_devs.devices[0].lock().unwrap();
256 assert_eq!(block.id(), dummy_block_device.drive_id);
257 assert_eq!(block.partuuid(), &dummy_block_device.partuuid);
258 assert_eq!(block.read_only(), dummy_block_device.is_read_only.unwrap());
259 }
260
261 #[test]
262 fn test_add_one_root_block_device() {
263 let dummy_file = TempFile::new().unwrap();
264 let dummy_path = dummy_file.as_path().to_str().unwrap().to_string();
265
266 let dummy_block_device = BlockDeviceConfig {
267 drive_id: String::from("1"),
268 partuuid: None,
269 is_root_device: true,
270 cache_type: CacheType::Unsafe,
271
272 is_read_only: Some(true),
273 path_on_host: Some(dummy_path),
274 rate_limiter: None,
275 file_engine_type: None,
276
277 socket: None,
278 };
279
280 let mut block_devs = BlockBuilder::new();
281 block_devs
282 .insert(dummy_block_device.clone(), false)
283 .unwrap();
284
285 assert!(block_devs.has_root_device());
286 assert_eq!(block_devs.devices.len(), 1);
287 let block = block_devs.devices[0].lock().unwrap();
288 assert_eq!(block.id(), dummy_block_device.drive_id);
289 assert_eq!(block.partuuid(), &dummy_block_device.partuuid);
290 assert_eq!(block.read_only(), dummy_block_device.is_read_only.unwrap());
291 }
292
293 #[test]
294 fn test_add_one_root_block_device_with_pmem_already_as_root() {
295 let dummy_file = TempFile::new().unwrap();
296 let dummy_path = dummy_file.as_path().to_str().unwrap().to_string();
297
298 let dummy_block_device = BlockDeviceConfig {
299 drive_id: String::from("1"),
300 partuuid: None,
301 is_root_device: true,
302 cache_type: CacheType::Unsafe,
303
304 is_read_only: Some(true),
305 path_on_host: Some(dummy_path),
306 rate_limiter: None,
307 file_engine_type: None,
308
309 socket: None,
310 };
311
312 let mut block_devs = BlockBuilder::new();
313 assert!(matches!(
314 block_devs
315 .insert(dummy_block_device.clone(), true)
316 .unwrap_err(),
317 DriveError::AddingSecondRootDevice,
318 ));
319 assert!(!block_devs.has_root_device());
320 assert_eq!(block_devs.devices.len(), 0);
321 }
322
323 #[test]
324 fn test_add_two_root_block_devs() {
325 let dummy_file_1 = TempFile::new().unwrap();
326 let dummy_path_1 = dummy_file_1.as_path().to_str().unwrap().to_string();
327 let root_block_device_1 = BlockDeviceConfig {
328 drive_id: String::from("1"),
329 partuuid: None,
330 is_root_device: true,
331 cache_type: CacheType::Unsafe,
332
333 is_read_only: Some(false),
334 path_on_host: Some(dummy_path_1),
335 rate_limiter: None,
336 file_engine_type: None,
337
338 socket: None,
339 };
340
341 let dummy_file_2 = TempFile::new().unwrap();
342 let dummy_path_2 = dummy_file_2.as_path().to_str().unwrap().to_string();
343 let root_block_device_2 = BlockDeviceConfig {
344 drive_id: String::from("2"),
345 partuuid: None,
346 is_root_device: true,
347 cache_type: CacheType::Unsafe,
348
349 is_read_only: Some(false),
350 path_on_host: Some(dummy_path_2),
351 rate_limiter: None,
352 file_engine_type: None,
353
354 socket: None,
355 };
356
357 let mut block_devs = BlockBuilder::new();
358 block_devs.insert(root_block_device_1, false).unwrap();
359 assert_eq!(
360 block_devs.insert(root_block_device_2, false).unwrap_err(),
361 DriveError::RootBlockDeviceAlreadyAdded
362 );
363 }
364
365 #[test]
366 fn test_add_root_block_device_first() {
368 let dummy_file_1 = TempFile::new().unwrap();
369 let dummy_path_1 = dummy_file_1.as_path().to_str().unwrap().to_string();
370 let root_block_device = BlockDeviceConfig {
371 drive_id: String::from("1"),
372 partuuid: None,
373 is_root_device: true,
374 cache_type: CacheType::Unsafe,
375
376 is_read_only: Some(false),
377 path_on_host: Some(dummy_path_1),
378 rate_limiter: None,
379 file_engine_type: None,
380
381 socket: None,
382 };
383
384 let dummy_file_2 = TempFile::new().unwrap();
385 let dummy_path_2 = dummy_file_2.as_path().to_str().unwrap().to_string();
386 let dummy_block_dev_2 = BlockDeviceConfig {
387 drive_id: String::from("2"),
388 partuuid: None,
389 is_root_device: false,
390 cache_type: CacheType::Unsafe,
391
392 is_read_only: Some(false),
393 path_on_host: Some(dummy_path_2),
394 rate_limiter: None,
395 file_engine_type: None,
396
397 socket: None,
398 };
399
400 let dummy_file_3 = TempFile::new().unwrap();
401 let dummy_path_3 = dummy_file_3.as_path().to_str().unwrap().to_string();
402 let dummy_block_dev_3 = BlockDeviceConfig {
403 drive_id: String::from("3"),
404 partuuid: None,
405 is_root_device: false,
406 cache_type: CacheType::Unsafe,
407
408 is_read_only: Some(false),
409 path_on_host: Some(dummy_path_3),
410 rate_limiter: None,
411 file_engine_type: None,
412
413 socket: None,
414 };
415
416 let mut block_devs = BlockBuilder::new();
417 block_devs.insert(dummy_block_dev_2.clone(), false).unwrap();
418 block_devs.insert(dummy_block_dev_3.clone(), false).unwrap();
419 block_devs.insert(root_block_device.clone(), false).unwrap();
420
421 assert_eq!(block_devs.devices.len(), 3);
422
423 let mut block_iter = block_devs.devices.iter();
424 assert_eq!(
425 block_iter.next().unwrap().lock().unwrap().id(),
426 root_block_device.drive_id
427 );
428 assert_eq!(
429 block_iter.next().unwrap().lock().unwrap().id(),
430 dummy_block_dev_2.drive_id
431 );
432 assert_eq!(
433 block_iter.next().unwrap().lock().unwrap().id(),
434 dummy_block_dev_3.drive_id
435 );
436 }
437
438 #[test]
439 fn test_root_block_device_add_last() {
441 let dummy_file_1 = TempFile::new().unwrap();
442 let dummy_path_1 = dummy_file_1.as_path().to_str().unwrap().to_string();
443 let root_block_device = BlockDeviceConfig {
444 drive_id: String::from("1"),
445 partuuid: None,
446 is_root_device: true,
447 cache_type: CacheType::Unsafe,
448
449 is_read_only: Some(false),
450 path_on_host: Some(dummy_path_1),
451 rate_limiter: None,
452 file_engine_type: None,
453
454 socket: None,
455 };
456
457 let dummy_file_2 = TempFile::new().unwrap();
458 let dummy_path_2 = dummy_file_2.as_path().to_str().unwrap().to_string();
459 let dummy_block_dev_2 = BlockDeviceConfig {
460 drive_id: String::from("2"),
461 partuuid: None,
462 is_root_device: false,
463 cache_type: CacheType::Unsafe,
464
465 is_read_only: Some(false),
466 path_on_host: Some(dummy_path_2),
467 rate_limiter: None,
468 file_engine_type: None,
469
470 socket: None,
471 };
472
473 let dummy_file_3 = TempFile::new().unwrap();
474 let dummy_path_3 = dummy_file_3.as_path().to_str().unwrap().to_string();
475 let dummy_block_dev_3 = BlockDeviceConfig {
476 drive_id: String::from("3"),
477 partuuid: None,
478 is_root_device: false,
479 cache_type: CacheType::Unsafe,
480
481 is_read_only: Some(false),
482 path_on_host: Some(dummy_path_3),
483 rate_limiter: None,
484 file_engine_type: None,
485
486 socket: None,
487 };
488
489 let mut block_devs = BlockBuilder::new();
490 block_devs.insert(dummy_block_dev_2.clone(), false).unwrap();
491 block_devs.insert(dummy_block_dev_3.clone(), false).unwrap();
492 block_devs.insert(root_block_device.clone(), false).unwrap();
493
494 assert_eq!(block_devs.devices.len(), 3);
495
496 let mut block_iter = block_devs.devices.iter();
497 assert_eq!(
500 block_iter.next().unwrap().lock().unwrap().id(),
501 root_block_device.drive_id
502 );
503 assert_eq!(
504 block_iter.next().unwrap().lock().unwrap().id(),
505 dummy_block_dev_2.drive_id
506 );
507 assert_eq!(
508 block_iter.next().unwrap().lock().unwrap().id(),
509 dummy_block_dev_3.drive_id
510 );
511 }
512
513 #[test]
514 fn test_update() {
515 let dummy_file_1 = TempFile::new().unwrap();
516 let dummy_path_1 = dummy_file_1.as_path().to_str().unwrap().to_string();
517 let root_block_device = BlockDeviceConfig {
518 drive_id: String::from("1"),
519 partuuid: None,
520 is_root_device: true,
521 cache_type: CacheType::Unsafe,
522
523 is_read_only: Some(false),
524 path_on_host: Some(dummy_path_1.clone()),
525 rate_limiter: None,
526 file_engine_type: None,
527
528 socket: None,
529 };
530
531 let dummy_file_2 = TempFile::new().unwrap();
532 let dummy_path_2 = dummy_file_2.as_path().to_str().unwrap().to_string();
533 let mut dummy_block_device_2 = BlockDeviceConfig {
534 drive_id: String::from("2"),
535 partuuid: None,
536 is_root_device: false,
537 cache_type: CacheType::Unsafe,
538
539 is_read_only: Some(false),
540 path_on_host: Some(dummy_path_2.clone()),
541 rate_limiter: None,
542 file_engine_type: None,
543
544 socket: None,
545 };
546
547 let mut block_devs = BlockBuilder::new();
548
549 block_devs.insert(root_block_device, false).unwrap();
551 block_devs
552 .insert(dummy_block_device_2.clone(), false)
553 .unwrap();
554
555 assert_eq!(
557 block_devs.get_index_of_drive_id(&String::from("1")),
558 Some(0)
559 );
560
561 assert!(
563 block_devs
564 .get_index_of_drive_id(&String::from("foo"))
565 .is_none()
566 );
567
568 assert!(
571 block_devs
572 .get_index_of_drive_id(&dummy_block_device_2.drive_id)
573 .is_some()
574 );
575 dummy_block_device_2.is_read_only = Some(true);
577 block_devs
578 .insert(dummy_block_device_2.clone(), false)
579 .unwrap();
580
581 let index = block_devs
582 .get_index_of_drive_id(&dummy_block_device_2.drive_id)
583 .unwrap();
584 assert!(block_devs.devices[index].lock().unwrap().read_only());
586
587 let dummy_path_3 = String::from("test_update_3");
589 dummy_block_device_2.path_on_host = Some(dummy_path_3);
590 assert!(matches!(
591 block_devs.insert(dummy_block_device_2.clone(), false),
592 Err(DriveError::CreateBlockDevice(BlockError::VirtioBackend(
593 VirtioBlockError::BackingFile(_, _)
594 )))
595 ));
596
597 dummy_block_device_2.path_on_host = Some(dummy_path_2.clone());
599 dummy_block_device_2.is_root_device = true;
600 assert_eq!(
601 block_devs.insert(dummy_block_device_2, false),
602 Err(DriveError::RootBlockDeviceAlreadyAdded)
603 );
604
605 let root_block_device = BlockDeviceConfig {
606 drive_id: String::from("1"),
607 partuuid: None,
608 is_root_device: true,
609 cache_type: CacheType::Unsafe,
610
611 is_read_only: Some(false),
612 path_on_host: Some(dummy_path_1),
613 rate_limiter: None,
614 file_engine_type: None,
615
616 socket: None,
617 };
618 let mut root_block_device_old = root_block_device;
620 root_block_device_old.is_root_device = false;
621 let root_block_device_new = BlockDeviceConfig {
622 drive_id: String::from("2"),
623 partuuid: Some("0eaa91a0-01".to_string()),
624 is_root_device: true,
625 cache_type: CacheType::Unsafe,
626
627 is_read_only: Some(false),
628 path_on_host: Some(dummy_path_2),
629 rate_limiter: None,
630 file_engine_type: None,
631
632 socket: None,
633 };
634
635 block_devs.insert(root_block_device_old, false).unwrap();
636 let root_block_id = root_block_device_new.drive_id.clone();
637 block_devs.insert(root_block_device_new, false).unwrap();
638 assert!(block_devs.has_root_device());
639 assert_eq!(block_devs.devices[0].lock().unwrap().id(), root_block_id);
641 }
642
643 #[test]
644 fn test_block_config() {
645 let dummy_file = TempFile::new().unwrap();
646
647 let dummy_block_device = BlockDeviceConfig {
648 drive_id: String::from("1"),
649 partuuid: None,
650 is_root_device: true,
651 cache_type: CacheType::Unsafe,
652
653 is_read_only: Some(true),
654 path_on_host: Some(dummy_file.as_path().to_str().unwrap().to_string()),
655 rate_limiter: None,
656 file_engine_type: Some(FileEngineType::Sync),
657
658 socket: None,
659 };
660
661 let mut block_devs = BlockBuilder::new();
662 block_devs
663 .insert(dummy_block_device.clone(), false)
664 .unwrap();
665
666 let configs = block_devs.configs();
667 assert_eq!(configs.len(), 1);
668 assert_eq!(configs.first().unwrap(), &dummy_block_device);
669 }
670
671 #[test]
672 fn test_add_device() {
673 let mut block_devs = BlockBuilder::new();
674 let backing_file = TempFile::new().unwrap();
675
676 let block_id = "test_id";
677 let config = BlockDeviceConfig {
678 drive_id: block_id.to_string(),
679 partuuid: None,
680 is_root_device: true,
681 cache_type: CacheType::default(),
682
683 is_read_only: Some(true),
684 path_on_host: Some(backing_file.as_path().to_str().unwrap().to_string()),
685 rate_limiter: None,
686 file_engine_type: None,
687
688 socket: None,
689 };
690
691 let block = Block::new(config).unwrap();
692
693 block_devs.add_virtio_device(Arc::new(Mutex::new(block)));
694 assert_eq!(block_devs.devices.len(), 1);
695 assert_eq!(
696 block_devs.devices.pop_back().unwrap().lock().unwrap().id(),
697 block_id
698 );
699 }
700}