Skip to main content

disk_file/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#![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    /// Opens the disk using the specified metadata.
81    ///
82    /// This ensures that no metadata queries are made to the file, which may be
83    /// appropriate if this is wrapped in another disk implementation that
84    /// retrieves metadata in another way.
85    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    /// The range check here is in byte units, computing `sector << sector_shift`.
271    /// A left shift discards high bits without panicking, so a large enough
272    /// sector wraps to a small byte offset and passes the check.
273    ///
274    /// This is the one defect in this area whose symptom was silent wrong data
275    /// rather than a panic, so the test asserts both that the request is
276    /// rejected and that it did not return the contents of sector 0.
277    #[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        // `1 << 55` shifted left by 9 (512-byte sectors) is `1 << 64`, which
293        // truncates to a byte offset of zero.
294        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}