xref: /cloud-hypervisor/block/src/vhdx/vhdx_io.rs (revision 463c9b8e565ad6de149de36b9b0a8ae9d22025bc)
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