1#![expect(missing_docs)]
5#![forbid(unsafe_code)]
6
7mod readwriteat;
8
9use self::readwriteat::ReadWriteAt;
10use blocking::unblock;
11use disk_backend::DiskError;
12use disk_backend::DiskIo;
13use disk_backend::resolve::ResolveDiskParameters;
14use disk_backend::resolve::ResolvedDisk;
15use disk_backend_resources::FileDiskHandle;
16use guestmem::MemoryRead;
17use guestmem::MemoryWrite;
18use inspect::Inspect;
19use scsi_buffers::RequestBuffers;
20use std::fs;
21use std::sync::Arc;
22use thiserror::Error;
23use vm_resource::ResolveResource;
24use vm_resource::declare_static_resolver;
25use vm_resource::kind::DiskHandleKind;
26
27pub struct FileDiskResolver;
28declare_static_resolver!(FileDiskResolver, (DiskHandleKind, FileDiskHandle));
29
30#[derive(Debug, Error)]
31pub enum ResolveFileDiskError {
32 #[error("i/o error")]
33 Io(#[source] std::io::Error),
34 #[error("invalid disk")]
35 InvalidDisk(#[source] disk_backend::InvalidDisk),
36}
37
38impl ResolveResource<DiskHandleKind, FileDiskHandle> for FileDiskResolver {
39 type Output = ResolvedDisk;
40 type Error = ResolveFileDiskError;
41
42 fn resolve(
43 &self,
44 rsrc: FileDiskHandle,
45 input: ResolveDiskParameters<'_>,
46 ) -> Result<Self::Output, Self::Error> {
47 ResolvedDisk::new(
48 FileDisk::open(rsrc.0, input.read_only).map_err(ResolveFileDiskError::Io)?,
49 )
50 .map_err(ResolveFileDiskError::InvalidDisk)
51 }
52}
53
54#[derive(Debug, Inspect)]
55pub struct FileDisk {
56 file: Arc<fs::File>,
57 metadata: Metadata,
58 sector_shift: u32,
59}
60
61#[derive(Debug, Inspect)]
62pub struct Metadata {
63 pub disk_size: u64,
64 pub sector_size: u32,
65 pub physical_sector_size: u32,
66 pub read_only: bool,
67}
68
69#[cfg(windows)]
70impl std::os::windows::io::AsHandle for FileDisk {
71 fn as_handle(&self) -> std::os::windows::io::BorrowedHandle<'_> {
72 self.file.as_handle()
73 }
74}
75
76impl FileDisk {
77 pub fn open(file: fs::File, read_only: bool) -> Result<Self, std::io::Error> {
78 let metadata = Metadata {
79 disk_size: file.metadata()?.len(),
80 sector_size: 512,
81 physical_sector_size: 4096,
82 read_only,
83 };
84 Ok(Self::with_metadata(file, metadata))
85 }
86
87 pub fn with_metadata(file: fs::File, metadata: Metadata) -> Self {
93 assert!(metadata.sector_size.is_power_of_two());
94 assert!(metadata.sector_size >= 512);
95 let sector_shift = metadata.sector_size.trailing_zeros();
96 FileDisk {
97 file: Arc::new(file),
98 metadata,
99 sector_shift,
100 }
101 }
102
103 pub fn into_inner(self) -> fs::File {
104 Arc::try_unwrap(self.file).expect("no outstanding IOs")
105 }
106}
107
108impl FileDisk {
109 pub async fn read(&self, buffers: &RequestBuffers<'_>, sector: u64) -> Result<(), DiskError> {
110 if ((sector << self.sector_shift) + buffers.len() as u64) > self.metadata.disk_size {
111 return Err(DiskError::IllegalBlock);
112 }
113 let mut buffer = vec![0; buffers.len()];
114 let file = self.file.clone();
115 let offset = sector << self.sector_shift;
116 let buffer = unblock(move || -> Result<_, std::io::Error> {
117 let count = file.read_at(&mut buffer, offset)?;
118 if count != buffer.len() {
119 Err(std::io::Error::new(
120 std::io::ErrorKind::UnexpectedEof,
121 "short disk read",
122 ))
123 } else {
124 Ok(buffer)
125 }
126 })
127 .await
128 .map_err(DiskError::Io)?;
129 buffers.writer().write(&buffer)?;
130 Ok(())
131 }
132
133 pub async fn write(
134 &self,
135 buffers: &RequestBuffers<'_>,
136 sector: u64,
137 _fua: bool,
138 ) -> Result<(), DiskError> {
139 if ((sector << self.sector_shift) + buffers.len() as u64) > self.metadata.disk_size {
140 return Err(DiskError::IllegalBlock);
141 }
142 let mut buffer = vec![0; buffers.len()];
143 let file = self.file.clone();
144 buffers.reader().read(&mut buffer)?;
145 let offset = sector << self.sector_shift;
146 unblock(move || -> Result<(), std::io::Error> {
147 let count = file.write_at(&buffer, offset)?;
148 if count != buffer.len() {
149 Err(std::io::Error::other("short disk write"))
150 } else {
151 Ok(())
152 }
153 })
154 .await
155 .map_err(DiskError::Io)?;
156 Ok(())
157 }
158
159 pub async fn flush(&self) -> Result<(), DiskError> {
160 let file = self.file.clone();
161 unblock(move || file.sync_all())
162 .await
163 .map_err(DiskError::Io)?;
164 Ok(())
165 }
166}
167
168impl DiskIo for FileDisk {
169 fn disk_type(&self) -> &str {
170 "file"
171 }
172
173 fn sector_count(&self) -> u64 {
174 self.metadata.disk_size >> self.sector_shift
175 }
176
177 fn sector_size(&self) -> u32 {
178 self.metadata.sector_size
179 }
180
181 fn is_read_only(&self) -> bool {
182 self.metadata.read_only
183 }
184
185 fn disk_id(&self) -> Option<[u8; 16]> {
186 None
187 }
188
189 fn physical_sector_size(&self) -> u32 {
190 self.metadata.physical_sector_size
191 }
192
193 fn is_fua_respected(&self) -> bool {
194 false
195 }
196
197 async fn read_vectored(
198 &self,
199 buffers: &RequestBuffers<'_>,
200 sector: u64,
201 ) -> Result<(), DiskError> {
202 self.read(buffers, sector).await
203 }
204
205 async fn write_vectored(
206 &self,
207 buffers: &RequestBuffers<'_>,
208 sector: u64,
209 fua: bool,
210 ) -> Result<(), DiskError> {
211 self.write(buffers, sector, fua).await
212 }
213
214 async fn sync_cache(&self) -> Result<(), DiskError> {
215 self.flush().await
216 }
217
218 async fn unmap(
219 &self,
220 _sector: u64,
221 _count: u64,
222 _block_level_only: bool,
223 ) -> Result<(), DiskError> {
224 Ok(())
225 }
226
227 fn unmap_behavior(&self) -> disk_backend::UnmapBehavior {
228 disk_backend::UnmapBehavior::Ignored
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::FileDisk;
235 use disk_backend::Disk;
236 use disk_backend::DiskError;
237 use guestmem::GuestMemory;
238 use pal_async::async_test;
239 use scsi_buffers::OwnedRequestBuffers;
240
241 const SECTOR_SIZE: usize = 512;
242 const DISK_SIZE: u64 = 1024 * 1024;
243
244 fn file_disk() -> Disk {
245 let file = tempfile::tempfile().unwrap();
246 file.set_len(DISK_SIZE).unwrap();
247 Disk::new(FileDisk::open(file, false).unwrap()).unwrap()
248 }
249
250 #[async_test]
251 async fn short_read_returns_error() {
252 let file = tempfile::tempfile().unwrap();
253 file.set_len(DISK_SIZE).unwrap();
254 let disk = FileDisk::open(file.try_clone().unwrap(), false).unwrap();
255 file.set_len((SECTOR_SIZE + SECTOR_SIZE / 2) as u64)
256 .unwrap();
257
258 let mem = GuestMemory::allocate(SECTOR_SIZE);
259 mem.write_at(0, &[0xcd; SECTOR_SIZE]).unwrap();
260 let buffers = OwnedRequestBuffers::linear(0, SECTOR_SIZE, true);
261 let result = disk.read(&buffers.buffer(&mem), 1).await;
262
263 assert!(
264 matches!(result, Err(DiskError::Io(ref error)) if error.kind() == std::io::ErrorKind::UnexpectedEof),
265 "{result:?}"
266 );
267 let mut contents = [0; SECTOR_SIZE];
268 mem.read_at(0, &mut contents).unwrap();
269 assert_eq!(contents, [0xcd; SECTOR_SIZE]);
270 }
271
272 #[async_test]
273 async fn sector_range_conformance() {
274 storage_tests::sector_range::test_disk_sector_range_conformance(&file_disk()).await;
275 }
276
277 #[async_test]
285 async fn sector_does_not_wrap_when_shifted() {
286 let disk = file_disk();
287 let mem = GuestMemory::allocate(SECTOR_SIZE);
288
289 mem.write_at(0, &[0xcd; SECTOR_SIZE]).unwrap();
290 disk.write_vectored(
291 &OwnedRequestBuffers::linear(0, SECTOR_SIZE, false).buffer(&mem),
292 0,
293 false,
294 )
295 .await
296 .unwrap();
297 mem.write_at(0, &[0; SECTOR_SIZE]).unwrap();
298
299 let r = disk
302 .read_vectored(
303 &OwnedRequestBuffers::linear(0, SECTOR_SIZE, true).buffer(&mem),
304 1 << 55,
305 )
306 .await;
307
308 let mut buf = [0; SECTOR_SIZE];
309 mem.read_at(0, &mut buf).unwrap();
310 assert_ne!(buf, [0xcd; SECTOR_SIZE], "read returned sector 0");
311 assert!(matches!(r, Err(DiskError::IllegalBlock)), "{r:?}");
312 }
313}