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