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            file.read_at(&mut buffer, offset)?;
111            Ok(buffer)
112        })
113        .await
114        .map_err(DiskError::Io)?;
115        buffers.writer().write(&buffer)?;
116        Ok(())
117    }
118
119    pub async fn write(
120        &self,
121        buffers: &RequestBuffers<'_>,
122        sector: u64,
123        _fua: bool,
124    ) -> Result<(), DiskError> {
125        if ((sector << self.sector_shift) + buffers.len() as u64) > self.metadata.disk_size {
126            return Err(DiskError::IllegalBlock);
127        }
128        let mut buffer = vec![0; buffers.len()];
129        let file = self.file.clone();
130        buffers.reader().read(&mut buffer)?;
131        let offset = sector << self.sector_shift;
132        unblock(move || file.write_at(&buffer, offset))
133            .await
134            .map_err(DiskError::Io)?;
135        Ok(())
136    }
137
138    pub async fn flush(&self) -> Result<(), DiskError> {
139        let file = self.file.clone();
140        unblock(move || file.sync_all())
141            .await
142            .map_err(DiskError::Io)?;
143        Ok(())
144    }
145}
146
147impl DiskIo for FileDisk {
148    fn disk_type(&self) -> &str {
149        "file"
150    }
151
152    fn sector_count(&self) -> u64 {
153        self.metadata.disk_size >> self.sector_shift
154    }
155
156    fn sector_size(&self) -> u32 {
157        self.metadata.sector_size
158    }
159
160    fn is_read_only(&self) -> bool {
161        self.metadata.read_only
162    }
163
164    fn disk_id(&self) -> Option<[u8; 16]> {
165        None
166    }
167
168    fn physical_sector_size(&self) -> u32 {
169        self.metadata.physical_sector_size
170    }
171
172    fn is_fua_respected(&self) -> bool {
173        false
174    }
175
176    async fn read_vectored(
177        &self,
178        buffers: &RequestBuffers<'_>,
179        sector: u64,
180    ) -> Result<(), DiskError> {
181        self.read(buffers, sector).await
182    }
183
184    async fn write_vectored(
185        &self,
186        buffers: &RequestBuffers<'_>,
187        sector: u64,
188        fua: bool,
189    ) -> Result<(), DiskError> {
190        self.write(buffers, sector, fua).await
191    }
192
193    async fn sync_cache(&self) -> Result<(), DiskError> {
194        self.flush().await
195    }
196
197    async fn unmap(
198        &self,
199        _sector: u64,
200        _count: u64,
201        _block_level_only: bool,
202    ) -> Result<(), DiskError> {
203        Ok(())
204    }
205
206    fn unmap_behavior(&self) -> disk_backend::UnmapBehavior {
207        disk_backend::UnmapBehavior::Ignored
208    }
209}