xref: /cloud-hypervisor/block/src/vhdx/vhdx_bat.rs (revision eeae63b4595fbf0cc69f62b6e9d9a79c543c4ac7)
1 // Copyright © 2021 Intel Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4 
5 use std::fs::File;
6 use std::io::{self, Seek, SeekFrom};
7 use std::mem::size_of;
8 
9 use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
10 use remain::sorted;
11 use thiserror::Error;
12 
13 use crate::vhdx::vhdx_header::RegionTableEntry;
14 use crate::vhdx::vhdx_metadata::DiskSpec;
15 
16 // Payload BAT Entry States
17 pub const PAYLOAD_BLOCK_NOT_PRESENT: u64 = 0;
18 pub const PAYLOAD_BLOCK_UNDEFINED: u64 = 1;
19 pub const PAYLOAD_BLOCK_ZERO: u64 = 2;
20 pub const PAYLOAD_BLOCK_UNMAPPED: u64 = 3;
21 pub const PAYLOAD_BLOCK_FULLY_PRESENT: u64 = 6;
22 pub const PAYLOAD_BLOCK_PARTIALLY_PRESENT: u64 = 7;
23 
24 // Mask for the BAT state
25 pub const BAT_STATE_BIT_MASK: u64 = 0x07;
26 // Mask for the offset within the file in units of 1 MB
27 pub const BAT_FILE_OFF_MASK: u64 = 0xFFFFFFFFFFF00000;
28 
29 #[sorted]
30 #[derive(Error, Debug)]
31 pub enum VhdxBatError {
32     #[error("Invalid BAT entry")]
33     InvalidBatEntry,
34     #[error("Invalid BAT entry count")]
35     InvalidEntryCount,
36     #[error("Failed to read BAT entry {0}")]
37     ReadBat(#[source] io::Error),
38     #[error("Failed to write BAT entry {0}")]
39     WriteBat(#[source] io::Error),
40 }
41 
42 pub type Result<T> = std::result::Result<T, VhdxBatError>;
43 
44 #[derive(Default, Clone, Debug)]
45 pub struct BatEntry(pub u64);
46 
47 impl BatEntry {
48     // Read all BAT entries presented on the disk and insert them to a vector
49     pub fn collect_bat_entries(
50         f: &mut File,
51         disk_spec: &DiskSpec,
52         bat_entry: &RegionTableEntry,
53     ) -> Result<Vec<BatEntry>> {
54         let entry_count = BatEntry::calculate_entries(
55             disk_spec.block_size,
56             disk_spec.virtual_disk_size,
57             disk_spec.chunk_ratio,
58         );
59         if entry_count as usize > (bat_entry.length as usize / size_of::<BatEntry>()) {
60             return Err(VhdxBatError::InvalidEntryCount);
61         }
62 
63         let mut bat: Vec<BatEntry> = Vec::with_capacity(bat_entry.length as usize);
64         let offset = bat_entry.file_offset;
65         for i in 0..entry_count {
66             f.seek(SeekFrom::Start(offset + i * size_of::<u64>() as u64))
67                 .map_err(VhdxBatError::ReadBat)?;
68 
69             let bat_entry = BatEntry(
70                 f.read_u64::<LittleEndian>()
71                     .map_err(VhdxBatError::ReadBat)?,
72             );
73             bat.insert(i as usize, bat_entry);
74         }
75 
76         Ok(bat)
77     }
78 
79     // Calculate the number of entries in the BAT
80     fn calculate_entries(block_size: u32, virtual_disk_size: u64, chunk_ratio: u64) -> u64 {
81         let data_blocks_count = virtual_disk_size.div_ceil(block_size as u64);
82         data_blocks_count + (data_blocks_count - 1) / chunk_ratio
83     }
84 
85     // Routine for writing BAT entries to the disk
86     pub fn write_bat_entries(
87         f: &mut File,
88         bat_offset: u64,
89         bat_entries: &[BatEntry],
90     ) -> Result<()> {
91         for i in 0..bat_entries.len() as u64 {
92             f.seek(SeekFrom::Start(bat_offset + i * size_of::<u64>() as u64))
93                 .map_err(VhdxBatError::WriteBat)?;
94             let bat_entry = match bat_entries.get(i as usize) {
95                 Some(entry) => entry.0,
96                 None => {
97                     return Err(VhdxBatError::InvalidBatEntry);
98                 }
99             };
100 
101             f.write_u64::<LittleEndian>(bat_entry)
102                 .map_err(VhdxBatError::WriteBat)?;
103         }
104         Ok(())
105     }
106 }
107