1 // Copyright © 2021 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 use std::fs::File; 6 use std::io::{self, Read, Seek, SeekFrom, Write}; 7 8 use remain::sorted; 9 use thiserror::Error; 10 11 use crate::vhdx::vhdx_bat::{self, BatEntry, VhdxBatError}; 12 use crate::vhdx::vhdx_metadata::{self, DiskSpec}; 13 14 const SECTOR_SIZE: u64 = 512; 15 16 #[sorted] 17 #[derive(Error, Debug)] 18 pub enum VhdxIoError { 19 #[error("Invalid BAT entry state")] 20 InvalidBatEntryState, 21 #[error("Invalid BAT entry count")] 22 InvalidBatIndex, 23 #[error("Invalid disk size")] 24 InvalidDiskSize, 25 #[error("Failed reading sector blocks from file {0}")] 26 ReadSectorBlock(#[source] io::Error), 27 #[error("Failed changing file length {0}")] 28 ResizeFile(#[source] io::Error), 29 #[error("Differencing mode is not supported yet")] 30 UnsupportedMode, 31 #[error("Failed writing BAT to file {0}")] 32 WriteBat(#[source] VhdxBatError), 33 } 34 35 pub type Result<T> = std::result::Result<T, VhdxIoError>; 36 37 macro_rules! align { 38 ($n:expr, $align:expr) => {{ 39 $n.div_ceil($align) * $align 40 }}; 41 } 42 43 #[derive(Default)] 44 struct Sector { 45 bat_index: u64, 46 free_sectors: u64, 47 free_bytes: u64, 48 file_offset: u64, 49 block_offset: u64, 50 } 51 52 impl Sector { 53 /// Translate sector index and count of data in file to actual offsets and 54 /// BAT index. 55 pub fn new( 56 disk_spec: &DiskSpec, 57 bat: &[BatEntry], 58 sector_index: u64, 59 sector_count: u64, 60 ) -> Result<Sector> { 61 let mut sector = Sector::default(); 62 63 sector.bat_index = sector_index / disk_spec.sectors_per_block as u64; 64 sector.block_offset = sector_index % disk_spec.sectors_per_block as u64; 65 sector.free_sectors = disk_spec.sectors_per_block as u64 - sector.block_offset; 66 if sector.free_sectors > sector_count { 67 sector.free_sectors = sector_count; 68 } 69 70 sector.free_bytes = sector.free_sectors * disk_spec.logical_sector_size as u64; 71 sector.block_offset *= disk_spec.logical_sector_size as u64; 72 73 let bat_entry = match bat.get(sector.bat_index as usize) { 74 Some(entry) => entry.0, 75 None => { 76 return Err(VhdxIoError::InvalidBatIndex); 77 } 78 }; 79 sector.file_offset = bat_entry & vhdx_bat::BAT_FILE_OFF_MASK; 80 if sector.file_offset != 0 { 81 sector.file_offset += sector.block_offset; 82 } 83 84 Ok(sector) 85 } 86 } 87 88 /// VHDx IO read routine: requires relative sector index and count for the 89 /// requested data. 90 pub fn read( 91 f: &mut File, 92 buf: &mut [u8], 93 disk_spec: &DiskSpec, 94 bat: &[BatEntry], 95 mut sector_index: u64, 96 mut sector_count: u64, 97 ) -> Result<usize> { 98 let mut read_count: usize = 0; 99 100 while sector_count > 0 { 101 if disk_spec.has_parent { 102 return Err(VhdxIoError::UnsupportedMode); 103 } else { 104 let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?; 105 106 let bat_entry = match bat.get(sector.bat_index as usize) { 107 Some(entry) => entry.0, 108 None => { 109 return Err(VhdxIoError::InvalidBatIndex); 110 } 111 }; 112 113 match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK { 114 vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT 115 | vhdx_bat::PAYLOAD_BLOCK_UNDEFINED 116 | vhdx_bat::PAYLOAD_BLOCK_UNMAPPED 117 | vhdx_bat::PAYLOAD_BLOCK_ZERO => {} 118 vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => { 119 f.seek(SeekFrom::Start(sector.file_offset)) 120 .map_err(VhdxIoError::ReadSectorBlock)?; 121 f.read_exact( 122 &mut buf[read_count 123 ..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)], 124 ) 125 .map_err(VhdxIoError::ReadSectorBlock)?; 126 } 127 vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => { 128 return Err(VhdxIoError::UnsupportedMode); 129 } 130 _ => { 131 return Err(VhdxIoError::InvalidBatEntryState); 132 } 133 }; 134 sector_count -= sector.free_sectors; 135 sector_index += sector.free_sectors; 136 read_count += sector.free_bytes as usize; 137 }; 138 } 139 Ok(read_count) 140 } 141 142 /// VHDx IO write routine: requires relative sector index and count for the 143 /// requested data. 144 pub fn write( 145 f: &mut File, 146 buf: &[u8], 147 disk_spec: &mut DiskSpec, 148 bat_offset: u64, 149 bat: &mut [BatEntry], 150 mut sector_index: u64, 151 mut sector_count: u64, 152 ) -> Result<usize> { 153 let mut write_count: usize = 0; 154 155 while sector_count > 0 { 156 if disk_spec.has_parent { 157 return Err(VhdxIoError::UnsupportedMode); 158 } else { 159 let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?; 160 161 let bat_entry = match bat.get(sector.bat_index as usize) { 162 Some(entry) => entry.0, 163 None => { 164 return Err(VhdxIoError::InvalidBatIndex); 165 } 166 }; 167 168 match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK { 169 vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT 170 | vhdx_bat::PAYLOAD_BLOCK_UNDEFINED 171 | vhdx_bat::PAYLOAD_BLOCK_UNMAPPED 172 | vhdx_bat::PAYLOAD_BLOCK_ZERO => { 173 let file_offset = 174 align!(disk_spec.image_size, vhdx_metadata::BLOCK_SIZE_MIN as u64); 175 let new_size = file_offset 176 .checked_add(disk_spec.block_size as u64) 177 .ok_or(VhdxIoError::InvalidDiskSize)?; 178 179 f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?; 180 disk_spec.image_size = new_size; 181 182 let new_bat_entry = file_offset 183 | (vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT & vhdx_bat::BAT_STATE_BIT_MASK); 184 bat[sector.bat_index as usize] = BatEntry(new_bat_entry); 185 BatEntry::write_bat_entries(f, bat_offset, bat) 186 .map_err(VhdxIoError::WriteBat)?; 187 188 if file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 { 189 break; 190 } 191 192 f.seek(SeekFrom::Start(file_offset)) 193 .map_err(VhdxIoError::ReadSectorBlock)?; 194 f.write_all( 195 &buf[write_count 196 ..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)], 197 ) 198 .map_err(VhdxIoError::ReadSectorBlock)?; 199 } 200 vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => { 201 if sector.file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 { 202 break; 203 } 204 205 f.seek(SeekFrom::Start(sector.file_offset)) 206 .map_err(VhdxIoError::ReadSectorBlock)?; 207 f.write_all( 208 &buf[write_count 209 ..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)], 210 ) 211 .map_err(VhdxIoError::ReadSectorBlock)?; 212 } 213 vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => { 214 return Err(VhdxIoError::UnsupportedMode); 215 } 216 _ => { 217 return Err(VhdxIoError::InvalidBatEntryState); 218 } 219 }; 220 sector_count -= sector.free_sectors; 221 sector_index += sector.free_sectors; 222 write_count += sector.free_bytes as usize; 223 }; 224 } 225 Ok(write_count) 226 } 227