xref: /cloud-hypervisor/block/src/vhdx/vhdx_io.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, 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