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
69impl FileDisk {
70 pub fn open(file: fs::File, read_only: bool) -> Result<Self, std::io::Error> {
71 let metadata = Metadata {
72 disk_size: file.metadata()?.len(),
73 sector_size: 512,
74 physical_sector_size: 4096,
75 read_only,
76 };
77 Ok(Self::with_metadata(file, metadata))
78 }
79
80 pub fn with_metadata(file: fs::File, metadata: Metadata) -> Self {
86 assert!(metadata.sector_size.is_power_of_two());
87 assert!(metadata.sector_size >= 512);
88 let sector_shift = metadata.sector_size.trailing_zeros();
89 FileDisk {
90 file: Arc::new(file),
91 metadata,
92 sector_shift,
93 }
94 }
95
96 pub fn into_inner(self) -> fs::File {
97 Arc::try_unwrap(self.file).expect("no outstanding IOs")
98 }
99}
100
101impl FileDisk {
102 pub async fn read(&self, buffers: &RequestBuffers<'_>, sector: u64) -> Result<(), DiskError> {
103 if ((sector << self.sector_shift) + buffers.len() as u64) > self.metadata.disk_size {
104 return Err(DiskError::IllegalBlock);
105 }
106 let mut buffer = vec![0; buffers.len()];
107 let file = self.file.clone();
108 let offset = sector << self.sector_shift;
109 let buffer = unblock(move || -> Result<_, std::io::Error> {
110 let count = file.read_at(&mut buffer, offset)?;
111 if count != buffer.len() {
112 Err(std::io::Error::new(
113 std::io::ErrorKind::UnexpectedEof,
114 "short disk read",
115 ))
116 } else {
117 Ok(buffer)
118 }
119 })
120 .await
121 .map_err(DiskError::Io)?;
122 buffers.writer().write(&buffer)?;
123 Ok(())
124 }
125
126 pub async fn write(
127 &self,
128 buffers: &RequestBuffers<'_>,
129 sector: u64,
130 _fua: bool,
131 ) -> Result<(), DiskError> {
132 if ((sector << self.sector_shift) + buffers.len() as u64) > self.metadata.disk_size {
133 return Err(DiskError::IllegalBlock);
134 }
135 let mut buffer = vec![0; buffers.len()];
136 let file = self.file.clone();
137 buffers.reader().read(&mut buffer)?;
138 let offset = sector << self.sector_shift;
139 unblock(move || -> Result<(), std::io::Error> {
140 let count = file.write_at(&buffer, offset)?;
141 if count != buffer.len() {
142 Err(std::io::Error::other("short disk write"))
143 } else {
144 Ok(())
145 }
146 })
147 .await
148 .map_err(DiskError::Io)?;
149 Ok(())
150 }
151
152 pub async fn flush(&self) -> Result<(), DiskError> {
153 let file = self.file.clone();
154 unblock(move || file.sync_all())
155 .await
156 .map_err(DiskError::Io)?;
157 Ok(())
158 }
159}
160
161impl DiskIo for FileDisk {
162 fn disk_type(&self) -> &str {
163 "file"
164 }
165
166 fn sector_count(&self) -> u64 {
167 self.metadata.disk_size >> self.sector_shift
168 }
169
170 fn sector_size(&self) -> u32 {
171 self.metadata.sector_size
172 }
173
174 fn is_read_only(&self) -> bool {
175 self.metadata.read_only
176 }
177
178 fn disk_id(&self) -> Option<[u8; 16]> {
179 None
180 }
181
182 fn physical_sector_size(&self) -> u32 {
183 self.metadata.physical_sector_size
184 }
185
186 fn is_fua_respected(&self) -> bool {
187 false
188 }
189
190 async fn read_vectored(
191 &self,
192 buffers: &RequestBuffers<'_>,
193 sector: u64,
194 ) -> Result<(), DiskError> {
195 self.read(buffers, sector).await
196 }
197
198 async fn write_vectored(
199 &self,
200 buffers: &RequestBuffers<'_>,
201 sector: u64,
202 fua: bool,
203 ) -> Result<(), DiskError> {
204 self.write(buffers, sector, fua).await
205 }
206
207 async fn sync_cache(&self) -> Result<(), DiskError> {
208 self.flush().await
209 }
210
211 async fn unmap(
212 &self,
213 _sector: u64,
214 _count: u64,
215 _block_level_only: bool,
216 ) -> Result<(), DiskError> {
217 Ok(())
218 }
219
220 fn unmap_behavior(&self) -> disk_backend::UnmapBehavior {
221 disk_backend::UnmapBehavior::Ignored
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::FileDisk;
228 use disk_backend::Disk;
229 use disk_backend::DiskError;
230 use guestmem::GuestMemory;
231 use pal_async::async_test;
232 use scsi_buffers::OwnedRequestBuffers;
233
234 const SECTOR_SIZE: usize = 512;
235 const DISK_SIZE: u64 = 1024 * 1024;
236
237 fn file_disk() -> Disk {
238 let file = tempfile::tempfile().unwrap();
239 file.set_len(DISK_SIZE).unwrap();
240 Disk::new(FileDisk::open(file, false).unwrap()).unwrap()
241 }
242
243 #[async_test]
244 async fn short_read_returns_error() {
245 let file = tempfile::tempfile().unwrap();
246 file.set_len(DISK_SIZE).unwrap();
247 let disk = FileDisk::open(file.try_clone().unwrap(), false).unwrap();
248 file.set_len((SECTOR_SIZE + SECTOR_SIZE / 2) as u64)
249 .unwrap();
250
251 let mem = GuestMemory::allocate(SECTOR_SIZE);
252 mem.write_at(0, &[0xcd; SECTOR_SIZE]).unwrap();
253 let buffers = OwnedRequestBuffers::linear(0, SECTOR_SIZE, true);
254 let result = disk.read(&buffers.buffer(&mem), 1).await;
255
256 assert!(
257 matches!(result, Err(DiskError::Io(ref error)) if error.kind() == std::io::ErrorKind::UnexpectedEof),
258 "{result:?}"
259 );
260 let mut contents = [0; SECTOR_SIZE];
261 mem.read_at(0, &mut contents).unwrap();
262 assert_eq!(contents, [0xcd; SECTOR_SIZE]);
263 }
264
265 #[async_test]
266 async fn sector_range_conformance() {
267 storage_tests::sector_range::test_disk_sector_range_conformance(&file_disk()).await;
268 }
269
270 #[async_test]
278 async fn sector_does_not_wrap_when_shifted() {
279 let disk = file_disk();
280 let mem = GuestMemory::allocate(SECTOR_SIZE);
281
282 mem.write_at(0, &[0xcd; SECTOR_SIZE]).unwrap();
283 disk.write_vectored(
284 &OwnedRequestBuffers::linear(0, SECTOR_SIZE, false).buffer(&mem),
285 0,
286 false,
287 )
288 .await
289 .unwrap();
290 mem.write_at(0, &[0; SECTOR_SIZE]).unwrap();
291
292 let r = disk
295 .read_vectored(
296 &OwnedRequestBuffers::linear(0, SECTOR_SIZE, true).buffer(&mem),
297 1 << 55,
298 )
299 .await;
300
301 let mut buf = [0; SECTOR_SIZE];
302 mem.read_at(0, &mut buf).unwrap();
303 assert_ne!(buf, [0xcd; SECTOR_SIZE], "read returned sector 0");
304 assert!(matches!(r, Err(DiskError::IllegalBlock)), "{r:?}");
305 }
306}