Skip to main content

disk_blob/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! A disk backend for "blobs", i.e. raw disk data that can be accessed through
5//! a simple interface such as HTTP.
6
7#![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/// A read-only disk backed by a blob.
31#[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
58/// An error when attempting to open a blob in VHD1 format.
59#[derive(Debug, Error)]
60#[error(transparent)]
61pub struct Vhd1Error(#[from] ErrorInner);
62
63impl BlobDisk {
64    /// Returns a new blob disk where the blob is the raw disk data.
65    pub fn new(blob: impl 'static + Blob + Send + Sync) -> Self {
66        let blob = Arc::new(blob);
67        let sector_count = blob.len() / DEFAULT_SECTOR_SIZE as u64;
68        Self::new_inner(blob, sector_count, None)
69    }
70
71    /// Returns a new blob disk where the blob is a fixed VHD1.
72    pub async fn new_fixed_vhd1(blob: impl 'static + Blob + Send + Sync) -> anyhow::Result<Self> {
73        let blob = Arc::new(blob);
74        let blob_len = blob.len();
75        let footer_offset = blob_len
76            .checked_sub(VhdFooter::LEN)
77            .ok_or(ErrorInner::BlobTooSmall)?;
78
79        let mut footer = VhdFooter::new_zeroed();
80        blob.read(footer.as_mut_bytes(), footer_offset)
81            .await
82            .map_err(ErrorInner::VhdFooter)?;
83
84        if footer.cookie != VhdFooter::COOKIE_MAGIC {
85            return Err(ErrorInner::VhdFooterCookie.into());
86        }
87        if footer.checksum.get() != footer.compute_checksum() {
88            return Err(ErrorInner::VhdFooterChecksum.into());
89        }
90        if footer.file_format_version.get() != VhdFooter::FILE_FORMAT_VERSION_MAGIC {
91            return Err(ErrorInner::UnsupportedVhdVersion(footer.file_format_version.get()).into());
92        }
93        if footer.disk_type.get() != VhdFooter::DISK_TYPE_FIXED {
94            return Err(ErrorInner::NotFixedVhd.into());
95        }
96        let disk_size = footer.current_size.get();
97        if disk_size > footer_offset || disk_size % (DEFAULT_SECTOR_SIZE as u64) != 0 {
98            return Err(ErrorInner::InvalidDiskSize(disk_size).into());
99        }
100
101        Ok(Self::new_inner(
102            blob,
103            disk_size / DEFAULT_SECTOR_SIZE as u64,
104            Some(footer.unique_id.into()),
105        ))
106    }
107
108    fn new_inner(
109        blob: Arc<dyn Blob + Send + Sync>,
110        sector_count: u64,
111        disk_id: Option<[u8; 16]>,
112    ) -> Self {
113        Self {
114            blob,
115            sector_count,
116            sector_size: DEFAULT_SECTOR_SIZE,
117            sector_shift: DEFAULT_SECTOR_SIZE.trailing_zeros(),
118            disk_id,
119        }
120    }
121}
122
123impl DiskIo for BlobDisk {
124    fn disk_type(&self) -> &str {
125        "blob"
126    }
127
128    fn sector_count(&self) -> u64 {
129        self.sector_count
130    }
131
132    fn sector_size(&self) -> u32 {
133        self.sector_size
134    }
135
136    fn disk_id(&self) -> Option<[u8; 16]> {
137        self.disk_id
138    }
139
140    fn physical_sector_size(&self) -> u32 {
141        4096
142    }
143
144    fn is_fua_respected(&self) -> bool {
145        false
146    }
147
148    fn is_read_only(&self) -> bool {
149        true
150    }
151
152    async fn read_vectored(
153        &self,
154        buffers: &RequestBuffers<'_>,
155        sector: u64,
156    ) -> Result<(), DiskError> {
157        // The blob is not necessarily the same size as the disk it presents --
158        // a fixed VHD1 blob has a trailing footer -- so a read past the end of
159        // the disk can land inside the blob and succeed, returning data that is
160        // not part of the disk. Delegating the range check to the blob is
161        // therefore not sufficient.
162        if sector + (buffers.len() as u64 >> self.sector_shift) > self.sector_count {
163            return Err(DiskError::IllegalBlock);
164        }
165        let mut buf = vec![0; buffers.len()];
166        // Given the check above, and because the disk is a fixed size derived
167        // from the blob's length at open time, a read cannot run off the end of
168        // the blob. If one somehow does, the blob shrank after it was opened,
169        // which is an IO error and not an illegal block -- `sector_count` still
170        // reports the original size, so the sector is one the disk claims to
171        // have.
172        self.blob
173            .read(&mut buf, sector << self.sector_shift)
174            .await
175            .map_err(DiskError::Io)?;
176
177        buffers.writer().write(&buf)?;
178        Ok(())
179    }
180
181    async fn write_vectored(
182        &self,
183        _buffers: &RequestBuffers<'_>,
184        _sector: u64,
185        _fua: bool,
186    ) -> Result<(), DiskError> {
187        Err(DiskError::ReadOnly)
188    }
189
190    async fn sync_cache(&self) -> Result<(), DiskError> {
191        Err(DiskError::ReadOnly)
192    }
193
194    async fn unmap(
195        &self,
196        _sector: u64,
197        _count: u64,
198        _block_level_only: bool,
199    ) -> Result<(), DiskError> {
200        Err(DiskError::ReadOnly)
201    }
202
203    fn unmap_behavior(&self) -> UnmapBehavior {
204        UnmapBehavior::Ignored
205    }
206}