use std::sync::Arc;
use super::{DESCRIPTOR, header::SparseImageHeader, parser::parse};
use crate::{
ByteSource, ByteSourceCapabilities, ByteSourceHandle, ByteSourceSeekCost, Error, Result,
SourceHints, images::Image,
};
#[allow(dead_code)]
pub struct SparseImage {
source: ByteSourceHandle,
header: SparseImageHeader,
media_size: u64,
band_size: u64,
guest_to_file_offsets: Arc<[Option<u64>]>,
has_sparse_bands: bool,
}
impl SparseImage {
pub fn open(source: ByteSourceHandle) -> Result<Self> {
Self::open_with_hints(source, SourceHints::new())
}
pub fn open_with_hints(source: ByteSourceHandle, _hints: SourceHints<'_>) -> Result<Self> {
let parsed = parse(source.clone())?;
Ok(Self {
source,
header: parsed.header,
media_size: parsed.media_size,
band_size: parsed.band_size,
guest_to_file_offsets: parsed.guest_to_file_offsets,
has_sparse_bands: parsed.has_sparse_bands,
})
}
pub fn header(&self) -> &SparseImageHeader {
&self.header
}
pub fn band_size(&self) -> u64 {
self.band_size
}
}
impl ByteSource for SparseImage {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
if offset >= self.media_size || buf.is_empty() {
return Ok(0);
}
let mut copied = 0usize;
while copied < buf.len() {
let absolute_offset = offset
.checked_add(copied as u64)
.ok_or_else(|| Error::invalid_range("sparseimage read offset overflow"))?;
if absolute_offset >= self.media_size {
break;
}
let band_index = usize::try_from(absolute_offset / self.band_size)
.map_err(|_| Error::invalid_range("sparseimage band index is too large"))?;
let within_band = absolute_offset % self.band_size;
let available = usize::try_from(
(self.band_size - within_band)
.min(self.media_size - absolute_offset)
.min((buf.len() - copied) as u64),
)
.map_err(|_| Error::invalid_range("sparseimage read chunk is too large"))?;
match self
.guest_to_file_offsets
.get(band_index)
.copied()
.flatten()
{
Some(file_offset) => {
self.source.read_exact_at(
file_offset + within_band,
&mut buf[copied..copied + available],
)?;
}
None => {
buf[copied..copied + available].fill(0);
}
}
copied += available;
}
Ok(copied)
}
fn size(&self) -> Result<u64> {
Ok(self.media_size)
}
fn capabilities(&self) -> ByteSourceCapabilities {
let preferred_chunk_size = usize::try_from(self.band_size).unwrap_or(1024 * 1024);
ByteSourceCapabilities::concurrent(ByteSourceSeekCost::Cheap)
.with_preferred_chunk_size(preferred_chunk_size)
}
fn telemetry_name(&self) -> &'static str {
"image.sparseimage"
}
}
impl Image for SparseImage {
fn descriptor(&self) -> crate::FormatDescriptor {
DESCRIPTOR
}
fn logical_sector_size(&self) -> Option<u32> {
Some(512)
}
fn physical_sector_size(&self) -> Option<u32> {
self.logical_sector_size()
}
fn is_sparse(&self) -> bool {
self.has_sparse_bands
}
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::*;
struct MemDataSource {
data: Vec<u8>,
}
impl ByteSource for MemDataSource {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
let offset = usize::try_from(offset)
.map_err(|_| Error::invalid_range("test read offset is too large"))?;
if offset >= self.data.len() {
return Ok(0);
}
let read = buf.len().min(self.data.len() - offset);
buf[..read].copy_from_slice(&self.data[offset..offset + read]);
Ok(read)
}
fn size(&self) -> Result<u64> {
Ok(self.data.len() as u64)
}
}
fn sample_source(relative_path: &str) -> ByteSourceHandle {
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("formats")
.join(relative_path);
Arc::new(MemDataSource {
data: std::fs::read(path).unwrap(),
})
}
fn md5_hex(data: &[u8]) -> String {
format!("{:x}", md5::compute(data))
}
fn synthetic_sparseimage() -> Vec<u8> {
let sectors_per_band = 4u32;
let sector_count = 8u32;
let band_size = usize::try_from(u64::from(sectors_per_band) * 512).unwrap();
let mut image = vec![0u8; 4096 + band_size];
image[0..4].copy_from_slice(b"sprs");
image[4..8].copy_from_slice(&3u32.to_be_bytes());
image[8..12].copy_from_slice(§ors_per_band.to_be_bytes());
image[12..16].copy_from_slice(&1u32.to_be_bytes());
image[16..20].copy_from_slice(§or_count.to_be_bytes());
image[64..68].copy_from_slice(&2u32.to_be_bytes());
image[4096..4096 + band_size].fill(0xA5);
image
}
#[test]
fn opens_sparseimage_fixture_metadata() {
let image = SparseImage::open(sample_source("sparseimage/hfsplus.sparseimage")).unwrap();
assert_eq!(image.size().unwrap(), 4_194_304);
assert_eq!(image.band_size(), 1_048_576);
assert_eq!(image.logical_sector_size(), Some(512));
}
#[test]
fn reads_full_sparseimage_fixture() {
let image = SparseImage::open(sample_source("sparseimage/hfsplus.sparseimage")).unwrap();
assert_eq!(
md5_hex(&image.read_all().unwrap()),
"22c35335e6fafcbfc2ef21f1839f228d"
);
}
#[test]
fn reads_sparse_bands_as_zeroes() {
let image = SparseImage::open(Arc::new(MemDataSource {
data: synthetic_sparseimage(),
}))
.unwrap();
let mut expected = vec![0u8; 2048];
expected.extend_from_slice(&vec![0xA5; 2048]);
assert_eq!(image.read_all().unwrap(), expected);
assert!(image.is_sparse());
}
#[test]
fn rejects_duplicate_band_numbers() {
let mut image = synthetic_sparseimage();
image[68..72].copy_from_slice(&2u32.to_be_bytes());
let result = SparseImage::open(Arc::new(MemDataSource { data: image }));
assert!(matches!(result, Err(Error::InvalidFormat(_))));
}
}
crate::images::driver::impl_image_data_source!(SparseImage);