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.
new( disk_spec: &DiskSpec, bat: &[BatEntry], sector_index: u64, sector_count: u64, ) -> Result<Sector>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.
read( f: &mut File, buf: &mut [u8], disk_spec: &DiskSpec, bat: &[BatEntry], mut sector_index: u64, mut sector_count: u64, ) -> Result<usize>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 if disk_spec.has_parent {
99 return Err(VhdxIoError::UnsupportedMode);
100 }
101
102 let mut read_count: usize = 0;
103 while sector_count > 0 {
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
123 [read_count..(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 Ok(read_count)
139 }
140
141 /// VHDx IO write routine: requires relative sector index and count for the
142 /// requested data.
write( f: &mut File, buf: &[u8], disk_spec: &mut DiskSpec, bat_offset: u64, bat: &mut [BatEntry], mut sector_index: u64, mut sector_count: u64, ) -> Result<usize>143 pub fn write(
144 f: &mut File,
145 buf: &[u8],
146 disk_spec: &mut DiskSpec,
147 bat_offset: u64,
148 bat: &mut [BatEntry],
149 mut sector_index: u64,
150 mut sector_count: u64,
151 ) -> Result<usize> {
152 if disk_spec.has_parent {
153 return Err(VhdxIoError::UnsupportedMode);
154 }
155
156 let mut write_count: usize = 0;
157 while sector_count > 0 {
158 let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
159
160 let bat_entry = match bat.get(sector.bat_index as usize) {
161 Some(entry) => entry.0,
162 None => {
163 return Err(VhdxIoError::InvalidBatIndex);
164 }
165 };
166
167 match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
168 vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
169 | vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
170 | vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
171 | vhdx_bat::PAYLOAD_BLOCK_ZERO => {
172 let file_offset =
173 align!(disk_spec.image_size, vhdx_metadata::BLOCK_SIZE_MIN as u64);
174 let new_size = file_offset
175 .checked_add(disk_spec.block_size as u64)
176 .ok_or(VhdxIoError::InvalidDiskSize)?;
177
178 f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?;
179 disk_spec.image_size = new_size;
180
181 let new_bat_entry = file_offset
182 | (vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT & vhdx_bat::BAT_STATE_BIT_MASK);
183 bat[sector.bat_index as usize] = BatEntry(new_bat_entry);
184 BatEntry::write_bat_entries(f, bat_offset, bat).map_err(VhdxIoError::WriteBat)?;
185
186 if file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
187 break;
188 }
189
190 f.seek(SeekFrom::Start(file_offset))
191 .map_err(VhdxIoError::ReadSectorBlock)?;
192 f.write_all(
193 &buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
194 )
195 .map_err(VhdxIoError::ReadSectorBlock)?;
196 }
197 vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
198 if sector.file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
199 break;
200 }
201
202 f.seek(SeekFrom::Start(sector.file_offset))
203 .map_err(VhdxIoError::ReadSectorBlock)?;
204 f.write_all(
205 &buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
206 )
207 .map_err(VhdxIoError::ReadSectorBlock)?;
208 }
209 vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
210 return Err(VhdxIoError::UnsupportedMode);
211 }
212 _ => {
213 return Err(VhdxIoError::InvalidBatEntryState);
214 }
215 };
216 sector_count -= sector.free_sectors;
217 sector_index += sector.free_sectors;
218 write_count += sector.free_bytes as usize;
219 }
220 Ok(write_count)
221 }
222