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