use super::types::*;
use crate::io_buffers::IoBuffer;
use crate::macros::numerical_enum;
use crate::misc_helpers::invalid_data;
use crate::{Storage, StorageExt};
use bincode::config::{BigEndian, Configuration as BincodeConfiguration, Fixint};
use bincode::{Decode, Encode};
use std::collections::HashMap;
use std::mem::size_of;
use std::num::TryFromIntError;
use std::sync::atomic::{AtomicBool, AtomicU16, AtomicU32, AtomicU64, AtomicU8, Ordering};
use std::{cmp, io};
use tokio::sync::{Mutex, MutexGuard};
use tracing::error;
pub(super) const MAGIC: u32 = 0x51_46_49_fb;
const MAX_FILE_LENGTH: u64 = 0x0100_0000_0000_0000u64;
pub(super) const MAX_OFFSET: HostOffset = HostOffset(MAX_FILE_LENGTH - 512);
pub(super) const MIN_CLUSTER_SIZE: usize = 512;
pub(super) const MAX_CLUSTER_SIZE: usize = 2 * 1024 * 1024;
pub(super) const MIN_REFCOUNT_WIDTH: usize = 1;
pub(super) const MAX_REFCOUNT_WIDTH: usize = 64;
const BINCODE_CFG: BincodeConfiguration<BigEndian, Fixint> = bincode::config::standard()
.with_fixed_int_encoding()
.with_big_endian();
#[derive(Decode, Encode)]
struct V2Header {
magic: u32,
version: u32,
backing_file_offset: u64,
backing_file_size: u32,
cluster_bits: u32,
size: AtomicU64,
crypt_method: u32,
l1_size: AtomicU32,
l1_table_offset: AtomicU64,
refcount_table_offset: AtomicU64,
refcount_table_clusters: AtomicU32,
nb_snapshots: u32,
snapshots_offset: u64,
}
impl V2Header {
const RAW_SIZE: usize = 72;
}
#[derive(Decode, Encode)]
struct V3HeaderBase {
incompatible_features: u64,
compatible_features: u64,
autoclear_features: u64,
refcount_order: u32,
header_length: u32,
}
impl V3HeaderBase {
const RAW_SIZE: usize = 104 - V2Header::RAW_SIZE;
}
impl Default for V3HeaderBase {
fn default() -> Self {
V3HeaderBase {
incompatible_features: 0,
compatible_features: 0,
autoclear_features: 0,
refcount_order: 4,
header_length: (V2Header::RAW_SIZE + V3HeaderBase::RAW_SIZE) as u32,
}
}
}
numerical_enum! {
pub(super) enum IncompatibleFeatures as u64 {
Dirty = 1 << 0,
Corrupt = 1 << 1,
ExternalDataFile = 1 << 2,
CompressionType = 1 << 3,
ExtendedL2Entries = 1 << 4,
}
}
impl From<IncompatibleFeatures> for (FeatureType, u8) {
fn from(feat: IncompatibleFeatures) -> (FeatureType, u8) {
assert!((feat as u64).is_power_of_two());
(
FeatureType::Incompatible,
(feat as u64).trailing_zeros() as u8,
)
}
}
numerical_enum! {
pub(super) enum CompatibleFeatures as u64 {
LazyRefcounts = 1 << 0,
}
}
impl From<CompatibleFeatures> for (FeatureType, u8) {
fn from(feat: CompatibleFeatures) -> (FeatureType, u8) {
assert!((feat as u64).is_power_of_two());
(
FeatureType::Compatible,
(feat as u64).trailing_zeros() as u8,
)
}
}
numerical_enum! {
pub(super) enum AutoclearFeatures as u64 {
Bitmaps = 1 << 0,
RawExternalData = 1 << 1,
}
}
impl From<AutoclearFeatures> for (FeatureType, u8) {
fn from(feat: AutoclearFeatures) -> (FeatureType, u8) {
assert!((feat as u64).is_power_of_two());
(FeatureType::Autoclear, (feat as u64).trailing_zeros() as u8)
}
}
numerical_enum! {
pub(super) enum HeaderExtensionType as u32 {
End = 0,
BackingFileFormat = 0xe2792aca,
FeatureNameTable = 0x6803f857,
ExternalDataFileName = 0x44415441,
}
}
#[derive(Default, Decode, Encode)]
struct HeaderExtensionHeader {
extension_type: u32,
length: u32,
}
impl HeaderExtensionHeader {
const RAW_SIZE: usize = 8;
}
numerical_enum! {
#[derive(Hash)]
pub(super) enum FeatureType as u8 {
Incompatible = 0,
Compatible = 1,
Autoclear = 2,
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub(super) enum HeaderExtension {
BackingFileFormat(String),
FeatureNameTable(HashMap<(FeatureType, u8), String>),
ExternalDataFileName(String),
Unknown {
extension_type: u32,
data: Vec<u8>,
},
}
pub(super) struct Header {
v2: V2Header,
v3: V3HeaderBase,
unknown_header_fields: Vec<u8>,
backing_filename: Option<String>,
extensions: Vec<HeaderExtension>,
external_data_file: bool,
}
impl Header {
pub async fn load<S: Storage>(image: &S, writable: bool) -> io::Result<Self> {
let mut header_buf = vec![0u8; V2Header::RAW_SIZE];
image.read(header_buf.as_mut_slice(), 0).await?;
let header: V2Header = decode_binary(&header_buf)?;
if header.magic != MAGIC {
return Err(invalid_data("Not a qcow2 file"));
}
let v3header_base = if header.version == 2 {
V3HeaderBase::default()
} else if header.version == 3 {
let mut header_buf = vec![0u8; V3HeaderBase::RAW_SIZE];
image
.read(header_buf.as_mut_slice(), V2Header::RAW_SIZE as u64)
.await?;
decode_binary(&header_buf)?
} else {
return Err(invalid_data(format!(
"qcow2 v{} is not supported",
header.version
)));
};
let cluster_size = 1usize.checked_shl(header.cluster_bits).ok_or_else(|| {
invalid_data(format!("Invalid cluster size: 2^{}", header.cluster_bits))
})?;
if !(MIN_CLUSTER_SIZE..=MAX_CLUSTER_SIZE).contains(&cluster_size) {
return Err(invalid_data(format!(
"Invalid cluster size: {cluster_size}; must be between {MIN_CLUSTER_SIZE} and {MAX_CLUSTER_SIZE}",
)));
}
let min_header_size = V2Header::RAW_SIZE + V3HeaderBase::RAW_SIZE;
if (v3header_base.header_length as usize) < min_header_size {
return Err(invalid_data(format!(
"qcow2 header too short: {} < {min_header_size}",
v3header_base.header_length,
)));
} else if (v3header_base.header_length as usize) > cluster_size {
return Err(invalid_data(format!(
"qcow2 header too big: {} > {cluster_size}",
v3header_base.header_length,
)));
}
let unknown_header_fields = if header.version == 2 {
Vec::new()
} else {
let mut unknown_header_fields =
vec![0u8; v3header_base.header_length as usize - min_header_size];
image
.read(&mut unknown_header_fields, min_header_size as u64)
.await?;
unknown_header_fields
};
let l1_offset = HostOffset(header.l1_table_offset.load(Ordering::Relaxed));
l1_offset
.checked_cluster(header.cluster_bits)
.ok_or_else(|| invalid_data(format!("Unaligned L1 table: {l1_offset}")))?;
let rt_offset = HostOffset(header.refcount_table_offset.load(Ordering::Relaxed));
rt_offset
.checked_cluster(header.cluster_bits)
.ok_or_else(|| invalid_data(format!("Unaligned refcount table: {rt_offset}")))?;
let rc_width = 1usize
.checked_shl(v3header_base.refcount_order)
.ok_or_else(|| {
invalid_data(format!(
"Invalid refcount width: 2^{}",
v3header_base.refcount_order
))
})?;
if !(MIN_REFCOUNT_WIDTH..=MAX_REFCOUNT_WIDTH).contains(&rc_width) {
return Err(invalid_data(format!(
"Invalid refcount width: {rc_width}; must be between {MIN_REFCOUNT_WIDTH} and {MAX_REFCOUNT_WIDTH}",
)));
}
let backing_filename = if header.backing_file_offset != 0 {
let (offset, length) = (header.backing_file_offset, header.backing_file_size);
if length > 1023 {
return Err(invalid_data(format!(
"Backing file name is too long ({length}, must not exceed 1023)"
)));
}
let end = offset.checked_add(length as u64).ok_or(invalid_data(
"Backing file name offset is invalid (too high)",
))?;
if end >= cluster_size as u64 {
return Err(invalid_data(
"Backing file name offset is invalid (beyond first cluster)",
));
}
let mut backing_buf = vec![0; length as usize];
image.read(&mut backing_buf, offset).await?;
Some(
String::from_utf8(backing_buf)
.map_err(|err| invalid_data(format!("Backing file name is invalid: {err}")))?,
)
} else {
None
};
let extensions = if header.version == 2 {
Vec::new()
} else {
let mut ext_offset: u64 = v3header_base.header_length as u64;
let mut extensions = Vec::<HeaderExtension>::new();
loop {
if ext_offset + HeaderExtensionHeader::RAW_SIZE as u64 > cluster_size as u64 {
return Err(invalid_data("Header extensions exceed the first cluster"));
}
let mut ext_hdr_buf = vec![0; HeaderExtensionHeader::RAW_SIZE];
image.read(&mut ext_hdr_buf, ext_offset).await?;
ext_offset += HeaderExtensionHeader::RAW_SIZE as u64;
let ext_hdr: HeaderExtensionHeader = decode_binary(&ext_hdr_buf)?;
let ext_end = ext_offset
.checked_add(ext_hdr.length as u64)
.ok_or_else(|| invalid_data("Header size overflow"))?;
if ext_end > cluster_size as u64 {
return Err(invalid_data("Header extensions exceed the first cluster"));
}
let mut ext_data = vec![0; ext_hdr.length as usize];
image.read(&mut ext_data, ext_offset).await?;
ext_offset += (ext_hdr.length as u64).next_multiple_of(8);
let Some(extension) =
HeaderExtension::deserialize(ext_hdr.extension_type, ext_data)?
else {
break;
};
extensions.push(extension);
}
extensions
};
let backing_fmt = extensions
.iter()
.find(|ext| matches!(ext, HeaderExtension::BackingFileFormat(_)));
if let Some(backing_fmt) = backing_fmt {
let conflicting = extensions.iter().find(|ext| {
matches!(ext, HeaderExtension::BackingFileFormat(_)) && ext != &backing_fmt
});
if let Some(conflicting) = conflicting {
return Err(io::Error::other(format!(
"Found conflicting backing file formats: {backing_fmt:?} != {conflicting:?}",
)));
}
}
let ext_data_file = extensions
.iter()
.find(|ext| matches!(ext, HeaderExtension::ExternalDataFileName(_)));
if let Some(ext_data_file) = ext_data_file {
let conflicting = extensions.iter().find(|ext| {
matches!(ext, HeaderExtension::ExternalDataFileName(_)) && ext != &ext_data_file
});
if let Some(conflicting) = conflicting {
return Err(io::Error::other(format!(
"Found conflicting external data file names: {ext_data_file:?} != {conflicting:?}",
)));
}
}
let mut incompatible_features = v3header_base.incompatible_features;
let autoclear_features = v3header_base.autoclear_features;
let external_data_file =
incompatible_features & IncompatibleFeatures::ExternalDataFile as u64 != 0;
incompatible_features &= !(IncompatibleFeatures::ExternalDataFile as u64);
let mut header = Header {
v2: header,
v3: v3header_base,
unknown_header_fields,
backing_filename,
extensions,
external_data_file,
};
if autoclear_features != 0 && writable {
header.v3.autoclear_features = 0;
header.write(image).await?;
}
if incompatible_features != 0 {
let feats = (0..64)
.filter(|bit| header.v3.incompatible_features & (1u64 << bit) != 0)
.map(|bit| {
if let Some(name) = header.feature_name(FeatureType::Incompatible, bit) {
format!("{bit} ({name})")
} else {
format!("{bit}")
}
})
.collect::<Vec<String>>();
return Err(invalid_data(format!(
"Unrecognized incompatible feature(s) {}",
feats.join(", ")
)));
}
Ok(header)
}
pub async fn write<S: Storage>(&mut self, image: &S) -> io::Result<()> {
let header_len = if self.v2.version > 2 {
let len = encoded_size(&self.v2).unwrap()
+ encoded_size(&self.v3).unwrap()
+ self.unknown_header_fields.len();
let len = len.next_multiple_of(8);
self.v3.header_length = len as u32;
len
} else {
V2Header::RAW_SIZE
};
let mut header_exts;
let mut backing_file_ofs;
loop {
header_exts = self.serialize_extensions()?;
backing_file_ofs = header_len
.checked_add(header_exts.len())
.ok_or_else(|| invalid_data("Header size overflow"))?;
let backing_file_len = self
.backing_filename
.as_ref()
.map(|n| n.len()) .unwrap_or(0);
let header_end = backing_file_ofs
.checked_add(backing_file_len)
.ok_or_else(|| invalid_data("Header size overflow"))?;
if header_end <= self.cluster_size() {
break;
}
if !self
.extensions
.iter()
.any(|e| e.extension_type() == HeaderExtensionType::FeatureNameTable as u32)
{
return Err(io::Error::other(format!(
"Header would be too long ({header_end} > {})",
self.cluster_size()
)));
}
self.extensions
.retain(|e| e.extension_type() != HeaderExtensionType::FeatureNameTable as u32);
}
if let Some(backing) = self.backing_filename.as_ref() {
self.v2.backing_file_offset = backing_file_ofs as u64;
self.v2.backing_file_size = backing.len() as u32; } else {
self.v2.backing_file_offset = 0;
self.v2.backing_file_size = 0;
};
let mut full_buf = encode_binary(&self.v2)?;
if self.v2.version > 2 {
full_buf.append(&mut encode_binary(&self.v3)?);
full_buf.extend_from_slice(&self.unknown_header_fields);
full_buf.resize(full_buf.len().next_multiple_of(8), 0);
}
full_buf.append(&mut header_exts);
if let Some(backing) = self.backing_filename.as_ref() {
full_buf.extend_from_slice(backing.as_bytes());
}
if full_buf.len() > self.cluster_size() {
return Err(io::Error::other(format!(
"Header is too big to write ({}, larger than a cluster ({}))",
full_buf.len(),
self.cluster_size(),
)));
}
image.write(&full_buf, 0).await
}
pub fn new(
cluster_bits: u32,
refcount_order: u32,
backing_filename: Option<String>,
backing_format: Option<String>,
external_data_file: Option<String>,
) -> Self {
assert!((MIN_CLUSTER_SIZE..=MAX_CLUSTER_SIZE)
.contains(&1usize.checked_shl(cluster_bits).unwrap()));
assert!((MIN_REFCOUNT_WIDTH..=MAX_REFCOUNT_WIDTH)
.contains(&1usize.checked_shl(refcount_order).unwrap()));
let has_external_data_file = external_data_file.is_some();
let incompatible_features = if has_external_data_file {
IncompatibleFeatures::ExternalDataFile as u64
} else {
0
};
let mut extensions = vec![HeaderExtension::feature_name_table()];
if let Some(backing_format) = backing_format {
extensions.push(HeaderExtension::BackingFileFormat(backing_format));
}
if let Some(external_data_file) = external_data_file {
extensions.push(HeaderExtension::ExternalDataFileName(external_data_file));
}
Header {
v2: V2Header {
magic: MAGIC,
version: 3,
backing_file_offset: 0, backing_file_size: 0, cluster_bits,
size: 0.into(),
crypt_method: 0,
l1_size: 0.into(),
l1_table_offset: 0.into(),
refcount_table_offset: 0.into(),
refcount_table_clusters: 0.into(),
nb_snapshots: 0,
snapshots_offset: 0,
},
v3: V3HeaderBase {
incompatible_features,
compatible_features: 0,
autoclear_features: 0,
refcount_order,
header_length: 0, },
unknown_header_fields: Vec::new(),
backing_filename,
extensions,
external_data_file: has_external_data_file,
}
}
pub fn update(&self, new_header: &Header) -> io::Result<()> {
macro_rules! check_field {
($($field:ident).*) => {
(self.$($field).* == new_header.$($field).*).then_some(()).ok_or_else(|| {
io::Error::other(format!(
"Incompatible header modification on {}: {} != {}",
stringify!($($field).*),
self.$($field).*,
new_header.$($field).*
))
})
};
}
check_field!(v2.magic)?;
check_field!(v2.version)?;
check_field!(v2.backing_file_offset)?; check_field!(v2.backing_file_size)?; check_field!(v2.cluster_bits)?;
check_field!(v2.crypt_method)?;
check_field!(v2.nb_snapshots)?; check_field!(v2.snapshots_offset)?; check_field!(v3.incompatible_features)?; check_field!(v3.compatible_features)?; check_field!(v3.autoclear_features)?; check_field!(v3.refcount_order)?;
(self.unknown_header_fields == new_header.unknown_header_fields)
.then_some(())
.ok_or_else(|| io::Error::other("Unknown header fields modified"))?;
(self.backing_filename == new_header.backing_filename)
.then_some(())
.ok_or_else(|| io::Error::other("Backing filename modified"))?;
(self.extensions == new_header.extensions)
.then_some(())
.ok_or_else(|| io::Error::other("Header extensions modified"))?;
check_field!(external_data_file)?;
self.v2.size.store(
new_header.v2.size.load(Ordering::Relaxed),
Ordering::Relaxed,
);
self.v2.l1_table_offset.store(
new_header.v2.l1_table_offset.load(Ordering::Relaxed),
Ordering::Relaxed,
);
self.v2.l1_size.store(
new_header.v2.l1_size.load(Ordering::Relaxed),
Ordering::Relaxed,
);
self.v2.refcount_table_offset.store(
new_header.v2.refcount_table_offset.load(Ordering::Relaxed),
Ordering::Relaxed,
);
self.v2.refcount_table_clusters.store(
new_header
.v2
.refcount_table_clusters
.load(Ordering::Relaxed),
Ordering::Relaxed,
);
Ok(())
}
pub fn size(&self) -> u64 {
self.v2.size.load(Ordering::Relaxed)
}
pub fn require_version(&self, minimum: u32) -> io::Result<()> {
let version = self.v2.version;
if version >= minimum {
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::Unsupported,
format!("qcow2 version {minimum} required, image has version {version}"),
))
}
}
pub fn set_size(&self, new_size: u64) {
self.v2.size.store(new_size, Ordering::Relaxed)
}
pub fn cluster_bits(&self) -> u32 {
self.v2.cluster_bits
}
pub fn cluster_size(&self) -> usize {
1 << self.cluster_bits()
}
pub fn l2_entries(&self) -> usize {
1 << (self.cluster_bits() - 3)
}
pub fn rb_bits(&self) -> u32 {
self.cluster_bits() + 3 - self.refcount_order()
}
pub fn rb_entries(&self) -> usize {
1 << self.rb_bits()
}
pub fn refcount_order(&self) -> u32 {
self.v3.refcount_order
}
pub fn l1_table_offset(&self) -> HostOffset {
HostOffset(self.v2.l1_table_offset.load(Ordering::Relaxed))
}
pub fn l1_table_entries(&self) -> usize {
self.v2.l1_size.load(Ordering::Relaxed) as usize
}
pub fn set_l1_table(&self, l1_table: &L1Table) -> io::Result<()> {
let offset = l1_table.get_offset().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"New L1 table has no assigned offset",
)
})?;
let entries = l1_table.entries();
let entries = entries
.try_into()
.map_err(|err| invalid_data(format!("Too many L1 entries ({entries}): {err}")))?;
self.v2.l1_table_offset.store(offset.0, Ordering::Relaxed);
self.v2.l1_size.store(entries, Ordering::Relaxed);
Ok(())
}
pub fn reftable_offset(&self) -> HostOffset {
HostOffset(self.v2.refcount_table_offset.load(Ordering::Relaxed))
}
pub fn reftable_clusters(&self) -> ClusterCount {
ClusterCount(self.v2.refcount_table_clusters.load(Ordering::Relaxed) as u64)
}
pub fn reftable_entries(&self) -> usize {
(self.reftable_clusters().byte_size(self.cluster_bits()) >> 3) as usize
}
pub fn set_reftable(&self, reftable: &RefTable) -> io::Result<()> {
let offset = reftable.get_offset().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"New refcount table has no assigned offset",
)
})?;
let clusters = reftable.cluster_count();
let clusters = clusters.0.try_into().map_err(|err| {
invalid_data(format!("Too many reftable clusters ({clusters}): {err}"))
})?;
self.v2
.refcount_table_clusters
.store(clusters, Ordering::Relaxed);
self.v2
.refcount_table_offset
.store(offset.0, Ordering::Relaxed);
Ok(())
}
pub fn backing_filename(&self) -> Option<&String> {
self.backing_filename.as_ref()
}
pub fn backing_format(&self) -> Option<&String> {
self.extensions.iter().find_map(|e| match e {
HeaderExtension::BackingFileFormat(fmt) => Some(fmt),
_ => None,
})
}
pub fn external_data_file(&self) -> bool {
self.external_data_file
}
pub fn external_data_filename(&self) -> Option<&String> {
self.extensions.iter().find_map(|e| match e {
HeaderExtension::ExternalDataFileName(filename) => Some(filename),
_ => None,
})
}
pub fn feature_name(&self, feat_type: FeatureType, bit: u32) -> Option<&String> {
for e in &self.extensions {
if let HeaderExtension::FeatureNameTable(names) = e {
if let Some(name) = names.get(&(feat_type, bit as u8)) {
return Some(name);
}
}
}
None
}
fn serialize_extensions(&self) -> io::Result<Vec<u8>> {
let mut result = Vec::new();
for e in &self.extensions {
let mut data = e.serialize_data()?;
let ext_hdr = HeaderExtensionHeader {
extension_type: e.extension_type(),
length: data.len().try_into().map_err(|err| {
invalid_data(format!("Header extension too long ({}): {err}", data.len()))
})?,
};
result.append(&mut encode_binary(&ext_hdr)?);
result.append(&mut data);
result.resize(result.len().next_multiple_of(8), 0);
}
let end_ext = HeaderExtensionHeader {
extension_type: HeaderExtensionType::End as u32,
length: 0,
};
result.append(&mut encode_binary(&end_ext)?);
result.resize(result.len().next_multiple_of(8), 0);
Ok(result)
}
async fn write_v2_header<S: Storage>(&self, image: &S) -> io::Result<()> {
let v2_header = encode_binary(&self.v2)?;
image.write(&v2_header, 0).await
}
pub async fn write_reftable_pointer<S: Storage>(&self, image: &S) -> io::Result<()> {
self.write_v2_header(image).await
}
pub async fn write_l1_table_pointer<S: Storage>(&self, image: &S) -> io::Result<()> {
self.write_v2_header(image).await
}
pub async fn write_size<S: Storage>(&self, image: &S) -> io::Result<()> {
self.write_v2_header(image).await
}
}
impl HeaderExtension {
fn deserialize(ext_type: u32, data: Vec<u8>) -> io::Result<Option<Self>> {
let ext = if let Ok(ext_type) = HeaderExtensionType::try_from(ext_type) {
match ext_type {
HeaderExtensionType::End => return Ok(None),
HeaderExtensionType::BackingFileFormat => {
let fmt = String::from_utf8(data).map_err(|err| {
invalid_data(format!("Invalid backing file format: {err}"))
})?;
HeaderExtension::BackingFileFormat(fmt)
}
HeaderExtensionType::FeatureNameTable => {
let mut feats = HashMap::new();
for feat in data.chunks(48) {
let feat_type: FeatureType = match feat[0].try_into() {
Ok(ft) => ft,
Err(_) => continue, };
let feat_name_bytes = feat[2..].split(|c| *c == 0).next().unwrap();
let feat_name = String::from_utf8_lossy(feat_name_bytes);
feats.insert((feat_type, feat[1]), feat_name.to_string());
}
HeaderExtension::FeatureNameTable(feats)
}
HeaderExtensionType::ExternalDataFileName => {
let filename = String::from_utf8(data).map_err(|err| {
invalid_data(format!("Invalid external data file name: {err}"))
})?;
HeaderExtension::ExternalDataFileName(filename)
}
}
} else {
HeaderExtension::Unknown {
extension_type: ext_type,
data,
}
};
Ok(Some(ext))
}
fn extension_type(&self) -> u32 {
match self {
HeaderExtension::BackingFileFormat(_) => HeaderExtensionType::BackingFileFormat as u32,
HeaderExtension::FeatureNameTable(_) => HeaderExtensionType::FeatureNameTable as u32,
HeaderExtension::ExternalDataFileName(_) => {
HeaderExtensionType::ExternalDataFileName as u32
}
HeaderExtension::Unknown {
extension_type,
data: _,
} => *extension_type,
}
}
fn serialize_data(&self) -> io::Result<Vec<u8>> {
match self {
HeaderExtension::BackingFileFormat(fmt) => Ok(fmt.as_bytes().into()),
HeaderExtension::FeatureNameTable(map) => {
let mut result = Vec::new();
for (bit, name) in map {
result.push(bit.0 as u8);
result.push(bit.1);
let mut padded_name = vec![0; 46];
let name_bytes = name.as_bytes();
let truncated_len = cmp::min(name_bytes.len(), 46);
padded_name[..truncated_len].copy_from_slice(&name_bytes[..truncated_len]);
result.extend_from_slice(&padded_name);
}
Ok(result)
}
HeaderExtension::ExternalDataFileName(filename) => Ok(filename.as_bytes().into()),
HeaderExtension::Unknown {
extension_type: _,
data,
} => Ok(data.clone()),
}
}
fn feature_name_table() -> Self {
use {AutoclearFeatures as A, CompatibleFeatures as C, IncompatibleFeatures as I};
let mut map = HashMap::new();
map.insert(I::Dirty.into(), "dirty".into());
map.insert(I::Corrupt.into(), "corrupt".into());
map.insert(I::ExternalDataFile.into(), "external data file".into());
map.insert(
I::CompressionType.into(),
"extended compression type".into(),
);
map.insert(I::ExtendedL2Entries.into(), "extended L2 entries".into());
map.insert(C::LazyRefcounts.into(), "lazy refcounts".into());
map.insert(A::Bitmaps.into(), "persistent dirty bitmaps".into());
map.insert(A::RawExternalData.into(), "raw external data file".into());
HeaderExtension::FeatureNameTable(map)
}
}
#[derive(Copy, Clone, Default, Debug)]
pub(super) struct L1Entry(u64);
impl L1Entry {
pub fn l2_offset(&self) -> Option<HostOffset> {
let ofs = self.0 & 0x00ff_ffff_ffff_fe00u64;
if ofs == 0 {
None
} else {
Some(HostOffset(ofs))
}
}
pub fn is_copied(&self) -> bool {
self.0 & (1u64 << 63) != 0
}
pub fn reserved_bits(&self) -> u64 {
self.0 & 0x7f00_0000_0000_01feu64
}
}
impl TableEntry for L1Entry {
fn try_from_plain(value: u64, header: &Header) -> io::Result<Self> {
let entry = L1Entry(value);
if entry.reserved_bits() != 0 {
return Err(invalid_data(format!(
"Invalid L1 entry 0x{value:x}, reserved bits set (0x{:x})",
entry.reserved_bits(),
)));
}
if let Some(l2_ofs) = entry.l2_offset() {
if l2_ofs.in_cluster_offset(header.cluster_bits()) != 0 {
return Err(invalid_data(format!(
"Invalid L1 entry 0x{value:x}, offset ({l2_ofs}) is not aligned to cluster size (0x{:x})",
header.cluster_size(),
)));
}
}
Ok(entry)
}
fn to_plain(&self) -> u64 {
self.0
}
}
#[derive(Debug)]
pub(super) struct L1Table {
cluster: Option<HostCluster>,
data: Box<[L1Entry]>,
cluster_bits: u32,
modified: AtomicBool,
}
impl L1Table {
pub fn clone_and_grow(&self, at_least_index: usize, header: &Header) -> io::Result<Self> {
let new_entry_count = cmp::max(at_least_index + 1, self.data.len());
let new_entry_count =
new_entry_count.next_multiple_of(header.cluster_size() / size_of::<L1Entry>());
if new_entry_count > <Self as Table>::MAX_ENTRIES {
return Err(io::Error::other(
"Cannot grow the image to this size; L1 table would become too big",
));
}
let mut new_data = vec![L1Entry::default(); new_entry_count];
new_data[..self.data.len()].copy_from_slice(&self.data);
Ok(Self {
cluster: None,
data: new_data.into_boxed_slice(),
cluster_bits: header.cluster_bits(),
modified: true.into(),
})
}
pub fn in_bounds(&self, index: usize) -> bool {
index < self.data.len()
}
pub fn enter_l2_table(&mut self, index: usize, l2: &L2Table) -> io::Result<()> {
let l2_offset = l2.get_offset().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"L2 table has no assigned offset",
)
})?;
let l1entry = L1Entry((1 << 63) | l2_offset.0);
debug_assert!(l1entry.reserved_bits() == 0);
self.data[index] = l1entry;
self.modified.store(true, Ordering::Relaxed);
Ok(())
}
}
impl Table for L1Table {
type InternalEntry = L1Entry;
type Entry = L1Entry;
const NAME: &'static str = "L1 table";
const MAX_ENTRIES: usize = 4 * 1024 * 1024;
fn from_data(data: Box<[L1Entry]>, header: &Header) -> Self {
Self {
cluster: None,
data,
cluster_bits: header.cluster_bits(),
modified: true.into(),
}
}
fn entries(&self) -> usize {
self.data.len()
}
fn get_ref(&self, index: usize) -> Option<&L1Entry> {
self.data.get(index)
}
fn get(&self, index: usize) -> L1Entry {
self.data.get(index).copied().unwrap_or(L1Entry(0))
}
fn get_cluster(&self) -> Option<HostCluster> {
self.cluster
}
fn get_offset(&self) -> Option<HostOffset> {
self.cluster.map(|index| index.offset(self.cluster_bits))
}
fn set_cluster(&mut self, cluster: HostCluster) {
self.cluster = Some(cluster);
self.modified.store(true, Ordering::Relaxed);
}
fn unset_cluster(&mut self) {
self.cluster = None;
}
fn is_modified(&self) -> bool {
self.modified.load(Ordering::Relaxed)
}
fn clear_modified(&self) {
self.modified.store(false, Ordering::Relaxed);
}
fn set_modified(&self) {
self.modified.store(true, Ordering::Relaxed);
}
fn cluster_bits(&self) -> u32 {
self.cluster_bits
}
}
#[derive(Copy, Clone, Default, Debug)]
pub(super) struct L2Entry(u64);
#[derive(Default, Debug)]
pub(super) struct AtomicL2Entry(AtomicU64);
#[derive(Debug, Clone)]
pub(super) enum L2Mapping {
DataFile {
host_cluster: HostCluster,
copied: bool,
},
Backing {
backing_offset: u64,
},
Zero {
host_cluster: Option<HostCluster>,
copied: bool,
},
Compressed {
host_offset: HostOffset,
length: u64,
},
}
impl L2Entry {
pub fn cluster_offset(&self, external_data_file: bool) -> Option<HostOffset> {
let ofs = self.0 & 0x00ff_ffff_ffff_fe00u64;
if ofs != 0 || (external_data_file && self.is_copied()) {
Some(HostOffset(ofs))
} else {
None
}
}
pub fn is_compressed(&self) -> bool {
self.0 & (1u64 << 62) != 0
}
pub fn is_copied(&self) -> bool {
self.0 & (1u64 << 63) != 0
}
#[must_use]
pub fn without_copied(self) -> Self {
L2Entry(self.0 & !(1u64 << 63))
}
pub fn is_zero(&self) -> bool {
self.0 & (1u64 << 0) != 0
}
pub fn reserved_bits(&self) -> u64 {
if self.is_compressed() {
self.0 & 0x8000_0000_0000_0000u64
} else {
self.0 & 0x3f00_0000_0000_01feu64
}
}
pub fn compressed_descriptor(&self) -> u64 {
self.0 & 0x3fff_ffff_ffff_ffffu64
}
pub fn compressed_range(&self, cluster_bits: u32) -> Option<(HostOffset, u64)> {
if self.is_compressed() {
let desc = self.compressed_descriptor();
let compressed_offset_bits = 62 - (cluster_bits - 8);
let offset = desc & ((1 << compressed_offset_bits) - 1) & 0x00ff_ffff_ffff_ffffu64;
let sectors = desc >> compressed_offset_bits;
let length = (sectors + 1) * 512 - (offset & 511);
Some((HostOffset(offset), length))
} else {
None
}
}
fn allocation(
&self,
cluster_bits: u32,
external_data_file: bool,
) -> Option<(HostCluster, ClusterCount)> {
if let Some((offset, length)) = self.compressed_range(cluster_bits) {
let first_cluster = offset.cluster(cluster_bits);
let cluster_count = ClusterCount::from_byte_size(
offset + length - first_cluster.offset(cluster_bits),
cluster_bits,
);
Some((first_cluster, cluster_count))
} else {
self.cluster_offset(external_data_file)
.map(|ofs| (ofs.cluster(cluster_bits), ClusterCount(1)))
}
}
fn into_mapping(
self,
guest_cluster: GuestCluster,
cluster_bits: u32,
external_data_file: bool,
) -> io::Result<L2Mapping> {
let mapping = if let Some((offset, length)) = self.compressed_range(cluster_bits) {
L2Mapping::Compressed {
host_offset: offset,
length,
}
} else if self.is_zero() {
let host_cluster = self
.cluster_offset(external_data_file)
.map(|ofs| {
ofs.checked_cluster(cluster_bits).ok_or_else(|| {
let offset = guest_cluster.offset(cluster_bits);
io::Error::other(format!(
"Unaligned pre-allocated zero cluster at {offset}; L2 entry: {self:?}"
))
})
})
.transpose()?;
L2Mapping::Zero {
host_cluster,
copied: host_cluster.is_some() && self.is_copied(),
}
} else if let Some(host_offset) = self.cluster_offset(external_data_file) {
let host_cluster = host_offset.checked_cluster(cluster_bits).ok_or_else(|| {
let offset = guest_cluster.offset(cluster_bits);
io::Error::other(format!(
"Unaligned data cluster at {offset}; L2 entry: {self:?}"
))
})?;
L2Mapping::DataFile {
host_cluster,
copied: self.is_copied(),
}
} else {
L2Mapping::Backing {
backing_offset: guest_cluster.offset(cluster_bits).0,
}
};
Ok(mapping)
}
fn from_mapping(value: L2Mapping, cluster_bits: u32) -> Self {
let num_val: u64 = match value {
L2Mapping::DataFile {
host_cluster,
copied,
} => {
debug_assert!(host_cluster.offset(cluster_bits) <= MAX_OFFSET);
if copied {
(1 << 63) | host_cluster.offset(cluster_bits).0
} else {
host_cluster.offset(cluster_bits).0
}
}
L2Mapping::Backing { backing_offset: _ } => 0,
L2Mapping::Zero {
host_cluster,
copied,
} => {
let host_offset = host_cluster.map(|hc| hc.offset(cluster_bits));
debug_assert!(host_offset.unwrap_or(HostOffset(0)) <= MAX_OFFSET);
if copied {
(1 << 63) | host_offset.unwrap().0 | 0x1
} else {
host_offset.unwrap_or(HostOffset(0)).0 | 0x1
}
}
L2Mapping::Compressed {
host_offset,
length,
} => {
let compressed_offset_bits = 62 - (cluster_bits - 8);
assert!(length < 1 << cluster_bits);
assert!(host_offset.0 < 1 << compressed_offset_bits);
let sectors = (length - 1 + (host_offset.0 & 511)) / 512;
(1 << 62) | (sectors << compressed_offset_bits) | host_offset.0
}
};
let entry = L2Entry(num_val);
debug_assert!(entry.reserved_bits() == 0);
entry
}
}
impl AtomicL2Entry {
fn get(&self) -> L2Entry {
L2Entry(self.0.load(Ordering::Relaxed))
}
unsafe fn swap(&self, l2e: L2Entry) -> L2Entry {
L2Entry(self.0.swap(l2e.0, Ordering::Relaxed))
}
}
impl TableEntry for AtomicL2Entry {
fn try_from_plain(value: u64, header: &Header) -> io::Result<Self> {
let entry = L2Entry(value);
if entry.reserved_bits() != 0 {
return Err(invalid_data(format!(
"Invalid L2 entry 0x{value:x}, reserved bits set (0x{:x})",
entry.reserved_bits(),
)));
}
if let Some(offset) = entry.cluster_offset(header.external_data_file()) {
if !entry.is_compressed() && offset.in_cluster_offset(header.cluster_bits()) != 0 {
return Err(invalid_data(format!(
"Invalid L2 entry 0x{value:x}, offset ({offset}) is not aligned to cluster size (0x{:x})",
header.cluster_size(),
)));
}
}
Ok(AtomicL2Entry(AtomicU64::new(entry.0)))
}
fn to_plain(&self) -> u64 {
self.get().0
}
}
impl L2Mapping {
pub fn is_consecutive(&self, preceding: &L2Mapping, cluster_bits: u32) -> bool {
match preceding {
L2Mapping::DataFile {
host_cluster: prior_cluster,
copied,
} => {
if let L2Mapping::DataFile {
host_cluster: next_cluster,
copied: next_copied,
} = self
{
*next_cluster == *prior_cluster + ClusterCount(1) && *next_copied == *copied
} else {
false
}
}
L2Mapping::Backing {
backing_offset: prior_backing_offset,
} => {
let Some(expected_next) = prior_backing_offset.checked_add(1 << cluster_bits)
else {
return false;
};
if let L2Mapping::Backing {
backing_offset: next_offset,
} = self
{
*next_offset == expected_next
} else {
false
}
}
L2Mapping::Zero {
host_cluster: _,
copied: _,
} => {
matches!(
self,
L2Mapping::Zero {
host_cluster: _,
copied: _,
}
)
}
L2Mapping::Compressed {
host_offset: _,
length: _,
} => {
matches!(
self,
L2Mapping::Compressed {
host_offset: _,
length: _,
}
)
}
}
}
}
#[derive(Debug)]
pub(super) struct L2Table {
cluster: Option<HostCluster>,
data: Box<[AtomicL2Entry]>,
cluster_bits: u32,
external_data_file: bool,
modified: AtomicBool,
writer_lock: Mutex<()>,
}
#[derive(Debug)]
pub(super) struct L2TableWriteGuard<'a> {
table: &'a L2Table,
_lock: MutexGuard<'a, ()>,
}
impl L2Table {
pub fn new_cleared(header: &Header) -> Self {
let mut data = Vec::with_capacity(header.l2_entries());
data.resize_with(header.l2_entries(), Default::default);
L2Table {
cluster: None,
data: data.into_boxed_slice(),
cluster_bits: header.cluster_bits(),
external_data_file: header.external_data_file(),
modified: true.into(),
writer_lock: Default::default(),
}
}
pub fn get_mapping(&self, lookup_cluster: GuestCluster) -> io::Result<L2Mapping> {
self.get(lookup_cluster.l2_index(self.cluster_bits))
.into_mapping(lookup_cluster, self.cluster_bits, self.external_data_file)
}
pub async fn lock_write(&self) -> L2TableWriteGuard<'_> {
L2TableWriteGuard {
table: self,
_lock: self.writer_lock.lock().await,
}
}
}
impl L2TableWriteGuard<'_> {
pub fn get_mapping(&self, lookup_cluster: GuestCluster) -> io::Result<L2Mapping> {
self.table.get_mapping(lookup_cluster)
}
#[must_use = "Leaked allocation must be freed"]
pub fn map_cluster(
&mut self,
index: usize,
host_cluster: HostCluster,
) -> Option<(HostCluster, ClusterCount)> {
let new = L2Entry::from_mapping(
L2Mapping::DataFile {
host_cluster,
copied: true,
},
self.table.cluster_bits,
);
let l2e = unsafe { self.table.data[index].swap(new) };
self.table.modified.store(true, Ordering::Relaxed);
let allocation = l2e.allocation(self.table.cluster_bits, self.table.external_data_file);
if let Some((a_cluster, a_count)) = allocation {
if a_cluster == host_cluster && a_count == ClusterCount(1) {
None
} else {
allocation
}
} else {
None
}
}
#[must_use = "Leaked allocation must be freed"]
pub fn zero_cluster(
&mut self,
index: usize,
keep_allocation: bool,
) -> io::Result<Option<(HostCluster, ClusterCount)>> {
let cluster_copied = if keep_allocation {
match self.table.data[index].get().into_mapping(
GuestCluster(0), self.table.cluster_bits,
self.table.external_data_file,
)? {
L2Mapping::DataFile {
host_cluster,
copied,
} => Some((host_cluster, copied)),
L2Mapping::Backing { backing_offset: _ } => None,
L2Mapping::Zero {
host_cluster: Some(host_cluster),
copied,
} => Some((host_cluster, copied)),
L2Mapping::Zero {
host_cluster: None,
copied: _,
} => None,
L2Mapping::Compressed {
host_offset: _,
length: _,
} => None,
}
} else {
None
};
let retained = cluster_copied.is_some();
let new = if let Some((cluster, copied)) = cluster_copied {
L2Mapping::Zero {
host_cluster: Some(cluster),
copied,
}
} else {
L2Mapping::Zero {
host_cluster: None,
copied: false,
}
};
let new = L2Entry::from_mapping(new, self.table.cluster_bits);
let old = unsafe { self.table.data[index].swap(new) };
self.table.modified.store(true, Ordering::Relaxed);
let leaked = if !retained {
old.allocation(self.table.cluster_bits, self.table.external_data_file)
} else {
None
};
Ok(leaked)
}
#[must_use = "Leaked allocation must be freed"]
pub fn discard_cluster(&mut self, index: usize) -> Option<(HostCluster, ClusterCount)> {
let new = L2Entry(0);
let old = unsafe { self.table.data[index].swap(new) };
self.table.modified.store(true, Ordering::Relaxed);
old.allocation(self.table.cluster_bits, self.table.external_data_file)
}
}
impl Table for L2Table {
type InternalEntry = AtomicL2Entry;
type Entry = L2Entry;
const NAME: &'static str = "L2 table";
const MAX_ENTRIES: usize = MAX_CLUSTER_SIZE / 8;
fn from_data(data: Box<[AtomicL2Entry]>, header: &Header) -> Self {
assert!(data.len() == header.l2_entries());
Self {
cluster: None,
data,
cluster_bits: header.cluster_bits(),
external_data_file: header.external_data_file(),
modified: true.into(),
writer_lock: Default::default(),
}
}
fn entries(&self) -> usize {
self.data.len()
}
fn get_ref(&self, index: usize) -> Option<&AtomicL2Entry> {
self.data.get(index)
}
fn get(&self, index: usize) -> L2Entry {
self.data
.get(index)
.map(|l2e| l2e.get())
.unwrap_or(L2Entry(0))
}
fn get_cluster(&self) -> Option<HostCluster> {
self.cluster
}
fn get_offset(&self) -> Option<HostOffset> {
self.cluster.map(|index| index.offset(self.cluster_bits))
}
fn set_cluster(&mut self, cluster: HostCluster) {
self.cluster = Some(cluster);
self.modified.store(true, Ordering::Relaxed);
}
fn unset_cluster(&mut self) {
self.cluster = None;
}
fn is_modified(&self) -> bool {
self.modified.load(Ordering::Relaxed)
}
fn clear_modified(&self) {
self.modified.store(false, Ordering::Relaxed);
}
fn set_modified(&self) {
self.modified.store(true, Ordering::Relaxed);
}
fn cluster_bits(&self) -> u32 {
self.cluster_bits
}
}
impl Clone for L2Table {
fn clone(&self) -> Self {
let mut data = Vec::with_capacity(self.data.len());
for entry in &self.data {
let entry = entry.get().without_copied();
data.push(AtomicL2Entry(AtomicU64::new(entry.0)));
}
let modified = AtomicBool::new(self.is_modified());
L2Table {
cluster: None,
data: data.into_boxed_slice(),
cluster_bits: self.cluster_bits,
external_data_file: self.external_data_file,
modified,
writer_lock: Default::default(),
}
}
}
impl Drop for L2Table {
fn drop(&mut self) {
if self.is_modified() {
error!("L2 table dropped while modified; was the image closed before being flushed?");
}
}
}
#[derive(Copy, Clone, Default, Debug)]
pub(super) struct RefTableEntry(u64);
impl RefTableEntry {
pub fn refblock_offset(&self) -> Option<HostOffset> {
let ofs = self.0 & 0xffff_ffff_ffff_fe00u64;
if ofs == 0 {
None
} else {
Some(HostOffset(ofs))
}
}
pub fn reserved_bits(&self) -> u64 {
self.0 & 0x0000_0000_0000_01ffu64
}
}
impl TableEntry for RefTableEntry {
fn try_from_plain(value: u64, header: &Header) -> io::Result<Self> {
let entry = RefTableEntry(value);
if entry.reserved_bits() != 0 {
return Err(invalid_data(format!(
"Invalid reftable entry 0x{value:x}, reserved bits set (0x{:x})",
entry.reserved_bits(),
)));
}
if let Some(rb_ofs) = entry.refblock_offset() {
if rb_ofs.in_cluster_offset(header.cluster_bits()) != 0 {
return Err(invalid_data(
format!(
"Invalid reftable entry 0x{value:x}, offset ({rb_ofs}) is not aligned to cluster size (0x{:x})",
header.cluster_size(),
),
));
}
}
Ok(entry)
}
fn to_plain(&self) -> u64 {
self.0
}
}
#[derive(Debug)]
pub(super) struct RefTable {
cluster: Option<HostCluster>,
data: Box<[RefTableEntry]>,
cluster_bits: u32,
modified: AtomicBool,
}
impl RefTable {
pub fn clone_and_grow(&self, header: &Header, at_least_index: usize) -> io::Result<Self> {
let cluster_size = header.cluster_size();
let rb_entries = header.rb_entries();
let mut extra_rbs = 1;
let new_entry_count = loop {
let entry_count = cmp::max(at_least_index + 1 + extra_rbs, self.data.len());
let entry_count = entry_count.next_multiple_of(cluster_size / size_of::<u64>());
let size = entry_count * size_of::<u64>();
let refcount_clusters = size / cluster_size + extra_rbs;
let rbs_needed = refcount_clusters.div_ceil(rb_entries);
if extra_rbs == rbs_needed {
break entry_count;
}
extra_rbs = rbs_needed;
};
if new_entry_count > <Self as Table>::MAX_ENTRIES {
return Err(io::Error::other(
"Cannot grow the image to this size; refcount table would become too big",
));
}
let mut new_data = vec![RefTableEntry::default(); new_entry_count];
new_data[..self.data.len()].copy_from_slice(&self.data);
Ok(Self {
cluster: None,
data: new_data.into_boxed_slice(),
cluster_bits: header.cluster_bits(),
modified: true.into(),
})
}
pub fn in_bounds(&self, index: usize) -> bool {
index < self.data.len()
}
pub fn enter_refblock(&mut self, index: usize, rb: &RefBlock) -> io::Result<()> {
let rb_offset = rb.get_offset().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"Refcount block as no assigned offset",
)
})?;
let rt_entry = RefTableEntry(rb_offset.0);
debug_assert!(rt_entry.reserved_bits() == 0);
self.data[index] = rt_entry;
self.modified.store(true, Ordering::Relaxed);
Ok(())
}
}
impl Table for RefTable {
type InternalEntry = RefTableEntry;
type Entry = RefTableEntry;
const NAME: &'static str = "Refcount table";
const MAX_ENTRIES: usize = <L1Table as Table>::MAX_ENTRIES;
fn from_data(data: Box<[RefTableEntry]>, header: &Header) -> Self {
Self {
cluster: None,
data,
cluster_bits: header.cluster_bits(),
modified: true.into(),
}
}
fn entries(&self) -> usize {
self.data.len()
}
fn get_ref(&self, index: usize) -> Option<&RefTableEntry> {
self.data.get(index)
}
fn get(&self, index: usize) -> RefTableEntry {
self.data.get(index).copied().unwrap_or(RefTableEntry(0))
}
fn get_cluster(&self) -> Option<HostCluster> {
self.cluster
}
fn get_offset(&self) -> Option<HostOffset> {
self.cluster.map(|index| index.offset(self.cluster_bits))
}
fn set_cluster(&mut self, cluster: HostCluster) {
self.cluster = Some(cluster);
self.modified.store(true, Ordering::Relaxed);
}
fn unset_cluster(&mut self) {
self.cluster = None;
}
fn is_modified(&self) -> bool {
self.modified.load(Ordering::Relaxed)
}
fn clear_modified(&self) {
self.modified.store(false, Ordering::Relaxed);
}
fn set_modified(&self) {
self.modified.store(true, Ordering::Relaxed);
}
fn cluster_bits(&self) -> u32 {
self.cluster_bits
}
}
pub(super) struct RefBlock {
cluster: Option<HostCluster>,
raw_data: IoBuffer,
refcount_order: u32,
cluster_bits: u32,
modified: AtomicBool,
writer_lock: Mutex<()>,
}
pub(super) struct RefBlockWriteGuard<'a> {
rb: &'a RefBlock,
_lock: MutexGuard<'a, ()>,
}
impl RefBlock {
pub fn new_cleared<S: Storage>(for_image: &S, header: &Header) -> io::Result<Self> {
let mut raw_data = IoBuffer::new(header.cluster_size(), for_image.mem_align())?;
raw_data.as_mut().into_slice().fill(0);
Ok(RefBlock {
cluster: None,
raw_data,
refcount_order: header.refcount_order(),
cluster_bits: header.cluster_bits(),
modified: true.into(),
writer_lock: Default::default(),
})
}
pub async fn load<S: Storage>(
image: &S,
header: &Header,
cluster: HostCluster,
) -> io::Result<Self> {
let cluster_bits = header.cluster_bits();
let cluster_size = 1 << cluster_bits;
let refcount_order = header.refcount_order();
let offset = cluster.offset(cluster_bits);
check_table(
"Refcount block",
offset.0,
cluster_size,
1,
MAX_CLUSTER_SIZE,
cluster_size,
)?;
let mut raw_data =
IoBuffer::new(cluster_size, cmp::max(image.mem_align(), size_of::<u64>()))?;
image.read(&mut raw_data, offset.0).await?;
Ok(RefBlock {
cluster: Some(cluster),
raw_data,
refcount_order,
cluster_bits,
modified: false.into(),
writer_lock: Default::default(),
})
}
pub async fn write<S: Storage>(&self, image: &S) -> io::Result<()> {
let offset = self
.get_offset()
.ok_or_else(|| io::Error::other("Cannot write qcow2 refcount block, no offset set"))?;
self.clear_modified();
if let Err(err) = image.write(self.raw_data.as_ref(), offset.0).await {
self.set_modified();
return Err(err);
}
Ok(())
}
pub fn get_cluster(&self) -> Option<HostCluster> {
self.cluster
}
pub fn get_offset(&self) -> Option<HostOffset> {
self.cluster.map(|index| index.offset(self.cluster_bits))
}
pub fn set_cluster(&mut self, cluster: HostCluster) {
self.cluster = Some(cluster);
self.set_modified();
}
fn sub_byte_refcount_access(&self, index: usize) -> (usize, u8, usize) {
let order = self.refcount_order;
debug_assert!(order < 3);
let byte_index = index >> (3 - order);
let mask = (1 << (1 << order)) - 1;
let shift = (index << order) % 8;
(byte_index, mask, shift)
}
pub fn get(&self, index: usize) -> u64 {
match self.refcount_order {
0..=2 => {
let (index, mask, shift) = self.sub_byte_refcount_access(index);
let raw_data_slice = unsafe { self.raw_data.as_ref().into_typed_slice::<u8>() };
let atomic =
unsafe { AtomicU8::from_ptr(&raw_data_slice[index] as *const u8 as *mut u8) };
((atomic.load(Ordering::Relaxed) >> shift) & mask) as u64
}
3 => {
let raw_data_slice = unsafe { self.raw_data.as_ref().into_typed_slice::<u8>() };
let atomic =
unsafe { AtomicU8::from_ptr(&raw_data_slice[index] as *const u8 as *mut u8) };
atomic.load(Ordering::Relaxed) as u64
}
4 => {
let raw_data_slice = unsafe { self.raw_data.as_ref().into_typed_slice::<u16>() };
let atomic = unsafe {
AtomicU16::from_ptr(&raw_data_slice[index] as *const u16 as *mut u16)
};
u16::from_be(atomic.load(Ordering::Relaxed)) as u64
}
5 => {
let raw_data_slice = unsafe { self.raw_data.as_ref().into_typed_slice::<u32>() };
let atomic = unsafe {
AtomicU32::from_ptr(&raw_data_slice[index] as *const u32 as *mut u32)
};
u32::from_be(atomic.load(Ordering::Relaxed)) as u64
}
6 => {
let raw_data_slice = unsafe { self.raw_data.as_ref().into_typed_slice::<u64>() };
let atomic = unsafe {
AtomicU64::from_ptr(&raw_data_slice[index] as *const u64 as *mut u64)
};
u64::from_be(atomic.load(Ordering::Relaxed))
}
_ => unreachable!(),
}
}
pub async fn lock_write(&self) -> RefBlockWriteGuard<'_> {
RefBlockWriteGuard {
rb: self,
_lock: self.writer_lock.lock().await,
}
}
pub fn is_modified(&self) -> bool {
self.modified.load(Ordering::Relaxed)
}
pub fn clear_modified(&self) {
self.modified.store(false, Ordering::Relaxed);
}
pub fn set_modified(&self) {
self.modified.store(true, Ordering::Relaxed);
}
pub fn is_zero(&self, index: usize) -> bool {
self.get(index) == 0
}
}
impl RefBlockWriteGuard<'_> {
unsafe fn fetch_update_bitset(
bitset: &AtomicU8,
change: i64,
base_mask: u8,
shift: usize,
) -> io::Result<u64> {
let mask = base_mask << shift;
let full = bitset.load(Ordering::Relaxed);
let old = (full & mask) >> shift;
let new = if change > 0 {
let change = change.try_into().map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("Requested refcount change of {change} is too big for the image’s refcount width"),
)
})?;
old.checked_add(change)
} else {
let change = (-change).try_into().map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("Requested refcount change of {change} is too big for the image’s refcount width"),
)
})?;
old.checked_sub(change)
};
let new = new.ok_or_else(|| {
invalid_data(format!(
"Changing refcount from {old} by {change} would overflow"
))
})?;
if new > base_mask {
return Err(invalid_data(format!(
"Changing refcount from {old} to {new} (by {change}) would overflow"
)));
}
let full = (full & !mask) | (new << shift);
bitset.store(full, Ordering::Relaxed);
Ok(old as u64)
}
unsafe fn fetch_update_full<
T,
L: FnOnce(&T) -> u64,
S: FnOnce(&T, u64) -> Result<(), TryFromIntError>,
>(
atomic: &T,
change: i64,
load: L,
store: S,
) -> io::Result<u64> {
let old = load(atomic);
let new = if change > 0 {
old.checked_add(change as u64)
} else {
old.checked_sub(-change as u64)
};
let new = new.ok_or_else(|| {
invalid_data(format!(
"Changing refcount from {old} by {change} would overflow"
))
})?;
store(atomic, new).map_err(|_| {
invalid_data(format!(
"Changing refcount from {old} to {new} (by {change}) would overflow"
))
})?;
Ok(old)
}
fn modify(&mut self, index: usize, change: i64) -> io::Result<u64> {
let result = match self.rb.refcount_order {
0..=2 => {
let (index, mask, shift) = self.rb.sub_byte_refcount_access(index);
let raw_data_slice = unsafe { self.rb.raw_data.as_ref().into_typed_slice::<u8>() };
let atomic =
unsafe { AtomicU8::from_ptr(&raw_data_slice[index] as *const u8 as *mut u8) };
unsafe { Self::fetch_update_bitset(atomic, change, mask, shift) }
}
3 => {
let raw_data_slice = unsafe { self.rb.raw_data.as_ref().into_typed_slice::<u8>() };
let atomic =
unsafe { AtomicU8::from_ptr(&raw_data_slice[index] as *const u8 as *mut u8) };
unsafe {
Self::fetch_update_full(
atomic,
change,
|a| a.load(Ordering::Relaxed) as u64,
|a, v| {
a.store(v.try_into()?, Ordering::Relaxed);
Ok(())
},
)
}
}
4 => {
let raw_data_slice = unsafe { self.rb.raw_data.as_ref().into_typed_slice::<u16>() };
let atomic = unsafe {
AtomicU16::from_ptr(&raw_data_slice[index] as *const u16 as *mut u16)
};
unsafe {
Self::fetch_update_full(
atomic,
change,
|a| u16::from_be(a.load(Ordering::Relaxed)) as u64,
|a, v| {
a.store(u16::try_from(v)?.to_be(), Ordering::Relaxed);
Ok(())
},
)
}
}
5 => {
let raw_data_slice = unsafe { self.rb.raw_data.as_ref().into_typed_slice::<u32>() };
let atomic = unsafe {
AtomicU32::from_ptr(&raw_data_slice[index] as *const u32 as *mut u32)
};
unsafe {
Self::fetch_update_full(
atomic,
change,
|a| u32::from_be(a.load(Ordering::Relaxed)) as u64,
|a, v| {
a.store(u32::try_from(v)?.to_be(), Ordering::Relaxed);
Ok(())
},
)
}
}
6 => {
let raw_data_slice = unsafe { self.rb.raw_data.as_ref().into_typed_slice::<u64>() };
let atomic = unsafe {
AtomicU64::from_ptr(&raw_data_slice[index] as *const u64 as *mut u64)
};
unsafe {
Self::fetch_update_full(
atomic,
change,
|a| u64::from_be(a.load(Ordering::Relaxed)),
|a, v| {
a.store(v.to_be(), Ordering::Relaxed);
Ok(())
},
)
}
}
_ => unreachable!(),
};
let result = result?;
self.rb.modified.store(true, Ordering::Relaxed);
Ok(result)
}
pub fn increment(&mut self, index: usize) -> io::Result<u64> {
self.modify(index, 1)
}
pub fn decrement(&mut self, index: usize) -> io::Result<u64> {
self.modify(index, -1)
}
pub fn is_zero(&self, index: usize) -> bool {
self.rb.is_zero(index)
}
}
impl Drop for RefBlock {
fn drop(&mut self) {
if self.is_modified() {
error!(
"Refcount block dropped while modified; was the image closed before being flushed?"
);
}
}
}
pub trait TableEntry
where
Self: Sized,
{
fn try_from_plain(value: u64, header: &Header) -> io::Result<Self>;
fn to_plain(&self) -> u64;
}
pub trait Table: Sized {
type InternalEntry: TableEntry;
type Entry: Copy;
const NAME: &'static str;
const MAX_ENTRIES: usize;
fn from_data(data: Box<[Self::InternalEntry]>, header: &Header) -> Self;
fn entries(&self) -> usize;
fn get_ref(&self, index: usize) -> Option<&Self::InternalEntry>;
fn get(&self, index: usize) -> Self::Entry;
fn get_cluster(&self) -> Option<HostCluster>;
fn get_offset(&self) -> Option<HostOffset>;
fn set_cluster(&mut self, cluster: HostCluster);
fn unset_cluster(&mut self);
fn cluster_bits(&self) -> u32;
fn is_modified(&self) -> bool;
fn clear_modified(&self);
fn set_modified(&self);
fn byte_size(&self) -> usize {
self.entries() * size_of::<u64>()
}
fn cluster_count(&self) -> ClusterCount {
ClusterCount::from_byte_size(self.byte_size() as u64, self.cluster_bits())
}
async fn load<S: Storage>(
image: &S,
header: &Header,
cluster: HostCluster,
entries: usize,
) -> io::Result<Self> {
let offset = cluster.offset(header.cluster_bits());
check_table(
Self::NAME,
offset.0,
entries,
size_of::<u64>(),
Self::MAX_ENTRIES,
header.cluster_size(),
)?;
let byte_size = entries * size_of::<u64>();
let mut buffer = IoBuffer::new(byte_size, cmp::max(image.mem_align(), size_of::<u64>()))?;
image.read(&mut buffer, offset.0).await?;
let raw_table = unsafe { buffer.as_ref().into_typed_slice::<u64>() };
let mut table = Vec::<Self::InternalEntry>::with_capacity(entries);
for be_value in raw_table {
table.push(Self::InternalEntry::try_from_plain(
u64::from_be(*be_value),
header,
)?)
}
let mut table = Self::from_data(table.into_boxed_slice(), header);
table.set_cluster(cluster);
table.clear_modified();
Ok(table)
}
async fn write<S: Storage>(&self, image: &S) -> io::Result<()> {
let offset = self
.get_offset()
.ok_or_else(|| io::Error::other("Cannot write qcow2 metadata table, no offset set"))?;
check_table(
Self::NAME,
offset.0,
self.entries(),
size_of::<u64>(),
Self::MAX_ENTRIES,
1 << self.cluster_bits(),
)?;
let byte_size = self.byte_size();
let mut buffer = IoBuffer::new(byte_size, cmp::max(image.mem_align(), size_of::<u64>()))?;
self.clear_modified();
let raw_table = unsafe { buffer.as_mut().into_typed_slice::<u64>() };
for (i, be_value) in raw_table.iter_mut().enumerate() {
*be_value = self.get_ref(i).map(|e| e.to_plain()).unwrap_or(0).to_be();
}
if let Err(err) = image.write(&buffer, offset.0).await {
self.set_modified();
return Err(err);
}
Ok(())
}
async fn write_entry<S: Storage>(&self, image: &S, index: usize) -> io::Result<()> {
let byte_size = self.byte_size();
let power_of_two_up_to_byte_size = ((byte_size / 2) + 1).next_power_of_two();
let alignment = cmp::min(
power_of_two_up_to_byte_size,
cmp::max(
cmp::max(image.mem_align(), image.req_align()),
size_of::<u64>(),
),
);
let alignment_in_entries = alignment / size_of::<u64>();
let offset = self
.get_offset()
.ok_or_else(|| io::Error::other("Cannot write qcow2 metadata table, no offset set"))?;
check_table(
Self::NAME,
offset.0,
self.entries(),
size_of::<u64>(),
Self::MAX_ENTRIES,
1 << self.cluster_bits(),
)?;
let mut buffer = IoBuffer::new(alignment, cmp::max(image.mem_align(), size_of::<u64>()))?;
let raw_entries = unsafe { buffer.as_mut().into_typed_slice::<u64>() };
let first_index = (index / alignment_in_entries) * alignment_in_entries;
#[allow(clippy::needless_range_loop)]
for i in 0..alignment_in_entries {
raw_entries[i] = self
.get_ref(first_index + i)
.map(|e| e.to_plain())
.unwrap_or(0)
.to_be();
}
image
.write(&buffer, offset.0 + (first_index * size_of::<u64>()) as u64)
.await
}
}
fn check_table(
name: &str,
offset: u64,
entries: usize,
entry_size: usize,
max_entries: usize,
cluster_size: usize,
) -> io::Result<()> {
if entries > max_entries {
return Err(invalid_data(format!(
"{name} too big: {entries} > {max_entries}",
)));
}
if !offset.is_multiple_of(cluster_size as u64) {
return Err(invalid_data(format!("{name}: Unaligned offset: {offset}")));
}
let byte_size = entries
.checked_mul(entry_size)
.ok_or_else(|| invalid_data(format!("{name} size overflow: {entries} * {entry_size}")))?;
let end_offset = offset
.checked_add(byte_size as u64)
.ok_or_else(|| invalid_data(format!("{name} offset overflow: {offset} + {byte_size}")))?;
if end_offset > MAX_FILE_LENGTH {
return Err(invalid_data(format!(
"{name}: Invalid end offset: {end_offset} > {MAX_FILE_LENGTH}"
)));
}
Ok(())
}
fn encoded_size<E: Encode>(val: E) -> io::Result<usize> {
let mut length = bincode::enc::write::SizeWriter::default();
bincode::encode_into_writer(val, &mut length, BINCODE_CFG)
.map_err(|err| invalid_data(err.to_string()))?;
Ok(length.bytes_written)
}
fn encode_binary<E: Encode>(val: &E) -> io::Result<Vec<u8>> {
let mut vec = vec![0; encoded_size(val)?];
bincode::encode_into_slice(val, &mut vec, BINCODE_CFG)
.map_err(|err| invalid_data(err.to_string()))?;
Ok(vec)
}
fn decode_binary<D: Decode<()>>(slice: &[u8]) -> io::Result<D> {
bincode::decode_from_slice(slice, BINCODE_CFG)
.map(|(result, _)| result)
.map_err(|err| invalid_data(err.to_string()))
}