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
12use blob::Blob;
13use disk_backend::DiskError;
14use disk_backend::DiskIo;
15use disk_backend::UnmapBehavior;
16use guestmem::MemoryWrite;
17use inspect::Inspect;
18use scsi_buffers::RequestBuffers;
19use std::sync::Arc;
20use thiserror::Error;
21use vhd1_defs::VhdFooter;
22use zerocopy::FromZeros;
23use zerocopy::IntoBytes;
24
25const DEFAULT_SECTOR_SIZE: u32 = 512;
26
27/// A read-only disk backed by a blob.
28#[derive(Inspect)]
29pub struct BlobDisk {
30    blob: Arc<dyn Blob + Send + Sync>,
31    sector_count: u64,
32    sector_size: u32,
33    sector_shift: u32,
34    disk_id: Option<[u8; 16]>,
35}
36
37#[derive(Debug, Error)]
38enum ErrorInner {
39    #[error("blob is too small")]
40    BlobTooSmall,
41    #[error("failed to read the vhd footer")]
42    VhdFooter(#[source] std::io::Error),
43    #[error("invalid vhd1 footer cookie")]
44    VhdFooterCookie,
45    #[error("invalid vhd1 footer checksum")]
46    VhdFooterChecksum,
47    #[error("unsupported vhd version: {0:#x}")]
48    UnsupportedVhdVersion(u32),
49    #[error("not a fixed vhd")]
50    NotFixedVhd,
51    #[error("invalid disk size: {0}")]
52    InvalidDiskSize(u64),
53}
54
55/// An error when attempting to open a blob in VHD1 format.
56#[derive(Debug, Error)]
57#[error(transparent)]
58pub struct Vhd1Error(#[from] ErrorInner);
59
60impl BlobDisk {
61    /// Returns a new blob disk where the blob is the raw disk data.
62    pub fn new(blob: impl 'static + Blob + Send + Sync) -> Self {
63        let blob = Arc::new(blob);
64        let sector_count = blob.len() / DEFAULT_SECTOR_SIZE as u64;
65        Self::new_inner(blob, sector_count, None)
66    }
67
68    /// Returns a new blob disk where the blob is a fixed VHD1.
69    pub async fn new_fixed_vhd1(blob: impl 'static + Blob + Send + Sync) -> anyhow::Result<Self> {
70        let blob = Arc::new(blob);
71        let blob_len = blob.len();
72        let footer_offset = blob_len
73            .checked_sub(VhdFooter::LEN)
74            .ok_or(ErrorInner::BlobTooSmall)?;
75
76        let mut footer = VhdFooter::new_zeroed();
77        blob.read(footer.as_mut_bytes(), footer_offset)
78            .await
79            .map_err(ErrorInner::VhdFooter)?;
80
81        if footer.cookie != VhdFooter::COOKIE_MAGIC {
82            return Err(ErrorInner::VhdFooterCookie.into());
83        }
84        if footer.checksum.get() != footer.compute_checksum() {
85            return Err(ErrorInner::VhdFooterChecksum.into());
86        }
87        if footer.file_format_version.get() != VhdFooter::FILE_FORMAT_VERSION_MAGIC {
88            return Err(ErrorInner::UnsupportedVhdVersion(footer.file_format_version.get()).into());
89        }
90        if footer.disk_type.get() != VhdFooter::DISK_TYPE_FIXED {
91            return Err(ErrorInner::NotFixedVhd.into());
92        }
93        let disk_size = footer.current_size.get();
94        if disk_size > footer_offset || disk_size % (DEFAULT_SECTOR_SIZE as u64) != 0 {
95            return Err(ErrorInner::InvalidDiskSize(disk_size).into());
96        }
97
98        Ok(Self::new_inner(
99            blob,
100            disk_size / DEFAULT_SECTOR_SIZE as u64,
101            Some(footer.unique_id.into()),
102        ))
103    }
104
105    fn new_inner(
106        blob: Arc<dyn Blob + Send + Sync>,
107        sector_count: u64,
108        disk_id: Option<[u8; 16]>,
109    ) -> Self {
110        Self {
111            blob,
112            sector_count,
113            sector_size: DEFAULT_SECTOR_SIZE,
114            sector_shift: DEFAULT_SECTOR_SIZE.trailing_zeros(),
115            disk_id,
116        }
117    }
118}
119
120impl DiskIo for BlobDisk {
121    fn disk_type(&self) -> &str {
122        "blob"
123    }
124
125    fn sector_count(&self) -> u64 {
126        self.sector_count
127    }
128
129    fn sector_size(&self) -> u32 {
130        self.sector_size
131    }
132
133    fn disk_id(&self) -> Option<[u8; 16]> {
134        self.disk_id
135    }
136
137    fn physical_sector_size(&self) -> u32 {
138        4096
139    }
140
141    fn is_fua_respected(&self) -> bool {
142        false
143    }
144
145    fn is_read_only(&self) -> bool {
146        true
147    }
148
149    async fn read_vectored(
150        &self,
151        buffers: &RequestBuffers<'_>,
152        sector: u64,
153    ) -> Result<(), DiskError> {
154        let mut buf = vec![0; buffers.len()];
155        self.blob
156            .read(&mut buf, sector << self.sector_shift)
157            .await
158            .map_err(DiskError::Io)?;
159
160        buffers.writer().write(&buf)?;
161        Ok(())
162    }
163
164    async fn write_vectored(
165        &self,
166        _buffers: &RequestBuffers<'_>,
167        _sector: u64,
168        _fua: bool,
169    ) -> Result<(), DiskError> {
170        Err(DiskError::ReadOnly)
171    }
172
173    async fn sync_cache(&self) -> Result<(), DiskError> {
174        Err(DiskError::ReadOnly)
175    }
176
177    async fn unmap(
178        &self,
179        _sector: u64,
180        _count: u64,
181        _block_level_only: bool,
182    ) -> Result<(), DiskError> {
183        Err(DiskError::ReadOnly)
184    }
185
186    fn unmap_behavior(&self) -> UnmapBehavior {
187        UnmapBehavior::Ignored
188    }
189}