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
58impl BlobDisk {
59 pub fn new(blob: impl 'static + Blob + Send + Sync) -> Self {
61 let blob = Arc::new(blob);
62 let sector_count = blob.len() / DEFAULT_SECTOR_SIZE as u64;
63 Self::new_inner(blob, sector_count, None)
64 }
65
66 pub async fn new_fixed_vhd1(blob: impl 'static + Blob + Send + Sync) -> anyhow::Result<Self> {
68 let blob = Arc::new(blob);
69 let blob_len = blob.len();
70 let footer_offset = blob_len
71 .checked_sub(VhdFooter::LEN)
72 .ok_or(ErrorInner::BlobTooSmall)?;
73
74 let mut footer = VhdFooter::new_zeroed();
75 blob.read(footer.as_mut_bytes(), footer_offset)
76 .await
77 .map_err(ErrorInner::VhdFooter)?;
78
79 if footer.cookie != VhdFooter::COOKIE_MAGIC {
80 return Err(ErrorInner::VhdFooterCookie.into());
81 }
82 if footer.checksum.get() != footer.compute_checksum() {
83 return Err(ErrorInner::VhdFooterChecksum.into());
84 }
85 if footer.file_format_version.get() != VhdFooter::FILE_FORMAT_VERSION_MAGIC {
86 return Err(ErrorInner::UnsupportedVhdVersion(footer.file_format_version.get()).into());
87 }
88 if footer.disk_type.get() != VhdFooter::DISK_TYPE_FIXED {
89 return Err(ErrorInner::NotFixedVhd.into());
90 }
91 let disk_size = footer.current_size.get();
92 if disk_size > footer_offset || disk_size % (DEFAULT_SECTOR_SIZE as u64) != 0 {
93 return Err(ErrorInner::InvalidDiskSize(disk_size).into());
94 }
95
96 Ok(Self::new_inner(
97 blob,
98 disk_size / DEFAULT_SECTOR_SIZE as u64,
99 Some(footer.unique_id.into()),
100 ))
101 }
102
103 fn new_inner(
104 blob: Arc<dyn Blob + Send + Sync>,
105 sector_count: u64,
106 disk_id: Option<[u8; 16]>,
107 ) -> Self {
108 Self {
109 blob,
110 sector_count,
111 sector_size: DEFAULT_SECTOR_SIZE,
112 sector_shift: DEFAULT_SECTOR_SIZE.trailing_zeros(),
113 disk_id,
114 }
115 }
116}
117
118impl DiskIo for BlobDisk {
119 fn disk_type(&self) -> &str {
120 "blob"
121 }
122
123 fn sector_count(&self) -> u64 {
124 self.sector_count
125 }
126
127 fn sector_size(&self) -> u32 {
128 self.sector_size
129 }
130
131 fn disk_id(&self) -> Option<[u8; 16]> {
132 self.disk_id
133 }
134
135 fn physical_sector_size(&self) -> u32 {
136 4096
137 }
138
139 fn is_fua_respected(&self) -> bool {
140 false
141 }
142
143 fn is_read_only(&self) -> bool {
144 true
145 }
146
147 async fn read_vectored(
148 &self,
149 buffers: &RequestBuffers<'_>,
150 sector: u64,
151 ) -> Result<(), DiskError> {
152 if sector + (buffers.len() as u64 >> self.sector_shift) > self.sector_count {
158 return Err(DiskError::IllegalBlock);
159 }
160 let mut buf = vec![0; buffers.len()];
161 self.blob
168 .read(&mut buf, sector << self.sector_shift)
169 .await
170 .map_err(DiskError::Io)?;
171
172 buffers.writer().write(&buf)?;
173 Ok(())
174 }
175
176 async fn write_vectored(
177 &self,
178 _buffers: &RequestBuffers<'_>,
179 _sector: u64,
180 _fua: bool,
181 ) -> Result<(), DiskError> {
182 Err(DiskError::ReadOnly)
183 }
184
185 async fn sync_cache(&self) -> Result<(), DiskError> {
186 Err(DiskError::ReadOnly)
187 }
188
189 async fn unmap(
190 &self,
191 _sector: u64,
192 _count: u64,
193 _block_level_only: bool,
194 ) -> Result<(), DiskError> {
195 Err(DiskError::ReadOnly)
196 }
197
198 fn unmap_behavior(&self) -> UnmapBehavior {
199 UnmapBehavior::Ignored
200 }
201}