diff --git a/block/src/formats/qcow/internal/backing.rs b/block/src/formats/qcow/internal/backing.rs index c680fc0fd..df248ab18 100644 --- a/block/src/formats/qcow/internal/backing.rs +++ b/block/src/formats/qcow/internal/backing.rs @@ -176,9 +176,5 @@ pub fn shared_backing_from(bf: BackingFile) -> BlockResult> backing_file: backing.map(|bf| shared_backing_from(*bf)).transpose()?, })) } - #[cfg(test)] - BackingKind::QcowFile(_) => { - unreachable!("QcowFile variant is only used by set_backing_file() in tests") - } } } diff --git a/block/src/formats/qcow/internal/metadata.rs b/block/src/formats/qcow/internal/metadata.rs index e58de6ab5..4ad3a1bfc 100644 --- a/block/src/formats/qcow/internal/metadata.rs +++ b/block/src/formats/qcow/internal/metadata.rs @@ -520,19 +520,6 @@ impl QcowState { } } - /// Maps a single cluster region for a sequential read. - pub(crate) fn map_cluster_read( - &mut self, - address: u64, - count: usize, - has_backing_file: bool, - ) -> io::Result { - match self.try_map_read(address, count, has_backing_file)? { - Some(mapping) => Ok(mapping), - None => self.map_read_with_populate(address, count, has_backing_file), - } - } - /// Write path mapping. Always called under write lock. fn map_write( &mut self, diff --git a/block/src/formats/qcow/internal/mod.rs b/block/src/formats/qcow/internal/mod.rs index 0152de5d6..0f8d76fc4 100644 --- a/block/src/formats/qcow/internal/mod.rs +++ b/block/src/formats/qcow/internal/mod.rs @@ -17,17 +17,12 @@ mod vec_cache; use std::cmp::{max, min}; use std::fmt::{Debug, Formatter, Result as FmtResult}; use std::fs::{OpenOptions, read_link}; -use std::io::{self, Read, Seek, SeekFrom, Write}; +use std::io::{self, Seek, SeekFrom}; use std::mem::size_of; -use std::os::fd::{AsRawFd, RawFd}; +use std::os::fd::AsRawFd; use std::path::Path; use std::str; -#[cfg(test)] -use header::{ - AUTOCLEAR_FEATURES_OFFSET, DEFAULT_REFCOUNT_ORDER, HEADER_EXT_BACKING_FORMAT, HEADER_EXT_END, - V2_BARE_HEADER_SIZE, V3_BARE_HEADER_SIZE, -}; pub use header::{ BackingFileConfig, CompressionType, ImageType, IncompatFeatures, MissingFeatureError, QcowHeader, @@ -37,28 +32,16 @@ use header::{ MAX_RAM_POINTER_TABLE_SIZE, MIN_CLUSTER_BITS, QCOW_MAGIC, max_refcount_clusters, offset_is_cluster_boundary, }; -use libc::{EINVAL, EIO, ENOSPC}; -use log::{error, warn}; -use metadata::ClusterReadMapping; +use log::warn; use qcow_raw_file::{BeUint, QcowRawFile}; pub use raw_file::RawFile; use refcount::RefCount; use remain::sorted; use thiserror::Error; pub(crate) use util::MAX_NESTING_DEPTH; -use util::{ - L1_TABLE_OFFSET_MASK, L2_TABLE_OFFSET_MASK, div_round_up_u32, div_round_up_u64, l1_entry_make, - l2_entry_compressed_cluster_layout, l2_entry_is_compressed, l2_entry_is_empty, - l2_entry_is_zero, l2_entry_make_std, l2_entry_make_zero, l2_entry_make_zero_plain, - l2_entry_std_cluster_addr, -}; -use vec_cache::{CacheMap, Cacheable, VecCache}; -use vmm_sys_util::file_traits::{FileSetLen, FileSync}; -use vmm_sys_util::seek_hole::SeekHole; -use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; +use util::{L1_TABLE_OFFSET_MASK, L2_TABLE_OFFSET_MASK, div_round_up_u32, div_round_up_u64}; +use vec_cache::{CacheMap, VecCache}; -use super::common::decompress_cluster; -use crate::BlockBackend; use crate::error::{BlockError, BlockErrorKind, BlockResult}; #[sorted] @@ -187,9 +170,6 @@ pub(crate) enum BackingKind { inner: Box, backing: Option>, }, - /// Full QcowFile used as backing, only in tests. - #[cfg(test)] - QcowFile(Box), } /// Backing file wrapper pub(crate) struct BackingFile { @@ -281,98 +261,6 @@ impl BackingFile { pub(crate) fn into_kind(self) -> (BackingKind, u64) { (self.kind, self.virtual_size) } - - /// Read from backing file, returning zeros for any portion beyond backing file size. - #[inline] - pub(crate) fn read_at(&mut self, address: u64, buf: &mut [u8]) -> std::io::Result<()> { - if address >= self.virtual_size { - buf.fill(0); - return Ok(()); - } - - let available = (self.virtual_size - address) as usize; - let (target, overflow) = if available >= buf.len() { - (buf, &mut [][..]) - } else { - buf.split_at_mut(available) - }; - Self::read_at_inner(&mut self.kind, address, target)?; - overflow.fill(0); - Ok(()) - } - - fn read_at_inner(kind: &mut BackingKind, address: u64, buf: &mut [u8]) -> std::io::Result<()> { - match kind { - BackingKind::Raw(file) => { - file.seek(SeekFrom::Start(address))?; - file.read_exact(buf) - } - #[cfg(test)] - BackingKind::QcowFile(qcow) => { - qcow.seek(SeekFrom::Start(address))?; - qcow.read_exact(buf) - } - BackingKind::Qcow { inner, backing } => { - let has_backing = backing.is_some(); - let cluster_size = inner.raw_file.cluster_size(); - let mut pos = 0usize; - while pos < buf.len() { - let curr_addr = address + pos as u64; - let intra = inner.raw_file.cluster_offset(curr_addr) as usize; - let count = min(buf.len() - pos, cluster_size as usize - intra); - let mapping = inner.map_cluster_read(curr_addr, count, has_backing)?; - match mapping { - ClusterReadMapping::Zero { length } => { - buf[pos..pos + length as usize].fill(0); - } - ClusterReadMapping::Allocated { - offset: host_off, - length, - } => { - inner.raw_file.file_mut().seek(SeekFrom::Start(host_off))?; - inner - .raw_file - .file_mut() - .read_exact(&mut buf[pos..pos + length as usize])?; - } - ClusterReadMapping::Compressed { - host_offset, - compressed_size, - cluster_offset, - length, - } => { - let mut compressed = vec![0u8; compressed_size]; - inner - .raw_file - .file_mut() - .seek(SeekFrom::Start(host_offset))?; - inner.raw_file.file_mut().read_exact(&mut compressed)?; - let decompressed = decompress_cluster( - &compressed, - cluster_size as usize, - &*inner.header.get_decoder(), - )?; - buf[pos..pos + length].copy_from_slice( - &decompressed[cluster_offset..cluster_offset + length], - ); - } - ClusterReadMapping::Backing { - offset: backing_off, - length, - } => { - if let Some(bf) = backing.as_mut() { - bf.read_at(backing_off, &mut buf[pos..pos + length as usize])?; - } else { - buf[pos..pos + length as usize].fill(0); - } - } - } - pos += count; - } - Ok(()) - } - } - } } impl Debug for BackingFile { @@ -384,7 +272,7 @@ impl Debug for BackingFile { /// Parses and validates a QCOW2 image file, returning the metadata, backing /// file and sparse flag. /// -/// Used by [`QcowFile`] and [`QcowDisk`] constructors. +/// Used by [`crate::formats::qcow::QcowDisk`] when opening an image. pub(crate) fn parse_qcow( mut file: RawFile, max_nesting_depth: u32, @@ -668,1114 +556,6 @@ pub(crate) fn parse_qcow( Ok((inner, backing_file, sparse)) } -/// Represents a qcow2 file. This is a sparse file format maintained by the qemu project. -/// Full documentation of the format can be found in the qemu repository. -/// -/// # Example -/// -/// ``` -/// # use block::formats::qcow::internal::{QcowFile, RawFile}; -/// # use std::io::{Read, Seek, SeekFrom}; -/// # fn test(file: std::fs::File) -> std::io::Result<()> { -/// let mut raw_img = RawFile::new(file, false); -/// let mut q = QcowFile::from(raw_img).expect("Can't open qcow file"); -/// let mut buf = [0u8; 12]; -/// q.seek(SeekFrom::Start(10 as u64))?; -/// q.read(&mut buf[..])?; -/// # Ok(()) -/// # } -/// ``` -#[derive(Debug)] -pub struct QcowFile { - raw_file: QcowRawFile, - header: QcowHeader, - l1_table: VecCache, - l2_entries: u64, - l2_cache: CacheMap>, - refcounts: RefCount, - current_offset: u64, - unref_clusters: Vec, // List of freshly unreferenced clusters. - // List of unreferenced clusters available to be used. unref clusters become available once the - // removal of references to them have been synced to disk. - avail_clusters: Vec, - backing_file: Option, - sparse: bool, -} - -impl QcowFile { - /// Creates a QcowFile from `file`. File must be a valid qcow2 image. - /// - /// Additionally, max nesting depth of this qcow2 image will be set to default value 10. - pub fn from(file: RawFile) -> BlockResult { - Self::from_with_nesting_depth(file, MAX_NESTING_DEPTH, true) - } - - /// Creates a QcowFile from `file` and with a max nesting depth. File must be a valid qcow2 - /// image. - pub fn from_with_nesting_depth( - file: RawFile, - max_nesting_depth: u32, - sparse: bool, - ) -> BlockResult { - let (inner, backing_file, sparse) = parse_qcow(file, max_nesting_depth, sparse)?; - let metadata::QcowState { - raw_file, - header, - l1_table, - l2_entries, - l2_cache, - refcounts, - avail_clusters, - unref_clusters, - } = inner; - Ok(QcowFile { - raw_file, - header, - l1_table, - l2_entries, - l2_cache, - refcounts, - current_offset: 0, - unref_clusters, - avail_clusters, - backing_file, - sparse, - }) - } - - /// Creates a new QcowFile at the given path. - pub fn new( - file: RawFile, - version: u32, - virtual_size: u64, - sparse: bool, - ) -> BlockResult { - let header = - QcowHeader::create_for_size_and_path(version, virtual_size, None).map_err(|e| { - let kind = match &e { - Error::BackingFileTooLong(_) => BlockErrorKind::InvalidFormat, - _ => BlockErrorKind::Io, - }; - BlockError::new(kind, e) - })?; - QcowFile::new_from_header(file, &header, sparse) - } - - /// Creates a new QcowFile at the given path with a backing file. - pub fn new_from_backing( - file: RawFile, - version: u32, - backing_file_size: u64, - backing_config: &BackingFileConfig, - sparse: bool, - ) -> BlockResult { - let mut header = QcowHeader::create_for_size_and_path( - version, - backing_file_size, - Some(&backing_config.path), - ) - .map_err(|e| { - let kind = match &e { - Error::BackingFileTooLong(_) => BlockErrorKind::InvalidFormat, - _ => BlockErrorKind::Io, - }; - BlockError::new(kind, e) - })?; - if let Some(backing_file) = &mut header.backing_file { - backing_file.format = backing_config.format; - } - QcowFile::new_from_header(file, &header, sparse) - // backing_file is loaded by new_from_header -> Self::from() based on the header - } - - fn new_from_header( - mut file: RawFile, - header: &QcowHeader, - sparse: bool, - ) -> BlockResult { - file.rewind() - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; - header - .write_to(&mut file) - .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; - - let mut qcow = Self::from_with_nesting_depth(file, MAX_NESTING_DEPTH, sparse)?; - - // Set the refcount for each refcount table cluster. - let cluster_size = 0x01u64 << qcow.header.cluster_bits; - let refcount_table_base = qcow.header.refcount_table_offset; - let end_cluster_addr = - refcount_table_base + u64::from(qcow.header.refcount_table_clusters) * cluster_size; - - let mut cluster_addr = 0; - while cluster_addr < end_cluster_addr { - let mut unref_clusters = qcow.set_cluster_refcount(cluster_addr, 1).map_err(|e| { - BlockError::new(BlockErrorKind::Io, Error::SettingRefcountRefcount(e)) - })?; - qcow.unref_clusters.append(&mut unref_clusters); - cluster_addr += cluster_size; - } - - Ok(qcow) - } - - #[cfg(test)] - pub fn set_backing_file(&mut self, backing: Option>) { - self.backing_file = backing.map(|b| { - let virtual_size = b.virtual_size(); - BackingFile { - kind: BackingKind::QcowFile(b), - virtual_size, - } - }); - } - - /// Returns the `QcowHeader` for this file. - pub fn header(&self) -> &QcowHeader { - &self.header - } - - /// Returns the L1 lookup table for this file. This is only useful for debugging. - pub fn l1_table(&self) -> &[u64] { - self.l1_table.get_values() - } - - /// Returns an L2_table of cluster addresses, only used for debugging. - pub fn l2_table(&mut self, l1_index: usize) -> BlockResult> { - let l2_addr_disk = *self - .l1_table - .get(l1_index) - .ok_or_else(|| BlockError::new(BlockErrorKind::OutOfBounds, Error::InvalidIndex))?; - - if l2_addr_disk == 0 { - // Reading from an unallocated cluster will return zeros. - return Ok(None); - } - - if !self.l2_cache.contains_key(l1_index) { - // Not in the cache. - let table = VecCache::from_vec( - Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingPointers(e)))?, - ); - let l1_table = &self.l1_table; - let raw_file = &mut self.raw_file; - self.l2_cache - .insert(l1_index, table, |index, evicted| { - raw_file.write_pointer_table_direct(l1_table[index], evicted.iter()) - }) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::EvictingCache(e)))?; - } - - // The index must exist as it was just inserted if it didn't already. - Ok(Some(self.l2_cache.get(l1_index).unwrap().get_values())) - } - - /// Returns the refcount table for this file. This is only useful for debugging. - pub fn ref_table(&self) -> &[u64] { - self.refcounts.ref_table() - } - - /// Returns the `index`th refcount block from the file. - pub fn refcount_block(&mut self, index: usize) -> BlockResult> { - self.refcounts - .refcount_block(&mut self.raw_file, index) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingRefCountBlock(e))) - } - - /// Returns the first cluster in the file with a 0 refcount. Used for testing. - pub fn first_zero_refcount(&mut self) -> BlockResult> { - let file_size = self - .raw_file - .file_mut() - .metadata() - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingFileSize(e)))? - .len(); - let cluster_size = 0x01u64 << self.header.cluster_bits; - - let mut cluster_addr = 0; - while cluster_addr < file_size { - let cluster_refcount = self - .refcounts - .get_cluster_refcount(&mut self.raw_file, cluster_addr) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingRefcount(e)))?; - if cluster_refcount == 0 { - return Ok(Some(cluster_addr)); - } - cluster_addr += cluster_size; - } - Ok(None) - } - - /// Resize the virtual size of the QCOW2 image. - /// - /// This supports growing the image, including growing the L1 table - /// if needed. Shrinking is not supported, as it could lead to data - /// loss. Not supported when a backing file is present in that case - /// an error is returned. - pub fn resize(&mut self, new_size: u64) -> BlockResult<()> { - let current_size = self.virtual_size(); - - if new_size == current_size { - return Ok(()); - } - - if new_size < current_size { - return Err(BlockError::new( - BlockErrorKind::UnsupportedFeature, - Error::ShrinkNotSupported, - )); - } - - if self.backing_file.is_some() { - return Err(BlockError::new( - BlockErrorKind::UnsupportedFeature, - Error::ResizeWithBackingFile, - )); - } - - // Grow the L1 table if needed - let cluster_size = self.raw_file.cluster_size(); - let entries_per_cluster = cluster_size / size_of::() as u64; - let new_clusters = div_round_up_u64(new_size, cluster_size); - let needed_l1_entries = div_round_up_u64(new_clusters, entries_per_cluster) as u32; - - if needed_l1_entries > self.header.l1_size { - self.grow_l1_table(needed_l1_entries)?; - } - - self.header.size = new_size; - - self.raw_file - .file_mut() - .rewind() - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; - self.header - .write_to(self.raw_file.file_mut()) - .map_err(|e| match e { - Error::WritingHeader(io_err) => { - BlockError::new(BlockErrorKind::Io, Error::ResizeIo(io_err)) - } - other => BlockError::new(BlockErrorKind::Io, other), - })?; - - self.raw_file - .file_mut() - .sync_all() - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SyncingHeader(e)))?; - - Ok(()) - } - - /// Grow the L1 table to accommodate at least `new_l1_size` entries. - /// - /// This allocates a new L1 table at file end (guaranteeing contiguity), - /// copies existing entries, updates refcounts, and atomically switches - /// to the new table. - fn grow_l1_table(&mut self, new_l1_size: u32) -> BlockResult<()> { - let old_l1_size = self.header.l1_size; - let old_l1_offset = self.header.l1_table_offset; - let cluster_size = self.raw_file.cluster_size(); - - let new_l1_bytes = new_l1_size as u64 * size_of::() as u64; - let new_l1_clusters = div_round_up_u64(new_l1_bytes, cluster_size); - - // Allocate contiguous clusters at file end for new L1 table - let file_size = self - .raw_file - .file_mut() - .seek(SeekFrom::End(0)) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ResizeIo(e)))?; - let new_l1_offset = self.raw_file.cluster_address(file_size + cluster_size - 1); - - // Extend file to fit all L1 clusters - let new_file_end = new_l1_offset + new_l1_clusters * cluster_size; - self.raw_file - .file_mut() - .set_len(new_file_end) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SettingFileSize(e)))?; - - // Set refcounts for the contiguous range - for i in 0..new_l1_clusters { - self.set_cluster_refcount(new_l1_offset + i * cluster_size, 1) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ResizeIo(e)))?; - } - - let mut new_l1_data = vec![0u64; new_l1_size as usize]; - let old_entries = self.l1_table.get_values(); - new_l1_data[..old_entries.len()].copy_from_slice(old_entries); - - for (i, l2_addr) in new_l1_data.iter_mut().enumerate() { - if *l2_addr != 0 && i < old_entries.len() { - let refcount = self - .refcounts - .get_cluster_refcount(&mut self.raw_file, *l2_addr) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingRefcount(e)))?; - *l2_addr = l1_entry_make(*l2_addr, refcount == 1); - } - } - - // Write the new L1 table to the file. - self.raw_file - .write_pointer_table_direct(new_l1_offset, new_l1_data.iter()) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ResizeIo(e)))?; - - self.raw_file - .file_mut() - .sync_all() - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SyncingHeader(e)))?; - - self.header.l1_size = new_l1_size; - self.header.l1_table_offset = new_l1_offset; - - self.raw_file - .file_mut() - .rewind() - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; - self.header - .write_to(self.raw_file.file_mut()) - .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; - - self.raw_file - .file_mut() - .sync_all() - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SyncingHeader(e)))?; - - // Free old L1 table clusters - let old_l1_bytes = old_l1_size as u64 * size_of::() as u64; - let old_l1_clusters = div_round_up_u64(old_l1_bytes, cluster_size); - for i in 0..old_l1_clusters { - let cluster_addr = old_l1_offset + i * cluster_size; - let _ = self.set_cluster_refcount(cluster_addr, 0); - } - - // Update L1 table cache - self.l1_table.extend(new_l1_size as usize); - - Ok(()) - } - - // 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 { - if address.checked_add(count as u64).is_none() || address > self.virtual_size() { - return 0; - } - min(count as u64, self.virtual_size() - address) as usize - } - - // Limits the range so that it doesn't overflow the end of a cluster. - fn limit_range_cluster(&self, address: u64, count: usize) -> usize { - let offset: u64 = self.raw_file.cluster_offset(address); - let limit = self.raw_file.cluster_size() - offset; - min(count as u64, limit) as usize - } - - // Gets the maximum virtual size of this image. - fn virtual_size(&self) -> u64 { - self.header.size - } - - // Gets the offset of `address` in the L1 table. - fn l1_table_index(&self, address: u64) -> u64 { - (address / self.raw_file.cluster_size()) / self.l2_entries - } - - // Gets the offset of `address` in the L2 table. - fn l2_table_index(&self, address: u64) -> u64 { - (address / self.raw_file.cluster_size()) % self.l2_entries - } - - /// Attempts to set the corrupt bit, logging failures without propagating them. - /// - /// This is "best effort" because the write may fail due to various reasons like - /// disk full, readonly storage, etc. This method is called just before returning - /// EIO to the caller. The error is not propagated because the original corruption - /// error is more important to return to the call site than a secondary I/O - /// failure from marking the image. - fn set_corrupt_bit_best_effort(&mut self) { - if let Err(e) = self.header.set_corrupt_bit(self.raw_file.file_mut()) { - warn!("Failed to persist corrupt bit: {e}"); - } - } - - // Decompress the cluster, return EIO on failure - fn decompress_l2_cluster(&mut self, l2_entry: u64) -> std::io::Result> { - let (compressed_cluster_addr, compressed_cluster_size) = - l2_entry_compressed_cluster_layout(l2_entry, self.header.cluster_bits); - // Read compressed cluster from raw file - self.raw_file - .file_mut() - .seek(SeekFrom::Start(compressed_cluster_addr))?; - let mut compressed_cluster = vec![0; compressed_cluster_size]; - self.raw_file - .file_mut() - .read_exact(&mut compressed_cluster)?; - let decoder = self.header.get_decoder(); - // Decompress - let cluster_size = self.raw_file.cluster_size() as usize; - let mut decompressed_cluster = vec![0; cluster_size]; - let decompressed_size = decoder - .decode(&compressed_cluster, &mut decompressed_cluster) - .map_err(|_| { - self.set_corrupt_bit_best_effort(); - io::Error::from_raw_os_error(EIO) - })?; - if decompressed_size as u64 != self.raw_file.cluster_size() { - self.set_corrupt_bit_best_effort(); - return Err(std::io::Error::from_raw_os_error(EIO)); - } - Ok(decompressed_cluster) - } - - fn file_read( - &mut self, - address: u64, - count: usize, - buf: &mut [u8], - ) -> std::io::Result> { - let err_inval = std::io::Error::from_raw_os_error(EINVAL); - if address >= self.virtual_size() { - return Err(err_inval); - } - - let l1_index = self.l1_table_index(address) as usize; - let l2_addr_disk = *self - .l1_table - .get(l1_index) - .ok_or_else(|| std::io::Error::from_raw_os_error(EINVAL))?; - - if l2_addr_disk == 0 { - // Reading from an unallocated cluster will return zeros. - return Ok(None); - } - - let l2_index = self.l2_table_index(address) as usize; - - self.cache_l2_cluster(l1_index, l2_addr_disk, false)?; - - let l2_entry = self.l2_cache.get(l1_index).unwrap()[l2_index]; - if l2_entry_is_empty(l2_entry) { - // Reading from an unallocated cluster will return zeros. - return Ok(None); - } else if l2_entry_is_compressed(l2_entry) { - // Compressed cluster. - // Read it, decompress, then return slice from decompressed data. - let mut decompressed_cluster = self.decompress_l2_cluster(l2_entry)?; - decompressed_cluster.resize(self.raw_file.cluster_size() as usize, 0); - let start = self.raw_file.cluster_offset(address) as usize; - let end = start.checked_add(count); - if end.is_none() || end.unwrap() > decompressed_cluster.len() { - return Err(err_inval); - } - buf[..count].copy_from_slice(&decompressed_cluster[start..end.unwrap()]); - } else if l2_entry_is_zero(l2_entry) { - // Cluster with zero flag reads as zeros without accessing disk. - buf[..count].fill(0); - return Ok(Some(())); - } else { - let cluster_addr = l2_entry_std_cluster_addr(l2_entry); - if cluster_addr & (self.raw_file.cluster_size() - 1) != 0 { - self.set_corrupt_bit_best_effort(); - return Err(io::Error::from_raw_os_error(EIO)); - } - let start = cluster_addr + self.raw_file.cluster_offset(address); - let raw_file = self.raw_file.file_mut(); - raw_file.seek(SeekFrom::Start(start))?; - raw_file.read_exact(buf)?; - } - Ok(Some(())) - } - - // Gets the offset of the given guest address in the host file. If L1, L2, or data clusters need - // to be allocated, they will be. - fn file_offset_write(&mut self, address: u64) -> std::io::Result { - if address >= self.virtual_size() { - return Err(std::io::Error::from_raw_os_error(EINVAL)); - } - - let l1_index = self.l1_table_index(address) as usize; - let l2_addr_disk = *self - .l1_table - .get(l1_index) - .ok_or_else(|| std::io::Error::from_raw_os_error(EINVAL))?; - let l2_index = self.l2_table_index(address) as usize; - - let mut set_refcounts = Vec::new(); - - if let Some(new_addr) = self.cache_l2_cluster(l1_index, l2_addr_disk, true)? { - // The cluster refcount starts at one meaning it is used but doesn't need COW. - set_refcounts.push((new_addr, 1)); - } - - let l2_entry = self.l2_cache.get(l1_index).unwrap()[l2_index]; - let cluster_addr = if l2_entry_is_compressed(l2_entry) { - // Writing to compressed cluster. - - // Allocate new cluster, decompress into new cluster, then use - // offset of new cluster. - let decompressed_cluster = self.decompress_l2_cluster(l2_entry)?; - let cluster_addr = self.append_data_cluster(None)?; - self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?; - self.raw_file - .file_mut() - .seek(SeekFrom::Start(cluster_addr))?; - let nwritten = self.raw_file.file_mut().write(&decompressed_cluster)?; - if nwritten != decompressed_cluster.len() { - self.set_corrupt_bit_best_effort(); - return Err(std::io::Error::from_raw_os_error(EIO)); - } - - // Decrement refcount for each cluster spanned by the old compressed data - self.deallocate_compressed_cluster(l2_entry)?; - - cluster_addr - } else if l2_entry_is_empty(l2_entry) || l2_entry_is_zero(l2_entry) { - let cluster_addr = if l2_entry_is_zero(l2_entry) { - self.append_zeroed_data_cluster()? - } else { - let initial_data = if let Some(backing) = self.backing_file.as_mut() { - let cluster_size = self.raw_file.cluster_size(); - let cluster_begin = address - (address % cluster_size); - let mut cluster_data = vec![0u8; cluster_size as usize]; - backing.read_at(cluster_begin, &mut cluster_data)?; - Some(cluster_data) - } else { - None - }; - self.append_data_cluster(initial_data)? - }; - // Need to allocate a data cluster - self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?; - cluster_addr - } else { - let cluster_addr = l2_entry_std_cluster_addr(l2_entry); - if cluster_addr & (self.raw_file.cluster_size() - 1) != 0 { - self.set_corrupt_bit_best_effort(); - return Err(io::Error::from_raw_os_error(EIO)); - } - cluster_addr - }; - - for (addr, count) in set_refcounts { - self.set_cluster_refcount_track_freed(addr, count)?; - } - - Ok(cluster_addr + self.raw_file.cluster_offset(address)) - } - - // Updates the l1 and l2 tables to point to the new `cluster_addr`. - fn update_cluster_addr( - &mut self, - l1_index: usize, - l2_index: usize, - cluster_addr: u64, - set_refcounts: &mut Vec<(u64, u64)>, - ) -> io::Result<()> { - if !self.l2_cache.get(l1_index).unwrap().dirty() { - // Free the previously used cluster if one exists. Modified tables are always - // witten to new clusters so the L1 table can be committed to disk after they - // are and L1 never points at an invalid table. - // The index must be valid from when it was inserted. - let addr = self.l1_table[l1_index]; - if addr != 0 { - self.unref_clusters.push(addr); - set_refcounts.push((addr, 0)); - } - - // Allocate a new cluster to store the L2 table and update the L1 table to point - // to the new table. The cluster will be written when the cache is flushed, no - // need to copy the data now. - let new_addr: u64 = self.get_new_cluster(None)?; - // The cluster refcount starts at one indicating it is used but doesn't need - // COW. - set_refcounts.push((new_addr, 1)); - self.l1_table[l1_index] = new_addr; - } - // 'unwrap' is OK because it was just added. - self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = l2_entry_make_std(cluster_addr); - Ok(()) - } - - // Allocate a new cluster and return its offset within the raw file. - fn get_new_cluster(&mut self, initial_data: Option>) -> std::io::Result { - // First use a pre allocated cluster if one is available. - if let Some(free_cluster) = self.avail_clusters.pop() { - if free_cluster == 0 { - self.set_corrupt_bit_best_effort(); - return Err(io::Error::from_raw_os_error(EIO)); - } - if let Some(initial_data) = initial_data { - self.raw_file.write_cluster(free_cluster, &initial_data)?; - } else { - self.raw_file.zero_cluster(free_cluster)?; - } - return Ok(free_cluster); - } - - let max_valid_cluster_offset = self.refcounts.max_valid_cluster_offset(); - if let Some(new_cluster) = self.raw_file.add_cluster_end(max_valid_cluster_offset)? { - if new_cluster == 0 { - self.set_corrupt_bit_best_effort(); - return Err(io::Error::from_raw_os_error(EIO)); - } - if let Some(initial_data) = initial_data { - self.raw_file.write_cluster(new_cluster, &initial_data)?; - } - Ok(new_cluster) - } else { - error!("No free clusters in get_new_cluster()"); - Err(std::io::Error::from_raw_os_error(ENOSPC)) - } - } - - // Allocate and initialize a new data cluster. Returns the offset of the - // cluster into the file on success. - fn append_data_cluster(&mut self, initial_data: Option>) -> std::io::Result { - let new_addr: u64 = self.get_new_cluster(initial_data)?; - // The cluster refcount starts at one indicating it is used but doesn't need COW. - self.set_cluster_refcount_track_freed(new_addr, 1)?; - Ok(new_addr) - } - - // Allocate and initialize a zeroed data cluster without building a cluster-sized buffer. - fn append_zeroed_data_cluster(&mut self) -> std::io::Result { - let new_addr: u64 = self.get_new_cluster(None)?; - let cluster_size = self.raw_file.cluster_size() as usize; - self.raw_file - .file_mut() - .write_zeroes_at(new_addr, cluster_size)?; - // The cluster refcount starts at one indicating it is used but doesn't need COW. - self.set_cluster_refcount_track_freed(new_addr, 1)?; - Ok(new_addr) - } - - // Returns true if the cluster containing `address` is already allocated. - fn cluster_allocated(&mut self, address: u64) -> std::io::Result { - if address >= self.virtual_size() { - return Err(std::io::Error::from_raw_os_error(EINVAL)); - } - - let l1_index = self.l1_table_index(address) as usize; - let l2_addr_disk = *self - .l1_table - .get(l1_index) - .ok_or_else(|| std::io::Error::from_raw_os_error(EINVAL))?; - let l2_index = self.l2_table_index(address) as usize; - - if l2_addr_disk == 0 { - // Empty overlay metadata means "consult backing" when a backing - // file exists; otherwise it is a hole in this image. - return Ok(self.backing_file.is_some()); - } - - self.cache_l2_cluster(l1_index, l2_addr_disk, false)?; - - let l2_entry = self.l2_cache.get(l1_index).unwrap()[l2_index]; - if l2_entry_is_empty(l2_entry) { - // Empty cluster with backing has existing data to seek in the backing file. - Ok(self.backing_file.is_some()) - } else if l2_entry_is_compressed(l2_entry) { - Ok(true) - } else if l2_entry_is_zero(l2_entry) { - // Zero flagged cluster is a logical hole. It reads as zeros with no data to seek. - Ok(false) - } else { - Ok(true) - } - } - - // Find the first guest address greater than or equal to `address` whose allocation state - // matches `allocated`. - fn find_allocated_cluster( - &mut self, - address: u64, - allocated: bool, - ) -> std::io::Result> { - let size = self.virtual_size(); - if address >= size { - return Ok(None); - } - - // If offset is already within a hole, return it. - if self.cluster_allocated(address)? == allocated { - return Ok(Some(address)); - } - - // Skip to the next cluster boundary. - let cluster_size = self.raw_file.cluster_size(); - let mut cluster_addr = (address / cluster_size + 1) * cluster_size; - - // Search for clusters with the desired allocation state. - while cluster_addr < size { - if self.cluster_allocated(cluster_addr)? == allocated { - return Ok(Some(cluster_addr)); - } - cluster_addr += cluster_size; - } - - Ok(None) - } - - // Deallocate compressed cluster and all related clusters spanned by compressed data. - fn deallocate_compressed_cluster(&mut self, l2_entry: u64) -> std::io::Result<()> { - let (compressed_cluster_addr, compressed_cluster_size) = - l2_entry_compressed_cluster_layout(l2_entry, self.header.cluster_bits); - - // Calculate the end of the compressed data region - let compressed_clusters_end = self.raw_file.cluster_address( - compressed_cluster_addr // Start of compressed data - + compressed_cluster_size as u64 // Add size to get end address - + self.raw_file.cluster_size() - - 1, // Catch possibly partially used last cluster - ); - - // Decrement refcount for each cluster spanned by the compressed data - let mut addr = self.raw_file.cluster_address(compressed_cluster_addr); - while addr < compressed_clusters_end { - let refcount = self - .refcounts - .get_cluster_refcount(&mut self.raw_file, addr) - .map_err(|e| { - if matches!(e, refcount::Error::RefblockUnaligned(_)) { - self.set_corrupt_bit_best_effort(); - } - io::Error::new( - io::ErrorKind::InvalidData, - format!("failed to get cluster refcount: {e}"), - ) - })?; - if refcount > 0 { - self.set_cluster_refcount_track_freed(addr, refcount - 1)?; - } - addr += self.raw_file.cluster_size(); - } - - Ok(()) - } - - // Deallocate the storage for the cluster starting at `address`. - // If `zero_marker` is true, preserve WRITE_ZEROES semantics with a logical-zero - // entry instead of allowing backing data to reappear through an empty entry. - fn deallocate_cluster(&mut self, address: u64, zero_marker: bool) -> std::io::Result<()> { - if address >= self.virtual_size() { - return Err(std::io::Error::from_raw_os_error(EINVAL)); - } - - let l1_index = self.l1_table_index(address) as usize; - let l2_addr_disk = *self - .l1_table - .get(l1_index) - .ok_or_else(|| std::io::Error::from_raw_os_error(EINVAL))?; - let l2_index = self.l2_table_index(address) as usize; - let write_zero_marker = zero_marker && self.backing_file.is_some(); - let dealloc_entry = if write_zero_marker { - l2_entry_make_zero_plain() - } else { - 0 - }; - - if l2_addr_disk == 0 { - // With a backing file, an empty L2 entry means "consult backing". - // WRITE_ZEROES needs a logical-zero marker instead. - if write_zero_marker { - if let Some(new_addr) = self.cache_l2_cluster(l1_index, l2_addr_disk, true)? { - self.set_cluster_refcount_track_freed(new_addr, 1)?; - } - self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = dealloc_entry; - } - return Ok(()); - } - - self.cache_l2_cluster(l1_index, l2_addr_disk, false)?; - - let l2_entry = self.l2_cache.get(l1_index).unwrap()[l2_index]; - if l2_entry_is_empty(l2_entry) { - // With a backing file, empty means "consult backing"; preserve - // WRITE_ZEROES semantics with an explicit zero marker. - if write_zero_marker { - self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = dealloc_entry; - } - return Ok(()); - } - // Compressed clusters cannot use the zero flag optimization, thus fully deallocate instead. - // Their layout may also use bit 0, so classify them before zero-flagged standard entries. - if l2_entry_is_compressed(l2_entry) { - self.deallocate_compressed_cluster(l2_entry)?; - self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = dealloc_entry; - return Ok(()); - } - if l2_entry_is_zero(l2_entry) { - return Ok(()); - } - - let cluster_addr = l2_entry_std_cluster_addr(l2_entry); - - // Decrement the refcount. - let refcount = self - .refcounts - .get_cluster_refcount(&mut self.raw_file, cluster_addr) - .map_err(|e| { - if matches!(e, refcount::Error::RefblockUnaligned(_)) { - self.set_corrupt_bit_best_effort(); - } - io::Error::new( - io::ErrorKind::InvalidData, - format!("failed to get cluster refcount: {e}"), - ) - })?; - if refcount == 0 { - return Err(std::io::Error::from_raw_os_error(EINVAL)); - } - - if self.sparse { - // Fully deallocate to reclaim storage space. - let new_refcount = refcount - 1; - self.set_cluster_refcount_track_freed(cluster_addr, new_refcount)?; - - // Rewrite the L2 entry to remove the cluster mapping (full deallocation). - self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = dealloc_entry; - - if new_refcount == 0 { - let cluster_size = self.raw_file.cluster_size(); - // This cluster is no longer in use; deallocate the storage. - // The underlying FS may not support FALLOC_FL_PUNCH_HOLE, - // so don't treat an error as fatal. Future reads will return zeros anyways. - let _ = self - .raw_file - .file_mut() - .punch_hole(cluster_addr, cluster_size); - self.unref_clusters.push(cluster_addr); - } - } else { - // Zero flag optimization - mark cluster as reading zeros without deallocating. - // Only safe if refcount == 1 (no other references to this cluster). - if refcount == 1 { - // Single reference - safe to use zero flag optimization - self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = - l2_entry_make_zero(cluster_addr); - } else { - // Multiple references - must decrement refcount and unmap this entry. - // Cannot use zero flag because other L2 entries still need the real data. - self.set_cluster_refcount_track_freed(cluster_addr, refcount - 1)?; - self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = dealloc_entry; - } - } - Ok(()) - } - - fn deallocate_bytes(&mut self, address: u64, length: usize) -> std::io::Result<()> { - self.deallocate_bytes_impl(address, length, false) - } - - // Apply WRITE_ZEROES semantics for `length` bytes starting at `address`. - fn write_zeroes_bytes(&mut self, address: u64, length: usize) -> std::io::Result<()> { - self.deallocate_bytes_impl(address, length, true) - } - - fn deallocate_bytes_impl( - &mut self, - address: u64, - length: usize, - zero_marker: bool, - ) -> std::io::Result<()> { - let write_count: usize = self.limit_range_file(address, length); - - let mut nwritten: usize = 0; - while nwritten < write_count { - let curr_addr = address + nwritten as u64; - let count = self.limit_range_cluster(curr_addr, write_count - nwritten); - - if count == self.raw_file.cluster_size() as usize { - // Full cluster - deallocate the storage. - self.deallocate_cluster(curr_addr, zero_marker)?; - } else { - // Partial cluster - zero out the relevant bytes if it was allocated. - // Any space in unallocated clusters can be left alone, since - // unallocated clusters already read back as zeroes. - let offset = self.file_offset_write(curr_addr)?; - // Partial cluster - zero it out. - self.raw_file.file_mut().write_zeroes_at(offset, count)?; - } - - nwritten += count; - } - Ok(()) - } - - // Reads an L2 cluster from the disk, returning an error if the file can't be read or if any - // cluster is compressed. - fn read_l2_cluster(raw_file: &mut QcowRawFile, cluster_addr: u64) -> std::io::Result> { - let l2_table = raw_file.read_pointer_cluster(cluster_addr, None)?; - Ok(l2_table) - } - - // Put an L2 cluster to the cache with evicting less-used cluster - // The new cluster may be allocated if necessary - // (may_alloc argument is true and l2_addr_disk == 0) - fn cache_l2_cluster( - &mut self, - l1_index: usize, - l2_addr_disk: u64, - may_alloc: bool, - ) -> std::io::Result> { - let mut new_cluster: Option = None; - if !self.l2_cache.contains_key(l1_index) { - // Not in the cache. - let l2_table = if may_alloc && l2_addr_disk == 0 { - // Allocate a new cluster to store the L2 table and update the L1 table to point - // to the new table. - let new_addr: u64 = self.get_new_cluster(None)?; - new_cluster = Some(new_addr); - self.l1_table[l1_index] = new_addr; - VecCache::new(self.l2_entries as usize) - } else { - let cluster_size = self.raw_file.cluster_size(); - if l2_addr_disk & (cluster_size - 1) != 0 { - self.set_corrupt_bit_best_effort(); - return Err(io::Error::from_raw_os_error(EIO)); - } - VecCache::from_vec(Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk)?) - }; - let l1_table = &self.l1_table; - let raw_file = &mut self.raw_file; - self.l2_cache.insert(l1_index, l2_table, |index, evicted| { - raw_file.write_pointer_table_direct(l1_table[index], evicted.iter()) - })?; - } - Ok(new_cluster) - } - - // Set the refcount for a cluster and add any unreferenced clusters to the unref list. - fn set_cluster_refcount_track_freed( - &mut self, - address: u64, - refcount: u64, - ) -> std::io::Result<()> { - let mut newly_unref = self.set_cluster_refcount(address, refcount)?; - self.unref_clusters.append(&mut newly_unref); - Ok(()) - } - - // Set the refcount for a cluster with the given address. - // Returns a list of any refblocks that can be reused, this happens when a refblock is moved, - // the old location can be reused. - fn set_cluster_refcount(&mut self, address: u64, refcount: u64) -> std::io::Result> { - let mut added_clusters = Vec::new(); - let mut unref_clusters = Vec::new(); - let mut refcount_set = false; - let mut new_cluster = None; - - while !refcount_set { - match self.refcounts.set_cluster_refcount( - &mut self.raw_file, - address, - refcount, - new_cluster.take(), - ) { - Ok(None) => { - refcount_set = true; - } - Ok(Some(freed_cluster)) => { - // Recursively set the freed refcount block's refcount to 0 - let mut freed = self.set_cluster_refcount(freed_cluster, 0)?; - unref_clusters.append(&mut freed); - refcount_set = true; - } - Err(refcount::Error::EvictingRefCounts(e)) => { - return Err(e); - } - Err(refcount::Error::InvalidIndex) => { - self.set_corrupt_bit_best_effort(); - return Err(std::io::Error::from_raw_os_error(EINVAL)); - } - Err(refcount::Error::NeedCluster(addr)) => { - // Read the address and call set_cluster_refcount again. - new_cluster = Some(( - addr, - VecCache::from_vec(self.raw_file.read_refcount_block(addr)?), - )); - } - Err(refcount::Error::NeedNewCluster) => { - // Allocate the cluster and call set_cluster_refcount again. - let addr = self.get_new_cluster(None)?; - added_clusters.push(addr); - new_cluster = Some(( - addr, - VecCache::new(self.refcounts.refcounts_per_block() as usize), - )); - } - Err(refcount::Error::ReadingRefCounts(e)) => { - return Err(e); - } - Err(refcount::Error::RefcountOverflow { .. }) => { - return Err(std::io::Error::from_raw_os_error(EINVAL)); - } - Err(refcount::Error::RefblockUnaligned(_)) => { - self.set_corrupt_bit_best_effort(); - return Err(io::Error::from_raw_os_error(EIO)); - } - } - } - - for addr in added_clusters { - self.set_cluster_refcount(addr, 1)?; - } - Ok(unref_clusters) - } - - fn sync_caches(&mut self) -> std::io::Result<()> { - // Write out all dirty L2 tables. - for (l1_index, l2_table) in self.l2_cache.iter_mut().filter(|(_k, v)| v.dirty()) { - // The index must be valid from when we inserted it. - let addr = self.l1_table[*l1_index]; - if addr != 0 { - self.raw_file - .write_pointer_table_direct(addr, l2_table.iter())?; - } else { - self.set_corrupt_bit_best_effort(); - return Err(std::io::Error::from_raw_os_error(EINVAL)); - } - l2_table.mark_clean(); - } - // Write the modified refcount blocks. - self.refcounts.flush_blocks(&mut self.raw_file)?; - // Make sure metadata(file len) and all data clusters are written. - self.raw_file.file_mut().sync_all()?; - - // Push L1 table and refcount table last as all the clusters they point to are now - // guaranteed to be valid. - let mut sync_required = if self.l1_table.dirty() { - // Write L1 table with OFLAG_COPIED bits - let refcounts = &mut self.refcounts; - self.raw_file.write_pointer_table( - self.header.l1_table_offset, - self.l1_table.iter(), - |raw_file, l2_addr| { - if l2_addr == 0 { - Ok(0) - } else { - let refcount = refcounts - .get_cluster_refcount(raw_file, l2_addr) - .map_err(|e| std::io::Error::other(Error::GettingRefcount(e)))?; - Ok(l1_entry_make(l2_addr, refcount == 1)) - } - }, - )?; - self.l1_table.mark_clean(); - true - } else { - false - }; - sync_required |= self.refcounts.flush_table(&mut self.raw_file)?; - if sync_required { - self.raw_file.file_mut().sync_data()?; - } - - Ok(()) - } -} - -/// Rebuild the reference count tables. fn rebuild_refcounts(raw_file: &mut QcowRawFile, header: QcowHeader) -> BlockResult<()> { fn add_ref( refcounts: &mut [u64], @@ -2093,316 +873,6 @@ fn rebuild_refcounts(raw_file: &mut QcowRawFile, header: QcowHeader) -> BlockRes .map_err(|e| BlockError::new(BlockErrorKind::Io, e)) } -impl AsRawFd for QcowFile { - fn as_raw_fd(&self) -> RawFd { - self.raw_file.as_raw_fd() - } -} - -impl Drop for QcowFile { - fn drop(&mut self) { - let _ = self.sync_caches(); - if self.raw_file.file().is_writable() { - let _ = self.header.set_dirty_bit(self.raw_file.file_mut(), false); - } - } -} - -impl Read for QcowFile { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - let address: u64 = self.current_offset; - let read_count: usize = self.limit_range_file(address, buf.len()); - - let mut nread: usize = 0; - while nread < read_count { - let curr_addr = address + nread as u64; - let count = self.limit_range_cluster(curr_addr, read_count - nread); - - if (self.file_read(curr_addr, count, &mut buf[nread..(nread + count)])?).is_some() { - // Data is successfully read from the cluster - } else if let Some(backing) = self.backing_file.as_mut() { - backing.read_at(curr_addr, &mut buf[nread..(nread + count)])?; - } else { - // Previously unwritten region, return zeros - buf[nread..(nread + count)].fill(0); - } - - nread += count; - } - self.current_offset += read_count as u64; - Ok(read_count) - } -} - -impl Seek for QcowFile { - fn seek(&mut self, pos: SeekFrom) -> std::io::Result { - let new_offset: Option = match pos { - SeekFrom::Start(off) => Some(off), - SeekFrom::End(off) => { - if off < 0 { - 0i64.checked_sub(off) - .and_then(|increment| self.virtual_size().checked_sub(increment as u64)) - } else { - self.virtual_size().checked_add(off as u64) - } - } - SeekFrom::Current(off) => { - if off < 0 { - 0i64.checked_sub(off) - .and_then(|increment| self.current_offset.checked_sub(increment as u64)) - } else { - self.current_offset.checked_add(off as u64) - } - } - }; - - if let Some(o) = new_offset - && o <= self.virtual_size() - { - self.current_offset = o; - return Ok(o); - } - Err(std::io::Error::from_raw_os_error(EINVAL)) - } -} - -impl Write for QcowFile { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - let address: u64 = self.current_offset; - let write_count: usize = self.limit_range_file(address, buf.len()); - - let mut nwritten: usize = 0; - while nwritten < write_count { - let curr_addr = address + nwritten as u64; - let offset = self.file_offset_write(curr_addr)?; - let count = self.limit_range_cluster(curr_addr, write_count - nwritten); - - self.raw_file.file_mut().seek(SeekFrom::Start(offset))?; - let count = self - .raw_file - .file_mut() - .write(&buf[nwritten..(nwritten + count)])?; - - nwritten += count; - } - self.current_offset += write_count as u64; - Ok(write_count) - } - - fn flush(&mut self) -> std::io::Result<()> { - self.sync_caches()?; - self.avail_clusters.append(&mut self.unref_clusters); - Ok(()) - } -} - -impl FileSync for QcowFile { - fn fsync(&mut self) -> std::io::Result<()> { - self.flush() - } -} - -impl FileSetLen for QcowFile { - fn set_len(&self, _len: u64) -> std::io::Result<()> { - Err(std::io::Error::other( - "set_len() not supported for QcowFile", - )) - } -} - -impl PunchHole for QcowFile { - fn punch_hole(&mut self, offset: u64, length: u64) -> std::io::Result<()> { - let mut remaining = length; - let mut offset = offset; - while remaining > 0 { - let chunk_length = min(remaining, usize::MAX as u64) as usize; - self.deallocate_bytes(offset, chunk_length)?; - remaining -= chunk_length as u64; - offset += chunk_length as u64; - } - Ok(()) - } -} - -impl WriteZeroesAt for QcowFile { - fn write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result { - self.write_zeroes_bytes(offset, length)?; - Ok(length) - } -} - -impl SeekHole for QcowFile { - fn seek_hole(&mut self, offset: u64) -> io::Result> { - match self.find_allocated_cluster(offset, false) { - Err(e) => Err(e), - Ok(None) => { - if offset < self.virtual_size() { - Ok(Some(self.seek(SeekFrom::End(0))?)) - } else { - Ok(None) - } - } - Ok(Some(o)) => { - self.seek(SeekFrom::Start(o))?; - Ok(Some(o)) - } - } - } - - fn seek_data(&mut self, offset: u64) -> io::Result> { - match self.find_allocated_cluster(offset, true) { - Err(e) => Err(e), - Ok(None) => Ok(None), - Ok(Some(o)) => { - self.seek(SeekFrom::Start(o))?; - Ok(Some(o)) - } - } - } -} - -impl BlockBackend for QcowFile { - fn logical_size(&self) -> std::result::Result { - Ok(self.virtual_size()) - } - - fn physical_size(&self) -> std::result::Result { - self.raw_file - .physical_size() - .map_err(crate::Error::GetFileMetadata) - } -} - -fn convert_copy(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> BlockResult<()> -where - R: Read + Seek, - W: Write + Seek, -{ - const CHUNK_SIZE: usize = 65536; - let mut buf = [0; CHUNK_SIZE]; - let mut read_count = 0; - reader - .seek(SeekFrom::Start(offset)) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; - writer - .seek(SeekFrom::Start(offset)) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; - loop { - let this_count = min(CHUNK_SIZE as u64, size - read_count) as usize; - let nread = reader - .read(&mut buf[..this_count]) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingData(e)))?; - writer - .write(&buf[..nread]) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::WritingData(e)))?; - read_count += nread as u64; - if nread == 0 || read_count == size { - break; - } - } - - Ok(()) -} - -fn convert_reader_writer(reader: &mut R, writer: &mut W, size: u64) -> BlockResult<()> -where - R: Read + Seek + SeekHole, - W: Write + Seek, -{ - let mut offset = 0; - while offset < size { - // Find the next range of data. - let next_data = match reader - .seek_data(offset) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))? - { - Some(o) => o, - None => { - // No more data in the file. - break; - } - }; - let next_hole = match reader - .seek_hole(next_data) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))? - { - Some(o) => o, - None => { - // This should not happen - there should always be at least one hole - // after any data. - return Err(BlockError::new( - BlockErrorKind::Io, - Error::SeekingFile(io::Error::from_raw_os_error(EINVAL)), - )); - } - }; - let count = next_hole - next_data; - convert_copy(reader, writer, next_data, count)?; - offset = next_hole; - } - - Ok(()) -} - -fn convert_reader(reader: &mut R, dst_file: RawFile, dst_type: ImageType) -> BlockResult<()> -where - R: Read + Seek + SeekHole, -{ - let src_size = reader - .seek(SeekFrom::End(0)) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; - reader - .rewind() - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; - - // Ensure the destination file is empty before writing to it. - dst_file - .set_len(0) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SettingFileSize(e)))?; - - match dst_type { - ImageType::Qcow2 => { - let mut dst_writer = QcowFile::new(dst_file, 3, src_size, true) - .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; - convert_reader_writer(reader, &mut dst_writer, src_size) - } - ImageType::Raw => { - let mut dst_writer = dst_file; - // Set the length of the destination file to convert it into a sparse file - // of the desired size. - dst_writer - .set_len(src_size) - .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SettingFileSize(e)))?; - convert_reader_writer(reader, &mut dst_writer, src_size) - } - } -} - -/// Copy the contents of a disk image in `src_file` into `dst_file`. -/// The type of `src_file` is automatically detected, and the output file type is -/// determined by `dst_type`. -pub fn convert( - mut src_file: RawFile, - dst_file: RawFile, - dst_type: ImageType, - src_max_nesting_depth: u32, -) -> BlockResult<()> { - let src_type = detect_image_type(&mut src_file)?; - match src_type { - ImageType::Qcow2 => { - let mut src_reader = - QcowFile::from_with_nesting_depth(src_file, src_max_nesting_depth, true) - .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; - convert_reader(&mut src_reader, dst_file, dst_type) - } - ImageType::Raw => { - // src_file is a raw file. - let mut src_reader = src_file; - convert_reader(&mut src_reader, dst_file, dst_type) - } - } -} - /// Detect the type of an image file by checking for a valid qcow2 header. pub fn detect_image_type(file: &mut RawFile) -> BlockResult { let orig_seek = file @@ -2421,7 +891,6 @@ pub fn detect_image_type(file: &mut RawFile) -> BlockResult { .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?; Ok(image_type) } - #[cfg(test)] mod unit_tests { use std::error::Error as StdError; @@ -2433,7 +902,10 @@ mod unit_tests { use vmm_sys_util::tempdir::TempDir; use vmm_sys_util::tempfile::TempFile; - use super::header::DEFAULT_CLUSTER_BITS; + use super::header::{ + AUTOCLEAR_FEATURES_OFFSET, DEFAULT_CLUSTER_BITS, DEFAULT_REFCOUNT_ORDER, + HEADER_EXT_BACKING_FORMAT, HEADER_EXT_END, V2_BARE_HEADER_SIZE, V3_BARE_HEADER_SIZE, + }; use super::util::ZERO_FLAG; use super::*; use crate::formats::qcow::{QcowDisk, QcowTempDisk}; diff --git a/block/src/formats/qcow/internal/refcount.rs b/block/src/formats/qcow/internal/refcount.rs index fa9077a37..7f56af55d 100644 --- a/block/src/formats/qcow/internal/refcount.rs +++ b/block/src/formats/qcow/internal/refcount.rs @@ -224,41 +224,6 @@ impl RefCount { Ok(self.refblock_cache.get(table_index).unwrap()[block_index]) } - /// Returns the refcount table for this file. This is only useful for debugging. - pub fn ref_table(&self) -> &[u64] { - self.ref_table.get_values() - } - - /// Returns the refcounts stored in the given block. - pub fn refcount_block( - &mut self, - raw_file: &mut QcowRawFile, - table_index: usize, - ) -> Result> { - let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?; - if block_addr_disk == 0 { - return Ok(None); - } - if !self.refblock_cache.contains_key(table_index) { - let table = VecCache::from_vec( - raw_file - .read_refcount_block(block_addr_disk) - .map_err(Error::ReadingRefCounts)?, - ); - // TODO(dgreid) - closure needs to return an error. - let ref_table = &self.ref_table; - self.refblock_cache - .insert(table_index, table, |index, evicted| { - raw_file.write_refcount_block(ref_table[index], evicted.get_values()) - }) - .map_err(Error::EvictingRefCounts)?; - } - // The index must exist as it was just inserted if it didn't already. - Ok(Some( - self.refblock_cache.get(table_index).unwrap().get_values(), - )) - } - // Gets the address of the refcount block and the index into the block for the given address. fn get_refcount_index(&self, address: u64) -> (usize, usize) { let block_index = (address / self.cluster_size) % self.refcount_block_entries;