1#![forbid(unsafe_code)]
8
9pub mod blob;
10pub mod resolver;
11
12#[cfg(test)]
13mod tests;
14
15use blob::Blob;
16use disk_backend::DiskError;
17use disk_backend::DiskIo;
18use disk_backend::UnmapBehavior;
19use guestmem::MemoryWrite;
20use inspect::Inspect;
21use scsi_buffers::RequestBuffers;
22use std::sync::Arc;
23use thiserror::Error;
24use vhd1_defs::VhdFooter;
25use zerocopy::FromZeros;
26use zerocopy::IntoBytes;
27
28const DEFAULT_SECTOR_SIZE: u32 = 512;
29
30#[derive(Inspect)]
32pub struct BlobDisk {
33 blob: Arc<dyn Blob + Send + Sync>,
34 sector_count: u64,
35 sector_size: u32,
36 sector_shift: u32,
37 disk_id: Option<[u8; 16]>,
38}
39
40#[derive(Debug, Error)]
41enum ErrorInner {
42 #[error("blob is too small")]
43 BlobTooSmall,
44 #[error("failed to read the vhd footer")]
45 VhdFooter(#[source] std::io::Error),
46 #[error("invalid vhd1 footer cookie")]
47 VhdFooterCookie,
48 #[error("invalid vhd1 footer checksum")]
49 VhdFooterChecksum,
50 #[error("unsupported vhd version: {0:#x}")]
51 UnsupportedVhdVersion(u32),
52 #[error("not a fixed vhd")]
53 NotFixedVhd,
54 #[error("invalid disk size: {0}")]
55 InvalidDiskSize(u64),
56}
57
58#[derive(Debug, Error)]
60#[error(transparent)]
61pub struct Vhd1Error(#[from] ErrorInner);
62
63impl BlobDisk {
64 pub fn new(blob: impl 'static + Blob + Send + Sync) -> Self {
66 let blob = Arc::new(blob);
67 let sector_count = blob.len() / DEFAULT_SECTOR_SIZE as u64;
68 Self::new_inner(blob, sector_count, None)
69 }
70
71 pub async fn new_fixed_vhd1(blob: impl 'static + Blob + Send + Sync) -> anyhow::Result<Self> {
73 let blob = Arc::new(blob);
74 let blob_len = blob.len();
75 let footer_offset = blob_len
76 .checked_sub(VhdFooter::LEN)
77 .ok_or(ErrorInner::BlobTooSmall)?;
78
79 let mut footer = VhdFooter::new_zeroed();
80 blob.read(footer.as_mut_bytes(), footer_offset)
81 .await
82 .map_err(ErrorInner::VhdFooter)?;
83
84 if footer.cookie != VhdFooter::COOKIE_MAGIC {
85 return Err(ErrorInner::VhdFooterCookie.into());
86 }
87 if footer.checksum.get() != footer.compute_checksum() {
88 return Err(ErrorInner::VhdFooterChecksum.into());
89 }
90 if footer.file_format_version.get() != VhdFooter::FILE_FORMAT_VERSION_MAGIC {
91 return Err(ErrorInner::UnsupportedVhdVersion(footer.file_format_version.get()).into());
92 }
93 if footer.disk_type.get() != VhdFooter::DISK_TYPE_FIXED {
94 return Err(ErrorInner::NotFixedVhd.into());
95 }
96 let disk_size = footer.current_size.get();
97 if disk_size > footer_offset || disk_size % (DEFAULT_SECTOR_SIZE as u64) != 0 {
98 return Err(ErrorInner::InvalidDiskSize(disk_size).into());
99 }
100
101 Ok(Self::new_inner(
102 blob,
103 disk_size / DEFAULT_SECTOR_SIZE as u64,
104 Some(footer.unique_id.into()),
105 ))
106 }
107
108 fn new_inner(
109 blob: Arc<dyn Blob + Send + Sync>,
110 sector_count: u64,
111 disk_id: Option<[u8; 16]>,
112 ) -> Self {
113 Self {
114 blob,
115 sector_count,
116 sector_size: DEFAULT_SECTOR_SIZE,
117 sector_shift: DEFAULT_SECTOR_SIZE.trailing_zeros(),
118 disk_id,
119 }
120 }
121}
122
123impl DiskIo for BlobDisk {
124 fn disk_type(&self) -> &str {
125 "blob"
126 }
127
128 fn sector_count(&self) -> u64 {
129 self.sector_count
130 }
131
132 fn sector_size(&self) -> u32 {
133 self.sector_size
134 }
135
136 fn disk_id(&self) -> Option<[u8; 16]> {
137 self.disk_id
138 }
139
140 fn physical_sector_size(&self) -> u32 {
141 4096
142 }
143
144 fn is_fua_respected(&self) -> bool {
145 false
146 }
147
148 fn is_read_only(&self) -> bool {
149 true
150 }
151
152 async fn read_vectored(
153 &self,
154 buffers: &RequestBuffers<'_>,
155 sector: u64,
156 ) -> Result<(), DiskError> {
157 if sector + (buffers.len() as u64 >> self.sector_shift) > self.sector_count {
163 return Err(DiskError::IllegalBlock);
164 }
165 let mut buf = vec![0; buffers.len()];
166 self.blob
173 .read(&mut buf, sector << self.sector_shift)
174 .await
175 .map_err(DiskError::Io)?;
176
177 buffers.writer().write(&buf)?;
178 Ok(())
179 }
180
181 async fn write_vectored(
182 &self,
183 _buffers: &RequestBuffers<'_>,
184 _sector: u64,
185 _fua: bool,
186 ) -> Result<(), DiskError> {
187 Err(DiskError::ReadOnly)
188 }
189
190 async fn sync_cache(&self) -> Result<(), DiskError> {
191 Err(DiskError::ReadOnly)
192 }
193
194 async fn unmap(
195 &self,
196 _sector: u64,
197 _count: u64,
198 _block_level_only: bool,
199 ) -> Result<(), DiskError> {
200 Err(DiskError::ReadOnly)
201 }
202
203 fn unmap_behavior(&self) -> UnmapBehavior {
204 UnmapBehavior::Ignored
205 }
206}