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