use crate::io::{Read, Seek, SeekFrom, Write};
use alloc::boxed::Box;
use crate::Result;
#[cfg(feature = "std")]
pub mod crash_inject;
#[cfg(feature = "diskcopy")]
pub mod diskcopy;
#[cfg(feature = "dmg")]
pub mod dmg;
#[cfg(feature = "std")]
pub mod file;
#[cfg(feature = "luks")]
pub mod luks;
pub mod memory;
#[cfg(feature = "qcow2")]
pub mod qcow2;
pub mod sector;
pub mod sliced;
#[cfg(feature = "std")]
pub use crash_inject::{CrashInject, FailAfter};
#[cfg(feature = "diskcopy")]
pub use diskcopy::DiskCopy42Backend;
#[cfg(feature = "dmg")]
pub use dmg::DmgBackend;
#[cfg(feature = "std")]
pub use file::FileBackend;
#[cfg(feature = "luks")]
pub use luks::LuksBackend;
pub use memory::MemoryBackend;
#[cfg(feature = "qcow2")]
pub use qcow2::Qcow2Backend;
pub use sector::{SectorDevice, SectorIo};
pub use sliced::SlicedBackend;
pub use crate::device::{FlashDriver, SectorDriver};
#[cfg(feature = "std")]
mod host {
use super::*;
use std::path::{Path, PathBuf};
pub fn open_image(path: &Path) -> crate::Result<Box<dyn BlockDevice>> {
open_image_with_password(path, None)
}
pub fn open_image_with_password(
path: &Path,
password: Option<&str>,
) -> crate::Result<Box<dyn BlockDevice>> {
#[cfg(feature = "qcow2")]
if Qcow2Backend::probe(path)? {
return open_qcow2(path, password, false);
}
#[cfg(feature = "dmg")]
if dmg::probe(path)? {
return Ok(Box::new(DmgBackend::open(path)?));
}
#[cfg(feature = "diskcopy")]
if diskcopy::probe(path)? {
return Ok(Box::new(DiskCopy42Backend::new(Box::new(
FileBackend::open(path)?,
))?));
}
let file = FileBackend::open(path)?;
open_maybe_luks(file, path, password, false)
}
#[cfg(feature = "qcow2")]
fn open_qcow2(
path: &Path,
password: Option<&str>,
read_only: bool,
) -> crate::Result<Box<dyn BlockDevice>> {
#[cfg(feature = "qcow2-crypto")]
if let Some(password) = password {
return Ok(if read_only {
Box::new(Qcow2Backend::open_encrypted_read_only(path, password)?)
} else {
Box::new(Qcow2Backend::open_encrypted(path, password)?)
});
}
let _ = password;
Ok(if read_only {
Box::new(Qcow2Backend::open_read_only(path)?)
} else {
Box::new(Qcow2Backend::open(path)?)
})
}
#[cfg(feature = "luks")]
fn open_maybe_luks(
mut file: FileBackend,
path: &Path,
password: Option<&str>,
read_only: bool,
) -> crate::Result<Box<dyn BlockDevice>> {
let Some(version) = luks::probe(&mut file) else {
return Ok(Box::new(file));
};
let Some(password) = password else {
return Err(crate::Error::InvalidArgument(format!(
"{}: this is a {version} volume — open it with a passphrase",
path.display()
)));
};
Ok(if read_only {
Box::new(LuksBackend::open_read_only(file, password)?)
} else {
Box::new(LuksBackend::open(file, password)?)
})
}
#[cfg(not(feature = "luks"))]
fn open_maybe_luks(
file: FileBackend,
_path: &Path,
_password: Option<&str>,
_read_only: bool,
) -> crate::Result<Box<dyn BlockDevice>> {
Ok(Box::new(file))
}
pub fn open_image_maybe_compressed(path: &Path) -> crate::Result<Box<dyn BlockDevice>> {
open_image_maybe_compressed_with_password(path, None)
}
pub fn open_image_maybe_compressed_with_password(
path: &Path,
password: Option<&str>,
) -> crate::Result<Box<dyn BlockDevice>> {
match crate::compression::detect_path(path)? {
Some(algo) => {
let bytes = crate::compression::decompress_to_memory(path, algo)?;
open_memory_maybe_luks(MemoryBackend::from_bytes(bytes), path, password)
}
None => open_image_with_password(path, password),
}
}
#[cfg(feature = "luks")]
fn open_memory_maybe_luks(
mut mem: MemoryBackend,
path: &Path,
password: Option<&str>,
) -> crate::Result<Box<dyn BlockDevice>> {
let Some(version) = luks::probe(&mut mem) else {
return Ok(Box::new(mem));
};
let Some(password) = password else {
return Err(crate::Error::InvalidArgument(format!(
"{}: this is a {version} volume — open it with a passphrase",
path.display()
)));
};
Ok(Box::new(LuksBackend::open(mem, password)?))
}
#[cfg(not(feature = "luks"))]
fn open_memory_maybe_luks(
mem: MemoryBackend,
_path: &Path,
_password: Option<&str>,
) -> crate::Result<Box<dyn BlockDevice>> {
Ok(Box::new(mem))
}
pub fn open_image_read_only(path: &Path) -> crate::Result<Box<dyn BlockDevice>> {
open_image_read_only_with_password(path, None)
}
pub fn open_image_read_only_with_password(
path: &Path,
password: Option<&str>,
) -> crate::Result<Box<dyn BlockDevice>> {
#[cfg(feature = "qcow2")]
if Qcow2Backend::probe(path)? {
return open_qcow2(path, password, true);
}
#[cfg(feature = "dmg")]
if dmg::probe(path)? {
return Ok(Box::new(DmgBackend::open(path)?));
}
#[cfg(feature = "diskcopy")]
if diskcopy::probe(path)? {
return Ok(Box::new(DiskCopy42Backend::new(Box::new(
FileBackend::open_read_only(path)?,
))?));
}
let file = FileBackend::open_read_only(path)?;
open_maybe_luks(file, path, password, true)
}
pub fn open_image_maybe_compressed_read_only(
path: &Path,
) -> crate::Result<Box<dyn BlockDevice>> {
open_image_maybe_compressed_read_only_with_password(path, None)
}
pub fn open_image_maybe_compressed_read_only_with_password(
path: &Path,
password: Option<&str>,
) -> crate::Result<Box<dyn BlockDevice>> {
match crate::compression::detect_path(path)? {
Some(algo) => {
let bytes = crate::compression::decompress_to_memory(path, algo)?;
open_memory_maybe_luks(MemoryBackend::from_bytes(bytes), path, password)
}
None => open_image_read_only_with_password(path, password),
}
}
#[derive(Debug, Clone)]
pub struct CreateOpts {
pub cluster_size: u32,
pub encrypt: Option<EncryptOpts>,
pub backing: Option<(PathBuf, Option<String>)>,
}
#[derive(Debug, Clone)]
pub struct EncryptOpts {
pub password: String,
#[cfg(feature = "luks")]
pub luks: luks::FormatOpts,
}
impl Default for CreateOpts {
fn default() -> Self {
Self {
cluster_size: 65_536,
encrypt: None,
backing: None,
}
}
}
impl CreateOpts {
pub fn with_cluster_size(cluster_size: u32) -> Self {
Self {
cluster_size,
..Self::default()
}
}
}
pub fn create_image(
path: &Path,
virtual_size: u64,
opts: &CreateOpts,
) -> crate::Result<Box<dyn BlockDevice>> {
#[cfg(not(feature = "qcow2"))]
if is_qcow2_path(path) {
return Err(crate::Error::Unsupported(
"qcow2 images need the `qcow2` feature".into(),
));
}
#[cfg(feature = "qcow2")]
if is_qcow2_path(path) {
let cluster_size = if opts.cluster_size == 0 {
65_536
} else {
opts.cluster_size
};
let backing = opts
.backing
.as_ref()
.map(|(p, f)| (p.as_path(), f.as_deref()));
#[cfg(feature = "qcow2-crypto")]
if let Some(enc) = &opts.encrypt {
if backing.is_some() {
return Err(crate::Error::Unsupported(
"qcow2: an encrypted image cannot also have a backing file — \
the base's clusters are not encrypted under this image's key"
.into(),
));
}
return Ok(Box::new(Qcow2Backend::create_encrypted(
path,
virtual_size,
cluster_size,
&enc.password,
&enc.luks,
)?));
}
if opts.encrypt.is_some() {
return Err(crate::Error::Unsupported(
"qcow2: encryption needs the `qcow2-crypto` feature".into(),
));
}
return Ok(Box::new(Qcow2Backend::create_with_backing(
path,
virtual_size,
cluster_size,
backing,
)?));
}
if opts.backing.is_some() {
return Err(crate::Error::Unsupported(
"a raw image has no header to record a backing file in — \
use a .qcow2 destination"
.into(),
));
}
#[cfg(feature = "luks")]
if let Some(enc) = &opts.encrypt {
let payload_offset = enc.luks.payload_offset();
let total = payload_offset.checked_add(virtual_size).ok_or_else(|| {
crate::Error::InvalidArgument("luks: container size overflows u64".into())
})?;
let file = FileBackend::create(path, total)?;
return Ok(Box::new(luks::format(file, &enc.password, &enc.luks)?));
}
if opts.encrypt.is_some() {
return Err(crate::Error::Unsupported(
"encryption needs the `luks` feature".into(),
));
}
Ok(Box::new(FileBackend::create(path, virtual_size)?))
}
pub fn is_qcow2_path(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")
}
}
#[cfg(feature = "std")]
pub use host::{
CreateOpts, EncryptOpts, create_image, is_qcow2_path, open_image, open_image_maybe_compressed,
open_image_maybe_compressed_read_only, open_image_maybe_compressed_read_only_with_password,
open_image_maybe_compressed_with_password, open_image_read_only,
open_image_read_only_with_password, open_image_with_password,
};
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(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(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(SeekFrom::Start(offset))?;
self.write_all(buf)?;
Ok(())
}
}
impl<B: BlockDevice + ?Sized> BlockDevice for Box<B> {
fn block_size(&self) -> u32 {
(**self).block_size()
}
fn total_size(&self) -> u64 {
(**self).total_size()
}
fn zero_range(&mut self, offset: u64, len: u64) -> Result<()> {
(**self).zero_range(offset, len)
}
fn sync(&mut self) -> Result<()> {
(**self).sync()
}
fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> Result<()> {
(**self).read_at(offset, buf)
}
fn write_at(&mut self, offset: u64, buf: &[u8]) -> Result<()> {
(**self).write_at(offset, buf)
}
}