mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
block: qcow: Extract rebuild_refcounts() function
Pull rebuild_refcounts out of QcowFile so QcowFile can be removed in a follow up commit. This function is still required by parse_qcow(). Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Rob Bradford <rbradford@meta.com>
This commit is contained in:
@@ -545,7 +545,7 @@ pub(crate) fn parse_qcow(
|
|||||||
|
|
||||||
// Skip refcount rebuilding for readonly files.
|
// Skip refcount rebuilding for readonly files.
|
||||||
if refcount_rebuild_required && is_writable {
|
if refcount_rebuild_required && is_writable {
|
||||||
QcowFile::rebuild_refcounts(&mut raw_file, header.clone())?;
|
rebuild_refcounts(&mut raw_file, header.clone())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let entries_per_cluster = cluster_size / size_of::<u64>() as u64;
|
let entries_per_cluster = cluster_size / size_of::<u64>() as u64;
|
||||||
@@ -1055,326 +1055,6 @@ impl QcowFile {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rebuild the reference count tables.
|
|
||||||
fn rebuild_refcounts(raw_file: &mut QcowRawFile, header: QcowHeader) -> BlockResult<()> {
|
|
||||||
fn add_ref(
|
|
||||||
refcounts: &mut [u64],
|
|
||||||
cluster_size: u64,
|
|
||||||
cluster_address: u64,
|
|
||||||
max_refcount: u64,
|
|
||||||
refcount_bits: u64,
|
|
||||||
) -> Result<()> {
|
|
||||||
let idx = (cluster_address / cluster_size) as usize;
|
|
||||||
if idx >= refcounts.len() {
|
|
||||||
return Err(Error::InvalidClusterIndex);
|
|
||||||
}
|
|
||||||
if refcounts[idx] >= max_refcount {
|
|
||||||
return Err(Error::RefcountOverflow(refcount::Error::RefcountOverflow {
|
|
||||||
value: refcounts[idx] + 1,
|
|
||||||
max: max_refcount,
|
|
||||||
refcount_bits,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
refcounts[idx] += 1;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a reference to the first cluster (header plus extensions).
|
|
||||||
fn set_header_refcount(
|
|
||||||
refcounts: &mut [u64],
|
|
||||||
cluster_size: u64,
|
|
||||||
max_refcount: u64,
|
|
||||||
refcount_bits: u64,
|
|
||||||
) -> Result<()> {
|
|
||||||
add_ref(refcounts, cluster_size, 0, max_refcount, refcount_bits)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add references to the L1 table clusters.
|
|
||||||
fn set_l1_refcounts(
|
|
||||||
refcounts: &mut [u64],
|
|
||||||
header: &QcowHeader,
|
|
||||||
cluster_size: u64,
|
|
||||||
max_refcount: u64,
|
|
||||||
refcount_bits: u64,
|
|
||||||
) -> Result<()> {
|
|
||||||
let entries_per_cluster = cluster_size / size_of::<u64>() as u64;
|
|
||||||
let l1_clusters = div_round_up_u64(u64::from(header.l1_size), entries_per_cluster);
|
|
||||||
let l1_table_offset = header.l1_table_offset;
|
|
||||||
for i in 0..l1_clusters {
|
|
||||||
add_ref(
|
|
||||||
refcounts,
|
|
||||||
cluster_size,
|
|
||||||
l1_table_offset + i * cluster_size,
|
|
||||||
max_refcount,
|
|
||||||
refcount_bits,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Traverse the L1 and L2 tables to find all reachable data clusters.
|
|
||||||
fn set_data_refcounts(
|
|
||||||
refcounts: &mut [u64],
|
|
||||||
header: &QcowHeader,
|
|
||||||
cluster_size: u64,
|
|
||||||
raw_file: &mut QcowRawFile,
|
|
||||||
max_refcount: u64,
|
|
||||||
refcount_bits: u64,
|
|
||||||
) -> Result<()> {
|
|
||||||
let l1_table = raw_file
|
|
||||||
.read_pointer_table(
|
|
||||||
header.l1_table_offset,
|
|
||||||
u64::from(header.l1_size),
|
|
||||||
Some(L1_TABLE_OFFSET_MASK),
|
|
||||||
)
|
|
||||||
.map_err(Error::ReadingPointers)?;
|
|
||||||
for l1_index in 0..header.l1_size as usize {
|
|
||||||
let l2_addr_disk = *l1_table.get(l1_index).ok_or(Error::InvalidIndex)?;
|
|
||||||
if l2_addr_disk != 0 {
|
|
||||||
// Add a reference to the L2 table cluster itself.
|
|
||||||
add_ref(
|
|
||||||
refcounts,
|
|
||||||
cluster_size,
|
|
||||||
l2_addr_disk,
|
|
||||||
max_refcount,
|
|
||||||
refcount_bits,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Read the L2 table and find all referenced data clusters.
|
|
||||||
let l2_table = raw_file
|
|
||||||
.read_pointer_table(
|
|
||||||
l2_addr_disk,
|
|
||||||
cluster_size / size_of::<u64>() as u64,
|
|
||||||
Some(L2_TABLE_OFFSET_MASK),
|
|
||||||
)
|
|
||||||
.map_err(Error::ReadingPointers)?;
|
|
||||||
for data_cluster_addr in l2_table {
|
|
||||||
if data_cluster_addr != 0 {
|
|
||||||
add_ref(
|
|
||||||
refcounts,
|
|
||||||
cluster_size,
|
|
||||||
data_cluster_addr,
|
|
||||||
max_refcount,
|
|
||||||
refcount_bits,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add references to the top-level refcount table clusters.
|
|
||||||
fn set_refcount_table_refcounts(
|
|
||||||
refcounts: &mut [u64],
|
|
||||||
header: &QcowHeader,
|
|
||||||
cluster_size: u64,
|
|
||||||
max_refcount: u64,
|
|
||||||
refcount_bits: u64,
|
|
||||||
) -> Result<()> {
|
|
||||||
let refcount_table_offset = header.refcount_table_offset;
|
|
||||||
for i in 0..u64::from(header.refcount_table_clusters) {
|
|
||||||
add_ref(
|
|
||||||
refcounts,
|
|
||||||
cluster_size,
|
|
||||||
refcount_table_offset + i * cluster_size,
|
|
||||||
max_refcount,
|
|
||||||
refcount_bits,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate clusters for refblocks.
|
|
||||||
// This needs to be done last so that we have the correct refcounts for all other
|
|
||||||
// clusters.
|
|
||||||
fn alloc_refblocks(
|
|
||||||
refcounts: &mut [u64],
|
|
||||||
cluster_size: u64,
|
|
||||||
refblock_clusters: u64,
|
|
||||||
max_refcount: u64,
|
|
||||||
refcount_bits: u64,
|
|
||||||
) -> Result<Vec<u64>> {
|
|
||||||
let mut ref_table = vec![0; refblock_clusters as usize];
|
|
||||||
let mut first_free_cluster: u64 = 0;
|
|
||||||
for refblock_addr in &mut ref_table {
|
|
||||||
loop {
|
|
||||||
if first_free_cluster >= refcounts.len() as u64 {
|
|
||||||
return Err(Error::NotEnoughSpaceForRefcounts);
|
|
||||||
}
|
|
||||||
if refcounts[first_free_cluster as usize] == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
first_free_cluster += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
*refblock_addr = first_free_cluster * cluster_size;
|
|
||||||
add_ref(
|
|
||||||
refcounts,
|
|
||||||
cluster_size,
|
|
||||||
*refblock_addr,
|
|
||||||
max_refcount,
|
|
||||||
refcount_bits,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
first_free_cluster += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ref_table)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the updated reference count blocks and reftable.
|
|
||||||
fn write_refblocks(
|
|
||||||
refcounts: &[u64],
|
|
||||||
mut header: QcowHeader,
|
|
||||||
ref_table: &[u64],
|
|
||||||
raw_file: &mut QcowRawFile,
|
|
||||||
refcount_block_entries: u64,
|
|
||||||
) -> Result<()> {
|
|
||||||
// Rewrite the header with lazy refcounts enabled while we are rebuilding the tables.
|
|
||||||
header.compatible_features |= COMPATIBLE_FEATURES_LAZY_REFCOUNTS;
|
|
||||||
raw_file.file_mut().rewind().map_err(Error::SeekingFile)?;
|
|
||||||
header.write_to(raw_file.file_mut())?;
|
|
||||||
|
|
||||||
for (i, refblock_addr) in ref_table.iter().enumerate() {
|
|
||||||
// Write a block of refcounts to the location indicated by refblock_addr.
|
|
||||||
let refblock_start = i * (refcount_block_entries as usize);
|
|
||||||
let refblock_end = min(
|
|
||||||
refcounts.len(),
|
|
||||||
refblock_start + refcount_block_entries as usize,
|
|
||||||
);
|
|
||||||
let refblock = &refcounts[refblock_start..refblock_end];
|
|
||||||
raw_file
|
|
||||||
.write_refcount_block(*refblock_addr, refblock)
|
|
||||||
.map_err(Error::WritingHeader)?;
|
|
||||||
|
|
||||||
// If this is the last (partial) cluster, pad it out to a full refblock cluster.
|
|
||||||
if refblock.len() < refcount_block_entries as usize {
|
|
||||||
let refblock_padding =
|
|
||||||
vec![0u64; refcount_block_entries as usize - refblock.len()];
|
|
||||||
let byte_offset =
|
|
||||||
refblock.len() as u64 * raw_file.cluster_size() / refcount_block_entries;
|
|
||||||
raw_file
|
|
||||||
.write_refcount_block(*refblock_addr + byte_offset, &refblock_padding)
|
|
||||||
.map_err(Error::WritingHeader)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rewrite the top-level refcount table.
|
|
||||||
raw_file
|
|
||||||
.write_pointer_table_direct(header.refcount_table_offset, ref_table.iter())
|
|
||||||
.map_err(Error::WritingHeader)?;
|
|
||||||
|
|
||||||
// Rewrite the header again, now with lazy refcounts disabled.
|
|
||||||
header.compatible_features &= !COMPATIBLE_FEATURES_LAZY_REFCOUNTS;
|
|
||||||
raw_file.file_mut().rewind().map_err(Error::SeekingFile)?;
|
|
||||||
header.write_to(raw_file.file_mut())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
let cluster_size = raw_file.cluster_size();
|
|
||||||
|
|
||||||
let file_size = raw_file
|
|
||||||
.file_mut()
|
|
||||||
.metadata()
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingFileSize(e)))?
|
|
||||||
.len();
|
|
||||||
|
|
||||||
let refcount_bits = 1u64 << header.refcount_order;
|
|
||||||
let max_refcount = if refcount_bits == 64 {
|
|
||||||
u64::MAX
|
|
||||||
} else {
|
|
||||||
(1u64 << refcount_bits) - 1
|
|
||||||
};
|
|
||||||
let refcount_block_entries = cluster_size * 8 / refcount_bits;
|
|
||||||
let pointers_per_cluster = cluster_size / size_of::<u64>() as u64;
|
|
||||||
let data_clusters = div_round_up_u64(header.size, cluster_size);
|
|
||||||
let l2_clusters = div_round_up_u64(data_clusters, pointers_per_cluster);
|
|
||||||
let l1_clusters = div_round_up_u64(l2_clusters, pointers_per_cluster);
|
|
||||||
let header_clusters = div_round_up_u64(size_of::<QcowHeader>() as u64, cluster_size);
|
|
||||||
let max_clusters = data_clusters + l2_clusters + l1_clusters + header_clusters;
|
|
||||||
let mut max_valid_cluster_index = max_clusters;
|
|
||||||
let refblock_clusters = div_round_up_u64(max_valid_cluster_index, refcount_block_entries);
|
|
||||||
let reftable_clusters = div_round_up_u64(refblock_clusters, pointers_per_cluster);
|
|
||||||
// Account for refblocks and the ref table size needed to address them.
|
|
||||||
let refblocks_for_refs = div_round_up_u64(
|
|
||||||
refblock_clusters + reftable_clusters,
|
|
||||||
refcount_block_entries,
|
|
||||||
);
|
|
||||||
let reftable_clusters_for_refs =
|
|
||||||
div_round_up_u64(refblocks_for_refs, refcount_block_entries);
|
|
||||||
max_valid_cluster_index += refblock_clusters + reftable_clusters;
|
|
||||||
max_valid_cluster_index += refblocks_for_refs + reftable_clusters_for_refs;
|
|
||||||
|
|
||||||
if max_valid_cluster_index > MAX_RAM_POINTER_TABLE_SIZE {
|
|
||||||
return Err(BlockError::new(
|
|
||||||
BlockErrorKind::CorruptImage,
|
|
||||||
Error::InvalidRefcountTableSize(max_valid_cluster_index),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let max_valid_cluster_offset = max_valid_cluster_index * cluster_size;
|
|
||||||
if max_valid_cluster_offset < file_size - cluster_size {
|
|
||||||
return Err(BlockError::new(
|
|
||||||
BlockErrorKind::CorruptImage,
|
|
||||||
Error::InvalidRefcountTableSize(max_valid_cluster_offset),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut refcounts = vec![0; max_valid_cluster_index as usize];
|
|
||||||
|
|
||||||
// Find all references clusters and rebuild refcounts.
|
|
||||||
set_header_refcount(&mut refcounts, cluster_size, max_refcount, refcount_bits)
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
|
||||||
set_l1_refcounts(
|
|
||||||
&mut refcounts,
|
|
||||||
&header,
|
|
||||||
cluster_size,
|
|
||||||
max_refcount,
|
|
||||||
refcount_bits,
|
|
||||||
)
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
|
||||||
set_data_refcounts(
|
|
||||||
&mut refcounts,
|
|
||||||
&header,
|
|
||||||
cluster_size,
|
|
||||||
raw_file,
|
|
||||||
max_refcount,
|
|
||||||
refcount_bits,
|
|
||||||
)
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
|
||||||
set_refcount_table_refcounts(
|
|
||||||
&mut refcounts,
|
|
||||||
&header,
|
|
||||||
cluster_size,
|
|
||||||
max_refcount,
|
|
||||||
refcount_bits,
|
|
||||||
)
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
|
||||||
|
|
||||||
// Allocate clusters to store the new reference count blocks.
|
|
||||||
let ref_table = alloc_refblocks(
|
|
||||||
&mut refcounts,
|
|
||||||
cluster_size,
|
|
||||||
refblock_clusters,
|
|
||||||
max_refcount,
|
|
||||||
refcount_bits,
|
|
||||||
)
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
|
||||||
|
|
||||||
// Write updated reference counts and point the reftable at them.
|
|
||||||
write_refblocks(
|
|
||||||
&refcounts,
|
|
||||||
header,
|
|
||||||
&ref_table,
|
|
||||||
raw_file,
|
|
||||||
refcount_block_entries,
|
|
||||||
)
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limits the range so that it doesn't exceed the virtual size of the file.
|
// Limits the range so that it doesn't exceed the virtual size of the file.
|
||||||
fn limit_range_file(&self, address: u64, count: usize) -> usize {
|
fn limit_range_file(&self, address: u64, count: usize) -> usize {
|
||||||
if address.checked_add(count as u64).is_none() || address > self.virtual_size() {
|
if address.checked_add(count as u64).is_none() || address > self.virtual_size() {
|
||||||
@@ -2095,6 +1775,324 @@ impl QcowFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rebuild the reference count tables.
|
||||||
|
fn rebuild_refcounts(raw_file: &mut QcowRawFile, header: QcowHeader) -> BlockResult<()> {
|
||||||
|
fn add_ref(
|
||||||
|
refcounts: &mut [u64],
|
||||||
|
cluster_size: u64,
|
||||||
|
cluster_address: u64,
|
||||||
|
max_refcount: u64,
|
||||||
|
refcount_bits: u64,
|
||||||
|
) -> Result<()> {
|
||||||
|
let idx = (cluster_address / cluster_size) as usize;
|
||||||
|
if idx >= refcounts.len() {
|
||||||
|
return Err(Error::InvalidClusterIndex);
|
||||||
|
}
|
||||||
|
if refcounts[idx] >= max_refcount {
|
||||||
|
return Err(Error::RefcountOverflow(refcount::Error::RefcountOverflow {
|
||||||
|
value: refcounts[idx] + 1,
|
||||||
|
max: max_refcount,
|
||||||
|
refcount_bits,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
refcounts[idx] += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a reference to the first cluster (header plus extensions).
|
||||||
|
fn set_header_refcount(
|
||||||
|
refcounts: &mut [u64],
|
||||||
|
cluster_size: u64,
|
||||||
|
max_refcount: u64,
|
||||||
|
refcount_bits: u64,
|
||||||
|
) -> Result<()> {
|
||||||
|
add_ref(refcounts, cluster_size, 0, max_refcount, refcount_bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add references to the L1 table clusters.
|
||||||
|
fn set_l1_refcounts(
|
||||||
|
refcounts: &mut [u64],
|
||||||
|
header: &QcowHeader,
|
||||||
|
cluster_size: u64,
|
||||||
|
max_refcount: u64,
|
||||||
|
refcount_bits: u64,
|
||||||
|
) -> Result<()> {
|
||||||
|
let entries_per_cluster = cluster_size / size_of::<u64>() as u64;
|
||||||
|
let l1_clusters = div_round_up_u64(u64::from(header.l1_size), entries_per_cluster);
|
||||||
|
let l1_table_offset = header.l1_table_offset;
|
||||||
|
for i in 0..l1_clusters {
|
||||||
|
add_ref(
|
||||||
|
refcounts,
|
||||||
|
cluster_size,
|
||||||
|
l1_table_offset + i * cluster_size,
|
||||||
|
max_refcount,
|
||||||
|
refcount_bits,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse the L1 and L2 tables to find all reachable data clusters.
|
||||||
|
fn set_data_refcounts(
|
||||||
|
refcounts: &mut [u64],
|
||||||
|
header: &QcowHeader,
|
||||||
|
cluster_size: u64,
|
||||||
|
raw_file: &mut QcowRawFile,
|
||||||
|
max_refcount: u64,
|
||||||
|
refcount_bits: u64,
|
||||||
|
) -> Result<()> {
|
||||||
|
let l1_table = raw_file
|
||||||
|
.read_pointer_table(
|
||||||
|
header.l1_table_offset,
|
||||||
|
u64::from(header.l1_size),
|
||||||
|
Some(L1_TABLE_OFFSET_MASK),
|
||||||
|
)
|
||||||
|
.map_err(Error::ReadingPointers)?;
|
||||||
|
for l1_index in 0..header.l1_size as usize {
|
||||||
|
let l2_addr_disk = *l1_table.get(l1_index).ok_or(Error::InvalidIndex)?;
|
||||||
|
if l2_addr_disk != 0 {
|
||||||
|
// Add a reference to the L2 table cluster itself.
|
||||||
|
add_ref(
|
||||||
|
refcounts,
|
||||||
|
cluster_size,
|
||||||
|
l2_addr_disk,
|
||||||
|
max_refcount,
|
||||||
|
refcount_bits,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Read the L2 table and find all referenced data clusters.
|
||||||
|
let l2_table = raw_file
|
||||||
|
.read_pointer_table(
|
||||||
|
l2_addr_disk,
|
||||||
|
cluster_size / size_of::<u64>() as u64,
|
||||||
|
Some(L2_TABLE_OFFSET_MASK),
|
||||||
|
)
|
||||||
|
.map_err(Error::ReadingPointers)?;
|
||||||
|
for data_cluster_addr in l2_table {
|
||||||
|
if data_cluster_addr != 0 {
|
||||||
|
add_ref(
|
||||||
|
refcounts,
|
||||||
|
cluster_size,
|
||||||
|
data_cluster_addr,
|
||||||
|
max_refcount,
|
||||||
|
refcount_bits,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add references to the top-level refcount table clusters.
|
||||||
|
fn set_refcount_table_refcounts(
|
||||||
|
refcounts: &mut [u64],
|
||||||
|
header: &QcowHeader,
|
||||||
|
cluster_size: u64,
|
||||||
|
max_refcount: u64,
|
||||||
|
refcount_bits: u64,
|
||||||
|
) -> Result<()> {
|
||||||
|
let refcount_table_offset = header.refcount_table_offset;
|
||||||
|
for i in 0..u64::from(header.refcount_table_clusters) {
|
||||||
|
add_ref(
|
||||||
|
refcounts,
|
||||||
|
cluster_size,
|
||||||
|
refcount_table_offset + i * cluster_size,
|
||||||
|
max_refcount,
|
||||||
|
refcount_bits,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate clusters for refblocks.
|
||||||
|
// This needs to be done last so that we have the correct refcounts for all other
|
||||||
|
// clusters.
|
||||||
|
fn alloc_refblocks(
|
||||||
|
refcounts: &mut [u64],
|
||||||
|
cluster_size: u64,
|
||||||
|
refblock_clusters: u64,
|
||||||
|
max_refcount: u64,
|
||||||
|
refcount_bits: u64,
|
||||||
|
) -> Result<Vec<u64>> {
|
||||||
|
let mut ref_table = vec![0; refblock_clusters as usize];
|
||||||
|
let mut first_free_cluster: u64 = 0;
|
||||||
|
for refblock_addr in &mut ref_table {
|
||||||
|
loop {
|
||||||
|
if first_free_cluster >= refcounts.len() as u64 {
|
||||||
|
return Err(Error::NotEnoughSpaceForRefcounts);
|
||||||
|
}
|
||||||
|
if refcounts[first_free_cluster as usize] == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
first_free_cluster += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*refblock_addr = first_free_cluster * cluster_size;
|
||||||
|
add_ref(
|
||||||
|
refcounts,
|
||||||
|
cluster_size,
|
||||||
|
*refblock_addr,
|
||||||
|
max_refcount,
|
||||||
|
refcount_bits,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
first_free_cluster += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ref_table)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the updated reference count blocks and reftable.
|
||||||
|
fn write_refblocks(
|
||||||
|
refcounts: &[u64],
|
||||||
|
mut header: QcowHeader,
|
||||||
|
ref_table: &[u64],
|
||||||
|
raw_file: &mut QcowRawFile,
|
||||||
|
refcount_block_entries: u64,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Rewrite the header with lazy refcounts enabled while we are rebuilding the tables.
|
||||||
|
header.compatible_features |= COMPATIBLE_FEATURES_LAZY_REFCOUNTS;
|
||||||
|
raw_file.file_mut().rewind().map_err(Error::SeekingFile)?;
|
||||||
|
header.write_to(raw_file.file_mut())?;
|
||||||
|
|
||||||
|
for (i, refblock_addr) in ref_table.iter().enumerate() {
|
||||||
|
// Write a block of refcounts to the location indicated by refblock_addr.
|
||||||
|
let refblock_start = i * (refcount_block_entries as usize);
|
||||||
|
let refblock_end = min(
|
||||||
|
refcounts.len(),
|
||||||
|
refblock_start + refcount_block_entries as usize,
|
||||||
|
);
|
||||||
|
let refblock = &refcounts[refblock_start..refblock_end];
|
||||||
|
raw_file
|
||||||
|
.write_refcount_block(*refblock_addr, refblock)
|
||||||
|
.map_err(Error::WritingHeader)?;
|
||||||
|
|
||||||
|
// If this is the last (partial) cluster, pad it out to a full refblock cluster.
|
||||||
|
if refblock.len() < refcount_block_entries as usize {
|
||||||
|
let refblock_padding = vec![0u64; refcount_block_entries as usize - refblock.len()];
|
||||||
|
let byte_offset =
|
||||||
|
refblock.len() as u64 * raw_file.cluster_size() / refcount_block_entries;
|
||||||
|
raw_file
|
||||||
|
.write_refcount_block(*refblock_addr + byte_offset, &refblock_padding)
|
||||||
|
.map_err(Error::WritingHeader)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrite the top-level refcount table.
|
||||||
|
raw_file
|
||||||
|
.write_pointer_table_direct(header.refcount_table_offset, ref_table.iter())
|
||||||
|
.map_err(Error::WritingHeader)?;
|
||||||
|
|
||||||
|
// Rewrite the header again, now with lazy refcounts disabled.
|
||||||
|
header.compatible_features &= !COMPATIBLE_FEATURES_LAZY_REFCOUNTS;
|
||||||
|
raw_file.file_mut().rewind().map_err(Error::SeekingFile)?;
|
||||||
|
header.write_to(raw_file.file_mut())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let cluster_size = raw_file.cluster_size();
|
||||||
|
|
||||||
|
let file_size = raw_file
|
||||||
|
.file_mut()
|
||||||
|
.metadata()
|
||||||
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingFileSize(e)))?
|
||||||
|
.len();
|
||||||
|
|
||||||
|
let refcount_bits = 1u64 << header.refcount_order;
|
||||||
|
let max_refcount = if refcount_bits == 64 {
|
||||||
|
u64::MAX
|
||||||
|
} else {
|
||||||
|
(1u64 << refcount_bits) - 1
|
||||||
|
};
|
||||||
|
let refcount_block_entries = cluster_size * 8 / refcount_bits;
|
||||||
|
let pointers_per_cluster = cluster_size / size_of::<u64>() as u64;
|
||||||
|
let data_clusters = div_round_up_u64(header.size, cluster_size);
|
||||||
|
let l2_clusters = div_round_up_u64(data_clusters, pointers_per_cluster);
|
||||||
|
let l1_clusters = div_round_up_u64(l2_clusters, pointers_per_cluster);
|
||||||
|
let header_clusters = div_round_up_u64(size_of::<QcowHeader>() as u64, cluster_size);
|
||||||
|
let max_clusters = data_clusters + l2_clusters + l1_clusters + header_clusters;
|
||||||
|
let mut max_valid_cluster_index = max_clusters;
|
||||||
|
let refblock_clusters = div_round_up_u64(max_valid_cluster_index, refcount_block_entries);
|
||||||
|
let reftable_clusters = div_round_up_u64(refblock_clusters, pointers_per_cluster);
|
||||||
|
// Account for refblocks and the ref table size needed to address them.
|
||||||
|
let refblocks_for_refs = div_round_up_u64(
|
||||||
|
refblock_clusters + reftable_clusters,
|
||||||
|
refcount_block_entries,
|
||||||
|
);
|
||||||
|
let reftable_clusters_for_refs = div_round_up_u64(refblocks_for_refs, refcount_block_entries);
|
||||||
|
max_valid_cluster_index += refblock_clusters + reftable_clusters;
|
||||||
|
max_valid_cluster_index += refblocks_for_refs + reftable_clusters_for_refs;
|
||||||
|
|
||||||
|
if max_valid_cluster_index > MAX_RAM_POINTER_TABLE_SIZE {
|
||||||
|
return Err(BlockError::new(
|
||||||
|
BlockErrorKind::CorruptImage,
|
||||||
|
Error::InvalidRefcountTableSize(max_valid_cluster_index),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let max_valid_cluster_offset = max_valid_cluster_index * cluster_size;
|
||||||
|
if max_valid_cluster_offset < file_size - cluster_size {
|
||||||
|
return Err(BlockError::new(
|
||||||
|
BlockErrorKind::CorruptImage,
|
||||||
|
Error::InvalidRefcountTableSize(max_valid_cluster_offset),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut refcounts = vec![0; max_valid_cluster_index as usize];
|
||||||
|
|
||||||
|
// Find all references clusters and rebuild refcounts.
|
||||||
|
set_header_refcount(&mut refcounts, cluster_size, max_refcount, refcount_bits)
|
||||||
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
||||||
|
set_l1_refcounts(
|
||||||
|
&mut refcounts,
|
||||||
|
&header,
|
||||||
|
cluster_size,
|
||||||
|
max_refcount,
|
||||||
|
refcount_bits,
|
||||||
|
)
|
||||||
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
||||||
|
set_data_refcounts(
|
||||||
|
&mut refcounts,
|
||||||
|
&header,
|
||||||
|
cluster_size,
|
||||||
|
raw_file,
|
||||||
|
max_refcount,
|
||||||
|
refcount_bits,
|
||||||
|
)
|
||||||
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
||||||
|
set_refcount_table_refcounts(
|
||||||
|
&mut refcounts,
|
||||||
|
&header,
|
||||||
|
cluster_size,
|
||||||
|
max_refcount,
|
||||||
|
refcount_bits,
|
||||||
|
)
|
||||||
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
||||||
|
|
||||||
|
// Allocate clusters to store the new reference count blocks.
|
||||||
|
let ref_table = alloc_refblocks(
|
||||||
|
&mut refcounts,
|
||||||
|
cluster_size,
|
||||||
|
refblock_clusters,
|
||||||
|
max_refcount,
|
||||||
|
refcount_bits,
|
||||||
|
)
|
||||||
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
||||||
|
|
||||||
|
// Write updated reference counts and point the reftable at them.
|
||||||
|
write_refblocks(
|
||||||
|
&refcounts,
|
||||||
|
header,
|
||||||
|
&ref_table,
|
||||||
|
raw_file,
|
||||||
|
refcount_block_entries,
|
||||||
|
)
|
||||||
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))
|
||||||
|
}
|
||||||
|
|
||||||
impl AsRawFd for QcowFile {
|
impl AsRawFd for QcowFile {
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
self.raw_file.as_raw_fd()
|
self.raw_file.as_raw_fd()
|
||||||
|
|||||||
Reference in New Issue
Block a user