use std::io::{Read, Seek, Write};
use crate::Result;
pub mod dmg;
pub mod file;
pub mod memory;
pub mod qcow2;
pub mod sliced;
pub use dmg::DmgBackend;
pub use file::FileBackend;
pub use memory::MemoryBackend;
pub use qcow2::Qcow2Backend;
pub use sliced::SlicedBackend;
use std::path::Path;
pub fn open_image(path: &Path) -> crate::Result<Box<dyn BlockDevice>> {
if Qcow2Backend::probe(path)? {
Ok(Box::new(Qcow2Backend::open(path)?))
} else if dmg::probe(path)? {
Ok(Box::new(DmgBackend::open(path)?))
} else {
Ok(Box::new(FileBackend::open(path)?))
}
}
pub fn open_image_maybe_compressed(
path: &Path,
) -> crate::Result<(Box<dyn BlockDevice>, Option<tempfile::NamedTempFile>)> {
match crate::compression::detect_path(path)? {
Some(algo) => {
let tmp = crate::compression::decompress_to_tempfile(path, algo)?;
let dev = FileBackend::open(tmp.path())?;
Ok((Box::new(dev), Some(tmp)))
}
None => Ok((open_image(path)?, None)),
}
}
#[derive(Debug, Clone, Copy)]
pub struct CreateOpts {
pub cluster_size: u32,
}
impl Default for CreateOpts {
fn default() -> Self {
Self {
cluster_size: 65_536,
}
}
}
pub fn create_image(
path: &Path,
virtual_size: u64,
opts: &CreateOpts,
) -> crate::Result<Box<dyn BlockDevice>> {
if is_qcow2_extension(path) {
Ok(Box::new(Qcow2Backend::create(
path,
virtual_size,
opts.cluster_size,
)?))
} else {
Ok(Box::new(FileBackend::create(path, virtual_size)?))
}
}
fn is_qcow2_extension(path: &Path) -> bool {
let Some(ext) = path.extension().and_then(|s| s.to_str()) else {
return false;
};
matches!(ext.to_ascii_lowercase().as_str(), "qcow2" | "qcow" | "q2")
}
pub trait BlockDevice: Read + Write + Seek + Send {
fn block_size(&self) -> u32;
fn total_size(&self) -> u64;
fn zero_range(&mut self, offset: u64, len: u64) -> Result<()> {
let size = self.total_size();
if offset.checked_add(len).is_none_or(|end| end > size) {
return Err(crate::Error::OutOfBounds { offset, len, size });
}
if len == 0 {
return Ok(());
}
self.seek(std::io::SeekFrom::Start(offset))?;
let zero = [0u8; 4096];
let mut remaining = len;
while remaining > 0 {
let n = remaining.min(zero.len() as u64) as usize;
self.write_all(&zero[..n])?;
remaining -= n as u64;
}
Ok(())
}
fn sync(&mut self) -> Result<()>;
fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> Result<()> {
let size = self.total_size();
let end = offset
.checked_add(buf.len() as u64)
.ok_or(crate::Error::OutOfBounds {
offset,
len: buf.len() as u64,
size,
})?;
if end > size {
return Err(crate::Error::OutOfBounds {
offset,
len: buf.len() as u64,
size,
});
}
self.seek(std::io::SeekFrom::Start(offset))?;
self.read_exact(buf)?;
Ok(())
}
fn write_at(&mut self, offset: u64, buf: &[u8]) -> Result<()> {
let size = self.total_size();
let end = offset
.checked_add(buf.len() as u64)
.ok_or(crate::Error::OutOfBounds {
offset,
len: buf.len() as u64,
size,
})?;
if end > size {
return Err(crate::Error::OutOfBounds {
offset,
len: buf.len() as u64,
size,
});
}
self.seek(std::io::SeekFrom::Start(offset))?;
self.write_all(buf)?;
Ok(())
}
}