1 // Copyright © 2021 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 use std::collections::btree_map::BTreeMap; 6 use std::fs::File; 7 use std::io::{Read, Seek, SeekFrom, Write}; 8 9 use byteorder::{BigEndian, ByteOrder}; 10 use remain::sorted; 11 use thiserror::Error; 12 use uuid::Uuid; 13 14 use crate::vhdx::vhdx_bat::{BatEntry, VhdxBatError}; 15 use crate::vhdx::vhdx_header::{RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError}; 16 use crate::vhdx::vhdx_io::VhdxIoError; 17 use crate::vhdx::vhdx_metadata::{DiskSpec, VhdxMetadataError}; 18 use crate::BlockBackend; 19 20 mod vhdx_bat; 21 mod vhdx_header; 22 mod vhdx_io; 23 mod vhdx_metadata; 24 25 #[sorted] 26 #[derive(Error, Debug)] 27 pub enum VhdxError { 28 #[error("Not a VHDx file {0}")] 29 NotVhdx(#[source] VhdxHeaderError), 30 #[error("Failed to parse VHDx header {0}")] 31 ParseVhdxHeader(#[source] VhdxHeaderError), 32 #[error("Failed to parse VHDx metadata {0}")] 33 ParseVhdxMetadata(#[source] VhdxMetadataError), 34 #[error("Failed to parse VHDx region entries {0}")] 35 ParseVhdxRegionEntry(#[source] VhdxHeaderError), 36 #[error("Failed reading metadata {0}")] 37 ReadBatEntry(#[source] VhdxBatError), 38 #[error("Failed reading sector from disk {0}")] 39 ReadFailed(#[source] VhdxIoError), 40 #[error("Failed writing to sector on disk {0}")] 41 WriteFailed(#[source] VhdxIoError), 42 } 43 44 pub type Result<T> = std::result::Result<T, VhdxError>; 45 46 #[derive(Debug)] 47 pub struct Vhdx { 48 file: File, 49 vhdx_header: VhdxHeader, 50 region_entries: BTreeMap<u64, u64>, 51 bat_entry: RegionTableEntry, 52 mdr_entry: RegionTableEntry, 53 disk_spec: DiskSpec, 54 bat_entries: Vec<BatEntry>, 55 current_offset: u64, 56 first_write: bool, 57 } 58 59 impl Vhdx { 60 /// Parse the Vhdx header, BAT, and metadata from a file and store info 61 // in Vhdx structure. 62 pub fn new(mut file: File) -> Result<Vhdx> { 63 let vhdx_header = VhdxHeader::new(&mut file).map_err(VhdxError::ParseVhdxHeader)?; 64 65 let collected_entries = RegionInfo::new( 66 &mut file, 67 vhdx_header::REGION_TABLE_1_START, 68 vhdx_header.region_entry_count(), 69 ) 70 .map_err(VhdxError::ParseVhdxRegionEntry)?; 71 72 let bat_entry = collected_entries.bat_entry; 73 let mdr_entry = collected_entries.mdr_entry; 74 75 let disk_spec = 76 DiskSpec::new(&mut file, &mdr_entry).map_err(VhdxError::ParseVhdxMetadata)?; 77 let bat_entries = BatEntry::collect_bat_entries(&mut file, &disk_spec, &bat_entry) 78 .map_err(VhdxError::ReadBatEntry)?; 79 80 Ok(Vhdx { 81 file, 82 vhdx_header, 83 region_entries: collected_entries.region_entries, 84 bat_entry, 85 mdr_entry, 86 disk_spec, 87 bat_entries, 88 current_offset: 0, 89 first_write: true, 90 }) 91 } 92 93 pub fn virtual_disk_size(&self) -> u64 { 94 self.disk_spec.virtual_disk_size 95 } 96 } 97 98 impl Read for Vhdx { 99 /// Wrapper function to satisfy Read trait implementation for VHDx disk. 100 /// Convert the offset to sector index and buffer length to sector count. 101 fn read(&mut self, buf: &mut [u8]) -> std::result::Result<usize, std::io::Error> { 102 let sector_count = (buf.len() as u64).div_ceil(self.disk_spec.logical_sector_size as u64); 103 let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64; 104 105 let result = vhdx_io::read( 106 &mut self.file, 107 buf, 108 &self.disk_spec, 109 &self.bat_entries, 110 sector_index, 111 sector_count, 112 ) 113 .map_err(|e| { 114 std::io::Error::new( 115 std::io::ErrorKind::Other, 116 format!( 117 "Failed reading {sector_count} sectors from VHDx at index {sector_index}: {e}" 118 ), 119 ) 120 })?; 121 122 self.current_offset += result as u64; 123 124 Ok(result) 125 } 126 } 127 128 impl Write for Vhdx { 129 fn flush(&mut self) -> std::result::Result<(), std::io::Error> { 130 self.file.flush() 131 } 132 133 /// Wrapper function to satisfy Write trait implementation for VHDx disk. 134 /// Convert the offset to sector index and buffer length to sector count. 135 fn write(&mut self, buf: &[u8]) -> std::result::Result<usize, std::io::Error> { 136 let sector_count = (buf.len() as u64).div_ceil(self.disk_spec.logical_sector_size as u64); 137 let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64; 138 139 if self.first_write { 140 self.first_write = false; 141 self.vhdx_header.update(&mut self.file).map_err(|e| { 142 std::io::Error::new( 143 std::io::ErrorKind::Other, 144 format!("Failed to update VHDx header: {e}"), 145 ) 146 })?; 147 } 148 149 let result = vhdx_io::write( 150 &mut self.file, 151 buf, 152 &mut self.disk_spec, 153 self.bat_entry.file_offset, 154 &mut self.bat_entries, 155 sector_index, 156 sector_count, 157 ) 158 .map_err(|e| { 159 std::io::Error::new( 160 std::io::ErrorKind::Other, 161 format!( 162 "Failed writing {sector_count} sectors on VHDx at index {sector_index}: {e}" 163 ), 164 ) 165 })?; 166 167 self.current_offset += result as u64; 168 169 Ok(result) 170 } 171 } 172 173 impl Seek for Vhdx { 174 /// Wrapper function to satisfy Seek trait implementation for VHDx disk. 175 /// Updates the offset field in the Vhdx struct. 176 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> { 177 let new_offset: Option<u64> = match pos { 178 SeekFrom::Start(off) => Some(off), 179 SeekFrom::End(off) => { 180 if off < 0 { 181 0i64.checked_sub(off).and_then(|increment| { 182 self.virtual_disk_size().checked_sub(increment as u64) 183 }) 184 } else { 185 self.virtual_disk_size().checked_add(off as u64) 186 } 187 } 188 SeekFrom::Current(off) => { 189 if off < 0 { 190 0i64.checked_sub(off) 191 .and_then(|increment| self.current_offset.checked_sub(increment as u64)) 192 } else { 193 self.current_offset.checked_add(off as u64) 194 } 195 } 196 }; 197 198 if let Some(o) = new_offset { 199 if o <= self.virtual_disk_size() { 200 self.current_offset = o; 201 return Ok(o); 202 } 203 } 204 205 Err(std::io::Error::new( 206 std::io::ErrorKind::InvalidData, 207 "Failed seek operation", 208 )) 209 } 210 } 211 212 impl BlockBackend for Vhdx { 213 fn size(&self) -> std::result::Result<u64, crate::Error> { 214 Ok(self.virtual_disk_size()) 215 } 216 } 217 218 impl Clone for Vhdx { 219 fn clone(&self) -> Self { 220 Vhdx { 221 file: self.file.try_clone().unwrap(), 222 vhdx_header: self.vhdx_header.clone(), 223 region_entries: self.region_entries.clone(), 224 bat_entry: self.bat_entry, 225 mdr_entry: self.mdr_entry, 226 disk_spec: self.disk_spec.clone(), 227 bat_entries: self.bat_entries.clone(), 228 current_offset: self.current_offset, 229 first_write: self.first_write, 230 } 231 } 232 } 233 234 pub(crate) fn uuid_from_guid(buf: &[u8]) -> Uuid { 235 // The first 3 fields of UUID are stored in Big Endian format, and 236 // the last 8 bytes are stored as byte array. Therefore, we read the 237 // first 3 fields in Big Endian format instead of Little Endian. 238 Uuid::from_fields_le( 239 BigEndian::read_u32(&buf[0..4]), 240 BigEndian::read_u16(&buf[4..6]), 241 BigEndian::read_u16(&buf[6..8]), 242 buf[8..16].try_into().unwrap(), 243 ) 244 } 245