// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause

pub(crate) mod backing;
pub(crate) mod decoder;
mod header;
pub(crate) mod metadata;
pub(crate) mod qcow_raw_file;
mod raw_file;
mod refcount;
mod util;
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::mem::size_of;
use std::os::fd::{AsRawFd, RawFd};
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,
};
use header::{
    COMPATIBLE_FEATURES_LAZY_REFCOUNTS, MAX_CLUSTER_BITS, MAX_QCOW_FILE_SIZE,
    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 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_std_cluster_addr,
};
use vmm_sys_util::file_traits::{FileSetLen, FileSync};
use vmm_sys_util::seek_hole::SeekHole;
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};

use crate::BlockBackend;
use crate::error::{BlockError, BlockErrorKind, BlockResult};
use crate::qcow::qcow_raw_file::{BeUint, QcowRawFile};
pub use crate::qcow::raw_file::RawFile;
use crate::qcow::refcount::RefCount;
use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache};
use crate::qcow_common::decompress_cluster;

#[sorted]
#[derive(Debug, Error)]
pub enum Error {
    #[error("Backing file I/O error: {0}")]
    BackingFileIo(String /* path */, #[source] io::Error),
    #[error("Backing file open error: {0}")]
    BackingFileOpen(String /* path */, #[source] Box<Error>),
    #[error("Backing file support is disabled")]
    BackingFilesDisabled,
    #[error("Backing file name is too long: {0} bytes over")]
    BackingFileTooLong(usize),
    #[error("Image is marked corrupt and cannot be opened for writing")]
    CorruptImage,
    #[error("Failed to evict cache")]
    EvictingCache(#[source] io::Error),
    #[error("File larger than max of {MAX_QCOW_FILE_SIZE}: {0}")]
    FileTooBig(u64),
    #[error("Failed to get file size")]
    GettingFileSize(#[source] io::Error),
    #[error("Failed to get refcount")]
    GettingRefcount(#[source] refcount::Error),
    #[error("Failed to parse filename")]
    InvalidBackingFileName(#[source] str::Utf8Error),
    #[error("Invalid cluster index")]
    InvalidClusterIndex,
    #[error("Invalid cluster size")]
    InvalidClusterSize,
    #[error("Invalid index")]
    InvalidIndex,
    #[error("Invalid L1 table offset")]
    InvalidL1TableOffset,
    #[error("Invalid L1 table size: {0}")]
    InvalidL1TableSize(u32),
    #[error("Invalid magic")]
    InvalidMagic,
    #[error("Invalid offset: {0}")]
    InvalidOffset(u64),
    #[error("Invalid refcount table offset")]
    InvalidRefcountTableOffset,
    #[error("Invalid refcount table size: {0}")]
    InvalidRefcountTableSize(u64),
    #[error("Maximum disk nesting depth exceeded")]
    MaxNestingDepthExceeded,
    #[error("No free clusters")]
    NoFreeClusters,
    #[error("No refcount clusters")]
    NoRefcountClusters,
    #[error("Not enough space for refcounts")]
    NotEnoughSpaceForRefcounts,
    #[error("Failed to open file {0}")]
    OpeningFile(#[source] io::Error),
    #[error("Failed to read data")]
    ReadingData(#[source] io::Error),
    #[error("Failed to read header")]
    ReadingHeader(#[source] io::Error),
    #[error("Failed to read pointers")]
    ReadingPointers(#[source] io::Error),
    #[error("Failed to read ref count block")]
    ReadingRefCountBlock(#[source] refcount::Error),
    #[error("Failed to read ref counts")]
    ReadingRefCounts(#[source] io::Error),
    #[error("Failed to rebuild ref counts")]
    RebuildingRefCounts(#[source] io::Error),
    #[error("Refcount overflow")]
    RefcountOverflow(#[source] refcount::Error),
    #[error("Refcount table offset past file end")]
    RefcountTableOffEnd,
    #[error("Too many clusters specified for refcount")]
    RefcountTableTooLarge,
    #[error("Failed to resize")]
    ResizeIo(#[source] io::Error),
    #[error("Resize not supported with backing file")]
    ResizeWithBackingFile,
    #[error("Failed to seek file")]
    SeekingFile(#[source] io::Error),
    #[error("Failed to set file size")]
    SettingFileSize(#[source] io::Error),
    #[error("Failed to set refcount refcount")]
    SettingRefcountRefcount(#[source] io::Error),
    #[error("Shrinking QCOW images is not supported")]
    ShrinkNotSupported,
    #[error("Size too small for number of clusters")]
    SizeTooSmallForNumberOfClusters,
    #[error("Failed to sync header")]
    SyncingHeader(#[source] io::Error),
    #[error("L1 entry table too large: {0}")]
    TooManyL1Entries(u64),
    #[error("Ref count table too large: {0}")]
    TooManyRefcounts(u64),
    #[error("Unsupported backing file format: {0}")]
    UnsupportedBackingFileFormat(String),
    #[error("Unsupported compression type")]
    UnsupportedCompressionType,
    #[error("Unsupported qcow2 feature(s)")]
    UnsupportedFeature(#[source] MissingFeatureError),
    #[error("Unsupported refcount order")]
    UnsupportedRefcountOrder,
    #[error("Unsupported version: {0}")]
    UnsupportedVersion(u32),
    #[error("Failed to write data")]
    WritingData(#[source] io::Error),
    #[error("Failed to write header")]
    WritingHeader(#[source] io::Error),
}

pub type Result<T> = std::result::Result<T, Error>;

/// Concrete backing file variants.
pub(crate) enum BackingKind {
    /// Raw backing file.
    Raw(RawFile),
    /// QCOW2 backing parsed into metadata and raw file.
    Qcow {
        inner: Box<metadata::QcowState>,
        backing: Option<Box<BackingFile>>,
    },
    /// Full QcowFile used as backing, only in tests.
    #[cfg(test)]
    QcowFile(Box<QcowFile>),
}
/// Backing file wrapper
pub(crate) struct BackingFile {
    kind: BackingKind,
    virtual_size: u64,
}

impl BackingFile {
    fn new(
        backing_file_config: Option<&BackingFileConfig>,
        direct_io: bool,
        max_nesting_depth: u32,
        sparse: bool,
    ) -> BlockResult<Option<Self>> {
        let Some(config) = backing_file_config else {
            return Ok(None);
        };

        // Check nesting depth - applies to any backing file
        if max_nesting_depth == 0 {
            return Err(BlockError::new(
                BlockErrorKind::Overflow,
                Error::MaxNestingDepthExceeded,
            ));
        }

        let backing_raw_file = OpenOptions::new()
            .read(true)
            .open(&config.path)
            .map_err(|e| {
                BlockError::new(
                    BlockErrorKind::Io,
                    Error::BackingFileIo(config.path.clone(), e),
                )
            })?;

        let mut raw_file = RawFile::new(backing_raw_file, direct_io);

        // Determine backing file format from header extension or auto-detect
        let backing_format = match config.format {
            Some(format) => format,
            None => detect_image_type(&mut raw_file)?,
        };

        let (kind, virtual_size) = match backing_format {
            ImageType::Raw => {
                let size = raw_file.seek(SeekFrom::End(0)).map_err(|e| {
                    BlockError::new(
                        BlockErrorKind::Io,
                        Error::BackingFileIo(config.path.clone(), e),
                    )
                })?;
                raw_file.rewind().map_err(|e| {
                    BlockError::new(
                        BlockErrorKind::Io,
                        Error::BackingFileIo(config.path.clone(), e),
                    )
                })?;
                (BackingKind::Raw(raw_file), size)
            }
            ImageType::Qcow2 => {
                let (inner, nested_backing, _sparse) =
                    parse_qcow(raw_file, max_nesting_depth - 1, sparse).map_err(|e| {
                        let kind = e.kind();
                        let source = e
                            .into_source()
                            .and_then(|s| s.downcast::<Error>().ok())
                            .map(|qcow_err| Error::BackingFileOpen(config.path.clone(), qcow_err));
                        match source {
                            Some(err) => BlockError::new(kind, err),
                            None => BlockError::from_kind(kind),
                        }
                    })?;
                let size = inner.header.size;
                (
                    BackingKind::Qcow {
                        inner: Box::new(inner),
                        backing: nested_backing.map(Box::new),
                    },
                    size,
                )
            }
        };

        Ok(Some(Self { kind, virtual_size }))
    }

    /// Consume and return the kind and virtual size.
    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 {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.debug_struct("BackingFile").finish()
    }
}

/// Parses and validates a QCOW2 image file, returning the metadata, backing
/// file and sparse flag.
///
/// Used by [`QcowFile`] and [`QcowDisk`] constructors.
pub(crate) fn parse_qcow(
    mut file: RawFile,
    max_nesting_depth: u32,
    sparse: bool,
) -> BlockResult<(metadata::QcowState, Option<BackingFile>, bool)> {
    let mut header = QcowHeader::new(&mut file).map_err(|e| {
        let kind = match &e {
            Error::InvalidMagic
            | Error::BackingFileTooLong(_)
            | Error::InvalidBackingFileName(_) => BlockErrorKind::InvalidFormat,
            Error::UnsupportedFeature(_) | Error::UnsupportedCompressionType => {
                BlockErrorKind::UnsupportedFeature
            }
            _ => BlockErrorKind::Io,
        };
        BlockError::new(kind, e)
    })?;

    // Only v2 and v3 files are supported.
    if header.version != 2 && header.version != 3 {
        return Err(BlockError::new(
            BlockErrorKind::UnsupportedFeature,
            Error::UnsupportedVersion(header.version),
        ));
    }

    // Make sure that the L1 table fits in RAM.
    if u64::from(header.l1_size) > MAX_RAM_POINTER_TABLE_SIZE {
        return Err(BlockError::new(
            BlockErrorKind::InvalidFormat,
            Error::InvalidL1TableSize(header.l1_size),
        ));
    }

    let cluster_bits: u32 = header.cluster_bits;
    if !(MIN_CLUSTER_BITS..=MAX_CLUSTER_BITS).contains(&cluster_bits) {
        return Err(BlockError::new(
            BlockErrorKind::InvalidFormat,
            Error::InvalidClusterSize,
        ));
    }
    let cluster_size = 0x01u64 << cluster_bits;

    // Limit the total size of the disk.
    if header.size > MAX_QCOW_FILE_SIZE {
        return Err(BlockError::new(
            BlockErrorKind::InvalidFormat,
            Error::FileTooBig(header.size),
        ));
    }

    let direct_io = file.is_direct();

    let backing_file = BackingFile::new(
        header.backing_file.as_ref(),
        direct_io,
        max_nesting_depth,
        sparse,
    )?;

    // Validate refcount order to be 0..6
    let refcount_bits: u64 = 0x01u64.checked_shl(header.refcount_order).ok_or_else(|| {
        BlockError::new(
            BlockErrorKind::UnsupportedFeature,
            Error::UnsupportedRefcountOrder,
        )
    })?;
    if refcount_bits > 64 {
        return Err(BlockError::new(
            BlockErrorKind::UnsupportedFeature,
            Error::UnsupportedRefcountOrder,
        ));
    }

    // Need at least one refcount cluster
    if header.refcount_table_clusters == 0 {
        return Err(BlockError::new(
            BlockErrorKind::InvalidFormat,
            Error::NoRefcountClusters,
        ));
    }
    offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)
        .map_err(|e| BlockError::new(BlockErrorKind::CorruptImage, e))?;
    offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)
        .map_err(|e| BlockError::new(BlockErrorKind::CorruptImage, e))?;
    // refcount table must be a cluster boundary, and within the file's virtual or actual size.
    offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)
        .map_err(|e| BlockError::new(BlockErrorKind::CorruptImage, e))?;
    let file_size = file
        .metadata()
        .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingFileSize(e)))?
        .len();
    if header.refcount_table_offset > max(file_size, header.size) {
        return Err(BlockError::new(
            BlockErrorKind::CorruptImage,
            Error::RefcountTableOffEnd,
        ));
    }

    // The first cluster should always have a non-zero refcount, so if it is 0,
    // this is an old file with broken refcounts, which requires a rebuild.
    let mut refcount_rebuild_required = true;
    file.seek(SeekFrom::Start(header.refcount_table_offset))
        .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?;
    let first_refblock_addr = u64::read_be(&mut file)
        .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingHeader(e)))?;
    if first_refblock_addr != 0 {
        file.seek(SeekFrom::Start(first_refblock_addr))
            .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?;
        let first_cluster_refcount = u16::read_be(&mut file)
            .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingHeader(e)))?;
        if first_cluster_refcount != 0 {
            refcount_rebuild_required = false;
        }
    }

    if (header.compatible_features & COMPATIBLE_FEATURES_LAZY_REFCOUNTS) != 0 {
        refcount_rebuild_required = true;
    }

    let mut raw_file = QcowRawFile::from(file, cluster_size, refcount_bits)
        .ok_or_else(|| BlockError::new(BlockErrorKind::InvalidFormat, Error::InvalidClusterSize))?;
    let is_writable = raw_file.file().is_writable();

    if header.is_corrupt() {
        if is_writable {
            return Err(BlockError::new(
                BlockErrorKind::CorruptImage,
                Error::CorruptImage,
            ));
        }
        let path = read_link(format!("/proc/self/fd/{}", raw_file.file().as_raw_fd()))
            .map_or_else(|_| "<unknown>".to_string(), |p| p.display().to_string());
        warn!("QCOW2 image is marked corrupt, opening read-only: {path}");
    }

    // Image already has dirty bit set. Refcounts may be invalid.
    if IncompatFeatures::from_bits_truncate(header.incompatible_features)
        .contains(IncompatFeatures::DIRTY)
    {
        log::warn!("QCOW2 image not cleanly closed, rebuilding refcounts");
        refcount_rebuild_required = true;
    }

    // Skip refcount rebuilding for readonly files.
    if refcount_rebuild_required && is_writable {
        QcowFile::rebuild_refcounts(&mut raw_file, header.clone())?;
    }

    let entries_per_cluster = cluster_size / size_of::<u64>() as u64;
    let num_clusters = div_round_up_u64(header.size, cluster_size);
    let num_l2_clusters = div_round_up_u64(num_clusters, entries_per_cluster);
    let l1_clusters = div_round_up_u64(num_l2_clusters, entries_per_cluster);
    let header_clusters = div_round_up_u64(size_of::<QcowHeader>() as u64, cluster_size);
    if num_l2_clusters > MAX_RAM_POINTER_TABLE_SIZE {
        return Err(BlockError::new(
            BlockErrorKind::CorruptImage,
            Error::TooManyL1Entries(num_l2_clusters),
        ));
    }
    let l1_table = VecCache::from_vec(
        raw_file
            .read_pointer_table(
                header.l1_table_offset,
                num_l2_clusters,
                Some(L1_TABLE_OFFSET_MASK),
            )
            .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingHeader(e)))?,
    );

    let num_clusters = div_round_up_u64(header.size, cluster_size);
    let refcount_clusters = max_refcount_clusters(
        header.refcount_order,
        cluster_size as u32,
        (num_clusters + l1_clusters + num_l2_clusters + header_clusters) as u32,
    );
    // Check that the given header doesn't have a suspiciously sized refcount table.
    if u64::from(header.refcount_table_clusters) > 2 * refcount_clusters {
        return Err(BlockError::new(
            BlockErrorKind::CorruptImage,
            Error::RefcountTableTooLarge,
        ));
    }
    if l1_clusters + refcount_clusters > MAX_RAM_POINTER_TABLE_SIZE {
        return Err(BlockError::new(
            BlockErrorKind::InvalidFormat,
            Error::TooManyRefcounts(refcount_clusters),
        ));
    }
    let refcount_block_entries = cluster_size * 8 / refcount_bits;
    let mut refcounts = RefCount::new(
        &mut raw_file,
        header.refcount_table_offset,
        refcount_clusters,
        refcount_block_entries,
        cluster_size,
        refcount_bits,
    )
    .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingRefCounts(e)))?;

    let l2_entries = cluster_size / size_of::<u64>() as u64;

    // Check that the L1 and refcount tables fit in a 64bit address space.
    let l1_index = (header.size / cluster_size) / l2_entries;
    header
        .l1_table_offset
        .checked_add(l1_index * size_of::<u64>() as u64)
        .ok_or_else(|| {
            BlockError::new(BlockErrorKind::CorruptImage, Error::InvalidL1TableOffset)
        })?;
    header
        .refcount_table_offset
        .checked_add(u64::from(header.refcount_table_clusters) * cluster_size)
        .ok_or_else(|| {
            BlockError::new(
                BlockErrorKind::CorruptImage,
                Error::InvalidRefcountTableOffset,
            )
        })?;

    // Find available (refcount == 0) clusters for the free list.
    let file_size = raw_file
        .file_mut()
        .metadata()
        .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingFileSize(e)))?
        .len();
    let mut avail_clusters = Vec::new();
    for i in (0..file_size).step_by(cluster_size as usize) {
        let refcount = refcounts
            .get_cluster_refcount(&mut raw_file, i)
            .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::GettingRefcount(e)))?;
        if refcount == 0 {
            avail_clusters.push(i);
        }
    }

    if is_writable {
        if !IncompatFeatures::from_bits_truncate(header.incompatible_features)
            .contains(IncompatFeatures::DIRTY)
        {
            header
                .set_dirty_bit(raw_file.file_mut(), true)
                .map_err(|e| {
                    BlockError::new(
                        BlockErrorKind::Io,
                        Error::WritingHeader(io::Error::other(e)),
                    )
                })?;
        }

        header
            .clear_autoclear_features(raw_file.file_mut())
            .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
    }

    let inner = metadata::QcowState {
        raw_file,
        header,
        l1_table,
        l2_entries,
        l2_cache: CacheMap::new(100),
        refcounts,
        avail_clusters,
        unref_clusters: Vec::new(),
    };

    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::qcow::{self, 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<u64>,
    l2_entries: u64,
    l2_cache: CacheMap<VecCache<u64>>,
    refcounts: RefCount,
    current_offset: u64,
    unref_clusters: Vec<u64>, // 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<u64>,
    backing_file: Option<BackingFile>,
    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<QcowFile> {
        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<QcowFile> {
        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<QcowFile> {
        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<QcowFile> {
        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<QcowFile> {
        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<Box<Self>>) {
        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<Option<&[u64]>> {
        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<Option<&[u64]>> {
        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<Option<u64>> {
        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::<u64>() 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::<u64>() 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::<u64>() 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(())
    }

    /// 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.
    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<Vec<u8>> {
        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<Option<()>> {
        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.
            return Ok(None);
        } 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<u64> {
        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 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
            };
            // Need to allocate a data cluster
            let cluster_addr = self.append_data_cluster(initial_data)?;
            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<Vec<u8>>) -> std::io::Result<u64> {
        // 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<Vec<u8>>) -> std::io::Result<u64> {
        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)
    }

    // Returns true if the cluster containing `address` is already allocated.
    fn cluster_allocated(&mut self, address: u64) -> std::io::Result<bool> {
        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 {
            // The whole L2 table for this address is not allocated yet,
            // so the cluster must also be unallocated.
            return Ok(false);
        }

        self.cache_l2_cluster(l1_index, l2_addr_disk, false)?;

        let cluster_addr = self.l2_cache.get(l1_index).unwrap()[l2_index];
        // If cluster_addr != 0, the cluster is allocated.
        Ok(cluster_addr != 0)
    }

    // 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<Option<u64>> {
        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`.
    // Any future reads of this cluster will return all zeroes.
    fn deallocate_cluster(&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 {
            // The whole L2 table for this address is not allocated yet,
            // so the cluster must also be unallocated.
            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) || l2_entry_is_zero(l2_entry) {
            // Already unallocated or zero.
            return Ok(());
        }

        // Compressed clusters cannot use the zero flag optimization, thus fully deallocate instead.
        if l2_entry_is_compressed(l2_entry) {
            self.deallocate_compressed_cluster(l2_entry)?;
            self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = 0;
            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] = 0;

            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] = 0;
            }
        }
        Ok(())
    }

    // Deallocate the storage for `length` bytes starting at `address`.
    // Any future reads of this range will return all zeroes.
    fn deallocate_bytes(&mut self, address: u64, length: usize) -> 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)?;
            } 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<Vec<u64>> {
        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<Option<u64>> {
        let mut new_cluster: Option<u64> = 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<Vec<u64>> {
        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(())
    }
}

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<usize> {
        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<u64> {
        let new_offset: Option<u64> = 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<usize> {
        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<usize> {
        self.punch_hole(offset, length as u64)?;
        Ok(length)
    }
}

impl SeekHole for QcowFile {
    fn seek_hole(&mut self, offset: u64) -> io::Result<Option<u64>> {
        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<Option<u64>> {
        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<u64, crate::Error> {
        Ok(self.virtual_size())
    }

    fn physical_size(&self) -> std::result::Result<u64, crate::Error> {
        self.raw_file
            .physical_size()
            .map_err(crate::Error::GetFileMetadata)
    }
}

fn convert_copy<R, W>(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<R, W>(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<R>(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<ImageType> {
    let orig_seek = file
        .stream_position()
        .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?;
    file.rewind()
        .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?;
    let magic = u32::read_be(file)
        .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingHeader(e)))?;
    let image_type = if magic == QCOW_MAGIC {
        ImageType::Qcow2
    } else {
        ImageType::Raw
    };
    file.seek(SeekFrom::Start(orig_seek))
        .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?;
    Ok(image_type)
}

#[cfg(test)]
mod unit_tests {
    use std::error::Error as StdError;
    use std::fs::File;
    use std::path::Path;

    use vmm_sys_util::tempdir::TempDir;
    use vmm_sys_util::tempfile::TempFile;
    use vmm_sys_util::write_zeroes::WriteZeroes;

    use super::util::{COMPRESSED_FLAG, ZERO_FLAG};
    use super::*;
    use crate::qcow_common::unit_tests::compress_allocated_clusters;

    fn valid_header_v3() -> Vec<u8> {
        vec![
            0x51u8, 0x46, 0x49, 0xfb, // magic
            0x00, 0x00, 0x00, 0x03, // version
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // backing file offset
            0x00, 0x00, 0x00, 0x00, // backing file size
            0x00, 0x00, 0x00, 0x10, // cluster_bits
            0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, // size
            0x00, 0x00, 0x00, 0x00, // crypt method
            0x00, 0x00, 0x01, 0x00, // L1 size
            0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // L1 table offset
            0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // refcount table offset
            0x00, 0x00, 0x00, 0x03, // refcount table clusters
            0x00, 0x00, 0x00, 0x00, // nb snapshots
            0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // snapshots offset
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // incompatible_features
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compatible_features
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // autoclear_features
            0x00, 0x00, 0x00, 0x04, // refcount_order
            0x00, 0x00, 0x00, 0x68, // header_length
        ]
    }

    fn valid_header_v2() -> Vec<u8> {
        vec![
            0x51u8, 0x46, 0x49, 0xfb, // magic
            0x00, 0x00, 0x00, 0x02, // version
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // backing file offset
            0x00, 0x00, 0x00, 0x00, // backing file size
            0x00, 0x00, 0x00, 0x10, // cluster_bits
            0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, // size
            0x00, 0x00, 0x00, 0x00, // crypt method
            0x00, 0x00, 0x01, 0x00, // L1 size
            0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // L1 table offset
            0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // refcount table offset
            0x00, 0x00, 0x00, 0x03, // refcount table clusters
            0x00, 0x00, 0x00, 0x00, // nb snapshots
            0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // snapshots offset
        ]
    }

    // Test case found by clusterfuzz to allocate excessive memory.
    fn test_huge_header() -> Vec<u8> {
        vec![
            0x51, 0x46, 0x49, 0xfb, // magic
            0x00, 0x00, 0x00, 0x03, // version
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // backing file offset
            0x00, 0x00, 0x00, 0x00, // backing file size
            0x00, 0x00, 0x00, 0x09, // cluster_bits
            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, // size
            0x00, 0x00, 0x00, 0x00, // crypt method
            0x00, 0x00, 0x01, 0x00, // L1 size
            0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // L1 table offset
            0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // refcount table offset
            0x00, 0x00, 0x00, 0x03, // refcount table clusters
            0x00, 0x00, 0x00, 0x00, // nb snapshots
            0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // snapshots offset
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // incompatible_features
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compatible_features
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // autoclear_features
            0x00, 0x00, 0x00, 0x04, // refcount_order
            0x00, 0x00, 0x00, 0x68, // header_length
        ]
    }

    fn basic_file(header: &[u8]) -> RawFile {
        let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
        disk_file.write_all(header).unwrap();
        disk_file.set_len(0x1_0000_0000).unwrap();
        disk_file.rewind().unwrap();
        disk_file
    }

    fn with_basic_file<F>(header: &[u8], mut testfn: F)
    where
        F: FnMut(RawFile),
    {
        testfn(basic_file(header)); // File closed when the function exits.
    }

    fn with_default_file<F>(file_size: u64, direct: bool, mut testfn: F)
    where
        F: FnMut(QcowFile),
    {
        let tmp: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), direct);
        let qcow_file = QcowFile::new(tmp, 3, file_size, true).unwrap();

        testfn(qcow_file); // File closed when the function exits.
    }

    #[test]
    fn write_read_start_backing_v2() {
        let disk_file = basic_file(&valid_header_v2());
        let mut backing = QcowFile::from(disk_file).unwrap();
        backing
            .write_all(b"test first bytes")
            .expect("Failed to write test string.");
        let mut buf = [0u8; 4];
        let wrapping_disk_file = basic_file(&valid_header_v2());
        let mut wrapping = QcowFile::from(wrapping_disk_file).unwrap();
        wrapping.set_backing_file(Some(Box::new(backing)));
        wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek.");
        wrapping.read_exact(&mut buf).expect("Failed to read.");
        assert_eq!(&buf, b"test");
    }

    #[test]
    fn write_read_start_backing_v3() {
        let disk_file = basic_file(&valid_header_v3());
        let mut backing = QcowFile::from(disk_file).unwrap();
        backing
            .write_all(b"test first bytes")
            .expect("Failed to write test string.");
        let mut buf = [0u8; 4];
        let wrapping_disk_file = basic_file(&valid_header_v3());
        let mut wrapping = QcowFile::from(wrapping_disk_file).unwrap();
        wrapping.set_backing_file(Some(Box::new(backing)));
        wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek.");
        wrapping.read_exact(&mut buf).expect("Failed to read.");
        assert_eq!(&buf, b"test");
    }

    #[test]
    fn default_header_v2() {
        let header = QcowHeader::create_for_size_and_path(2, 0x10_0000, None);
        let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
        header
            .expect("Failed to create header.")
            .write_to(&mut disk_file)
            .expect("Failed to write header to temporary file.");
        disk_file.rewind().unwrap();
        QcowFile::from(disk_file).expect("Failed to create Qcow from default Header");
    }

    #[test]
    fn default_header_v3() {
        let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, None);
        let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
        header
            .expect("Failed to create header.")
            .write_to(&mut disk_file)
            .expect("Failed to write header to temporary file.");
        disk_file.rewind().unwrap();
        QcowFile::from(disk_file).expect("Failed to create Qcow from default Header");
    }

    #[test]
    fn header_read() {
        with_basic_file(&valid_header_v2(), |mut disk_file: RawFile| {
            let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
            assert_eq!(header.version, 2);
            assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
            assert_eq!(header.header_size, V2_BARE_HEADER_SIZE);
        });
        with_basic_file(&valid_header_v3(), |mut disk_file: RawFile| {
            let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
            assert_eq!(header.version, 3);
            assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
            assert_eq!(header.header_size, V3_BARE_HEADER_SIZE);
        });
    }

    #[test]
    fn header_v2_with_backing() {
        let header = QcowHeader::create_for_size_and_path(2, 0x10_0000, Some("/my/path/to/a/file"))
            .expect("Failed to create header.");
        let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
        header
            .write_to(&mut disk_file)
            .expect("Failed to write header to shm.");
        disk_file.rewind().unwrap();
        let read_header = QcowHeader::new(&mut disk_file).expect("Failed to create header.");
        assert_eq!(
            header.backing_file.as_ref().map(|bf| bf.path.clone()),
            Some(String::from("/my/path/to/a/file"))
        );
        assert_eq!(
            read_header.backing_file.as_ref().map(|bf| &bf.path),
            header.backing_file.as_ref().map(|bf| &bf.path)
        );
    }

    #[test]
    fn header_v3_with_backing() {
        let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, Some("/my/path/to/a/file"))
            .expect("Failed to create header.");
        let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
        header
            .write_to(&mut disk_file)
            .expect("Failed to write header to shm.");
        disk_file.rewind().unwrap();
        let read_header = QcowHeader::new(&mut disk_file).expect("Failed to create header.");
        assert_eq!(
            header.backing_file.as_ref().map(|bf| bf.path.clone()),
            Some(String::from("/my/path/to/a/file"))
        );
        assert_eq!(
            read_header.backing_file.as_ref().map(|bf| &bf.path),
            header.backing_file.as_ref().map(|bf| &bf.path)
        );
    }

    /// Helper to create a test file with header extensions
    fn create_header_with_extension(ext_type: u32, ext_data: &[u8]) -> (RawFile, QcowHeader) {
        let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, None)
            .expect("Failed to create header.");

        let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
        header.write_to(&mut disk_file).unwrap();

        // Write extension
        disk_file
            .seek(SeekFrom::Start(header.header_size as u64))
            .unwrap();
        u32::write_be(&mut disk_file, ext_type).unwrap();
        u32::write_be(&mut disk_file, ext_data.len() as u32).unwrap();
        disk_file.write_all(ext_data).unwrap();

        // Add padding to 8-byte boundary
        let padding = (8 - (ext_data.len() % 8)) % 8;
        if padding > 0 {
            disk_file.write_all(&vec![0u8; padding]).unwrap();
        }

        u32::write_be(&mut disk_file, HEADER_EXT_END).unwrap();

        disk_file.rewind().unwrap();

        (disk_file, header)
    }

    #[test]
    fn read_header_extensions_unknown_extension() {
        let (mut disk_file, mut header) = create_header_with_extension(
            0x12345678, // unknown type
            "test".as_bytes(),
        );

        // Extension parsing needs a backing file to set format on
        header.backing_file = Some(BackingFileConfig {
            path: "/test/backing".to_string(),
            format: None,
        });

        QcowHeader::read_header_extensions(&mut disk_file, &mut header, None).unwrap();
        assert_eq!(header.backing_file.as_ref().and_then(|bf| bf.format), None);
    }

    #[test]
    fn read_header_extensions_raw_format() {
        let (mut disk_file, mut header) =
            create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "raw".as_bytes());

        header.backing_file = Some(BackingFileConfig {
            path: "/test/backing".to_string(),
            format: None,
        });

        QcowHeader::read_header_extensions(&mut disk_file, &mut header, None).unwrap();
        assert_eq!(
            header.backing_file.as_ref().and_then(|bf| bf.format),
            Some(ImageType::Raw)
        );
    }

    #[test]
    fn read_header_extensions_qcow2_format() {
        let (mut disk_file, mut header) =
            create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "qcow2".as_bytes());

        header.backing_file = Some(BackingFileConfig {
            path: "/test/backing".to_string(),
            format: None,
        });

        QcowHeader::read_header_extensions(&mut disk_file, &mut header, None).unwrap();
        assert_eq!(
            header.backing_file.as_ref().and_then(|bf| bf.format),
            Some(ImageType::Qcow2)
        );
    }

    #[test]
    fn read_header_extensions_invalid_format() {
        let (mut disk_file, mut header) =
            create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "vmdk".as_bytes());

        header.backing_file = Some(BackingFileConfig {
            path: "/test/backing".to_string(),
            format: None,
        });

        let result = QcowHeader::read_header_extensions(&mut disk_file, &mut header, None);
        assert!(matches!(
            result.unwrap_err(),
            Error::UnsupportedBackingFileFormat(_)
        ));
    }

    #[test]
    fn read_header_extensions_invalid_utf8() {
        let (mut disk_file, mut header) = create_header_with_extension(
            HEADER_EXT_BACKING_FORMAT,
            &[0xFF, 0xFE, 0xFD], // invalid UTF-8
        );

        let result = QcowHeader::read_header_extensions(&mut disk_file, &mut header, None);
        // Should fail with InvalidBackingFileName error
        assert!(matches!(
            result.unwrap_err(),
            Error::InvalidBackingFileName(_)
        ));
    }

    #[test]
    fn no_backing_file() {
        // `backing_file` is `None`
        let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, None)
            .expect("Failed to create header.");
        let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
        header
            .write_to(&mut disk_file)
            .expect("Failed to write header to shm.");
        disk_file.rewind().unwrap();
        // The maximum nesting depth is 0, which means backing file is not allowed.
        QcowFile::from_with_nesting_depth(disk_file, 0, true).unwrap();
    }

    #[test]
    fn disable_backing_file() {
        // `backing_file` is `Some`
        let header =
            QcowHeader::create_for_size_and_path(3, 0x10_0000, Some("/path/to/backing/file"))
                .expect("Failed to create header.");
        let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
        header
            .write_to(&mut disk_file)
            .expect("Failed to write header to shm.");
        disk_file.rewind().unwrap();
        // The maximum nesting depth is 0, which means backing file is not allowed.
        let res = QcowFile::from_with_nesting_depth(disk_file, 0, true);
        let err = res.unwrap_err();
        assert!(matches!(err.kind(), BlockErrorKind::Overflow));
        let source = StdError::source(&err).unwrap();
        let qcow_err = source.downcast_ref::<Error>().unwrap();
        assert!(matches!(qcow_err, Error::MaxNestingDepthExceeded));
    }

    /// Create a qcow2 file with itself as its backing file.
    ///
    /// Without configuration `max_nesting_depth`, this will cause infinite recursion when loading
    /// the file until stack overflow.
    fn new_self_referential_qcow(path: &Path) -> Result<()> {
        let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, path.to_str())?;
        let mut disk_file = RawFile::new(
            File::create(path).expect("Failed to create image file."),
            false,
        );
        header.write_to(&mut disk_file)?;
        Ok(())
    }

    #[test]
    fn max_nesting_backing() {
        let test_dir = TempDir::new_with_prefix("/tmp/ch").unwrap();
        let img_path = test_dir.as_path().join("test.img");

        new_self_referential_qcow(img_path.as_path()).unwrap();

        let err = QcowFile::from_with_nesting_depth(
            RawFile::new(
                File::open(img_path.as_path()).expect("Failed to open qcow image file"),
                false,
            ),
            MAX_NESTING_DEPTH,
            true,
        )
        .expect_err("Opening qcow file with itself as backing file should fail.");

        // This type of error is complex. For comparing easily, we can check if it contains the
        // type name after formatting.
        assert!(format!("{err:?}").contains(&format!("{:?}", Error::MaxNestingDepthExceeded)));
        // This should recursively call the function ten times before throwing an error, and the
        // error `BackingFileOpen` should also be repeated ten times.
        assert_eq!(
            format!("{err:?}")
                .matches("BackingFileOpen")
                .collect::<Vec<_>>()
                .len() as u32,
            MAX_NESTING_DEPTH,
        );
    }

    #[test]
    fn invalid_magic() {
        let invalid_header = vec![0x51u8, 0x46, 0x4a, 0xfb];
        with_basic_file(&invalid_header, |mut disk_file: RawFile| {
            QcowHeader::new(&mut disk_file).expect_err("Invalid header worked.");
        });
    }

    #[test]
    fn invalid_refcount_order() {
        let mut header = valid_header_v3();
        header[99] = 7;
        with_basic_file(&header, |disk_file: RawFile| {
            QcowFile::from(disk_file).expect_err("Invalid refcount order worked.");
        });
    }

    /// Test all valid refcount orders (0-6) can be opened.
    #[test]
    fn refcount_all_orders() {
        for order in 0..=6u8 {
            let mut header = valid_header_v3();
            header[99] = order;
            with_basic_file(&header, |disk_file: RawFile| {
                QcowFile::from(disk_file).expect("refcount order should work");
            });
        }
    }

    /// Test write/read roundtrip for all refcount orders.
    #[test]
    fn refcount_all_orders_write_read() {
        for order in 0..=6u8 {
            let mut header = valid_header_v3();
            header[99] = order;
            with_basic_file(&header, |disk_file: RawFile| {
                let mut q = QcowFile::from(disk_file).unwrap();
                let test_data = b"test data for refcount";

                // Write and read back
                q.write_all(test_data).unwrap();
                q.rewind().unwrap();
                let mut buf = vec![0u8; test_data.len()];
                q.read_exact(&mut buf).unwrap();
                assert_eq!(&buf, test_data);

                // Write to another cluster
                q.seek(SeekFrom::Start(0x10000)).unwrap();
                q.write_all(test_data).unwrap();
                q.seek(SeekFrom::Start(0x10000)).unwrap();
                q.read_exact(&mut buf).unwrap();
                assert_eq!(&buf, test_data);
            });
        }
    }

    /// Test overwrite and multi-cluster allocation for all refcount orders.
    #[test]
    fn refcount_all_orders_overwrite() {
        for order in 0..=6u8 {
            let mut header = valid_header_v3();
            header[99] = order;
            with_basic_file(&header, |disk_file: RawFile| {
                let mut q = QcowFile::from(disk_file).unwrap();

                // Write then overwrite
                q.write_all(b"initial data here!!!").unwrap();
                q.rewind().unwrap();
                let new_data = b"overwritten data!!!!";
                q.write_all(new_data).unwrap();
                q.rewind().unwrap();
                let mut buf = vec![0u8; new_data.len()];
                q.read_exact(&mut buf).unwrap();
                assert_eq!(&buf, new_data);

                // Allocate multiple clusters
                let cluster_size = 0x10000u64;
                for i in 1..4u64 {
                    q.seek(SeekFrom::Start(i * cluster_size)).unwrap();
                    q.write_all(b"cluster data").unwrap();
                }
                for i in 1..4u64 {
                    let mut cluster_buf = vec![0u8; 12];
                    q.seek(SeekFrom::Start(i * cluster_size)).unwrap();
                    q.read_exact(&mut cluster_buf).unwrap();
                    assert_eq!(&cluster_buf, b"cluster data");
                }
            });
        }
    }

    /// Test L2 cache eviction for all refcount orders.
    #[test]
    fn refcount_all_orders_l2_eviction() {
        for order in 0..=6u8 {
            let mut header = valid_header_v3();
            header[99] = order;
            with_basic_file(&header, |disk_file: RawFile| {
                let mut q = QcowFile::from(disk_file).unwrap();

                // L2 cache has 100 entries. Write to >100 regions to force eviction.
                let cluster_size = 0x10000u64;
                let l2_coverage = cluster_size * (cluster_size / 8);

                for i in 0..110u64 {
                    q.seek(SeekFrom::Start(i * l2_coverage)).unwrap();
                    q.write_all(b"eviction test").unwrap();
                }

                // Verify evicted regions can be re-read
                for i in [0u64, 1, 50, 100, 109] {
                    let mut buf = vec![0u8; 13];
                    q.seek(SeekFrom::Start(i * l2_coverage)).unwrap();
                    q.read_exact(&mut buf).unwrap();
                    assert_eq!(&buf, b"eviction test");
                }
            });
        }
    }

    /// Test sub-byte refcount read/write roundtrip with max values.
    #[test]
    fn refcount_subbyte_max_values() {
        for (bits, max_val) in [(1u64, 1u64), (2, 3), (4, 15)] {
            let file = vmm_sys_util::tempfile::TempFile::new().unwrap().into_file();
            let cluster_size = 0x10000u64;
            file.set_len(cluster_size * 2).unwrap();
            let raw = RawFile::new(file, false);
            let mut qcow_raw = QcowRawFile::from(raw, cluster_size, bits).unwrap();

            let entries = (cluster_size * 8 / bits) as usize;
            let mut table: Vec<u64> = (0..entries as u64).map(|i| i % (max_val + 1)).collect();
            table[0] = max_val;
            table[entries - 1] = max_val;

            qcow_raw.write_refcount_block(cluster_size, &table).unwrap();
            let read_table = qcow_raw.read_refcount_block(cluster_size).unwrap();

            assert_eq!(read_table.len(), entries);
            for (i, (&written, &read)) in table.iter().zip(read_table.iter()).enumerate() {
                assert_eq!(read, written & max_val, "{bits}-bit entry {i} mismatch");
            }
        }
    }

    /// Test byte-aligned refcounts with max values.
    #[test]
    fn refcount_byte_aligned_large_values() {
        for (bits, test_val) in [
            (8u64, 0xFFu64),
            (16, 0xFFFFu64),
            (32, 0xFFFF_FFFFu64),
            (64, u64::MAX),
        ] {
            let file = vmm_sys_util::tempfile::TempFile::new().unwrap().into_file();
            let cluster_size = 0x10000u64;
            file.set_len(cluster_size * 2).unwrap();
            let raw = RawFile::new(file, false);
            let mut qcow_raw = QcowRawFile::from(raw, cluster_size, bits).unwrap();

            let entries = (cluster_size * 8 / bits) as usize;
            let mut table: Vec<u64> = vec![0; entries];
            table[0] = test_val;
            table[1] = 1;
            table[entries - 1] = test_val;

            qcow_raw.write_refcount_block(cluster_size, &table).unwrap();
            let read_table = qcow_raw.read_refcount_block(cluster_size).unwrap();

            assert_eq!(read_table[0], test_val);
            assert_eq!(read_table[1], 1);
            assert_eq!(read_table[entries - 1], test_val);
        }
    }

    /// Test RefcountOverflow error when exceeding max refcount value.
    #[test]
    fn refcount_overflow_returns_error() {
        use super::refcount::Error as RefcountError;

        for (refcount_bits, max_val) in [(1u64, 1u64), (2, 3), (4, 15)] {
            let file = vmm_sys_util::tempfile::TempFile::new().unwrap().into_file();
            let cluster_size = 0x10000u64;
            let refcount_block_entries = cluster_size * 8 / refcount_bits;
            file.set_len(cluster_size * 3).unwrap();

            let raw = RawFile::new(file, false);
            let mut qcow_raw = QcowRawFile::from(raw, cluster_size, refcount_bits).unwrap();

            // Set up refcount table pointing to refcount block
            let refcount_table_offset = cluster_size;
            qcow_raw
                .file_mut()
                .seek(SeekFrom::Start(refcount_table_offset))
                .unwrap();
            qcow_raw
                .file_mut()
                .write_all(&(cluster_size * 2).to_be_bytes())
                .unwrap();

            let zeros = vec![0u64; refcount_block_entries as usize];
            qcow_raw
                .write_refcount_block(cluster_size * 2, &zeros)
                .unwrap();

            let mut refcount = RefCount::new(
                &mut qcow_raw,
                refcount_table_offset,
                1,
                refcount_block_entries,
                cluster_size,
                refcount_bits,
            )
            .unwrap();

            // Overflow should fail
            let result = refcount.set_cluster_refcount(&mut qcow_raw, 0, max_val + 1, None);
            assert!(
                matches!(result, Err(RefcountError::RefcountOverflow { .. })),
                "{refcount_bits}-bit: expected overflow error"
            );

            // Max value should not overflow
            let result = refcount.set_cluster_refcount(&mut qcow_raw, 0, max_val, None);
            assert!(
                !matches!(result, Err(RefcountError::RefcountOverflow { .. })),
                "{refcount_bits}-bit: max value should not overflow"
            );
        }
    }

    #[test]
    fn invalid_cluster_bits() {
        let mut header = valid_header_v3();
        header[23] = 3;
        with_basic_file(&header, |disk_file: RawFile| {
            QcowFile::from(disk_file).expect_err("Failed to create file.");
        });
    }

    #[test]
    fn test_header_huge_file() {
        let header = test_huge_header();
        with_basic_file(&header, |disk_file: RawFile| {
            QcowFile::from(disk_file).expect_err("Failed to create file.");
        });
    }

    #[test]
    fn test_header_crazy_file_size_rejected() {
        let mut header = valid_header_v3();
        header[24..32].copy_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1e]);
        with_basic_file(&header, |disk_file: RawFile| {
            QcowFile::from(disk_file).expect_err("Failed to create file.");
        });
    }

    #[test]
    fn test_huge_l1_table() {
        let mut header = valid_header_v3();
        header[36] = 0x12;
        with_basic_file(&header, |disk_file: RawFile| {
            QcowFile::from(disk_file).expect_err("Failed to create file.");
        });
    }

    #[test]
    fn test_header_1_tb_file_min_cluster() {
        let mut header = test_huge_header();
        header[24] = 0;
        header[26] = 1;
        header[31] = 0;
        // 1 TB with the min cluster size makes the arrays too big, it should fail.
        with_basic_file(&header, |disk_file: RawFile| {
            QcowFile::from(disk_file).expect_err("Failed to create file.");
        });
    }

    #[test]
    fn test_l2_entry_zero_flag() {
        let empty_entry: u64 = 0;
        let standard_entry: u64 = 0x1000;
        let zero_flag_entry: u64 = 0x1000 | ZERO_FLAG;
        let compressed_entry: u64 = COMPRESSED_FLAG;

        assert!(l2_entry_is_empty(empty_entry));
        assert!(!l2_entry_is_empty(standard_entry));

        assert!(!l2_entry_is_compressed(standard_entry));
        assert!(l2_entry_is_compressed(compressed_entry));

        assert!(!l2_entry_is_zero(standard_entry));
        assert!(l2_entry_is_zero(zero_flag_entry));

        // Note: l2_entry_is_zero() only checks bit 0, so compressed entries
        // must be checked first as the code does in file_read.
    }

    #[test]
    fn test_header_1_tb_file() {
        let mut header = test_huge_header();
        // reset to 1 TB size.
        header[24] = 0;
        header[26] = 1;
        header[31] = 0;
        // set cluster_bits
        header[23] = 16;
        with_basic_file(&header, |disk_file: RawFile| {
            let mut qcow = QcowFile::from(disk_file).expect("Failed to create file.");
            qcow.seek(SeekFrom::Start(0x100_0000_0000 - 8))
                .expect("Failed to seek.");
            let value = 0x0000_0040_3f00_ffffu64;
            qcow.write_all(&value.to_le_bytes())
                .expect("failed to write data");
        });
    }

    #[test]
    fn test_header_huge_num_refcounts() {
        let mut header = valid_header_v3();
        header[56..60].copy_from_slice(&[0x02, 0x00, 0xe8, 0xff]);
        with_basic_file(&header, |disk_file: RawFile| {
            QcowFile::from(disk_file).expect_err("Created disk with crazy refcount clusters");
        });
    }

    #[test]
    fn test_header_huge_refcount_offset() {
        let mut header = valid_header_v3();
        header[48..56].copy_from_slice(&[0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00]);
        with_basic_file(&header, |disk_file: RawFile| {
            QcowFile::from(disk_file).expect_err("Created disk with crazy refcount offset");
        });
    }

    #[test]
    fn write_read_start() {
        with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
            let mut q = QcowFile::from(disk_file).unwrap();
            q.write_all(b"test first bytes")
                .expect("Failed to write test string.");
            let mut buf = [0u8; 4];
            q.rewind().expect("Failed to seek.");
            q.read_exact(&mut buf).expect("Failed to read.");
            assert_eq!(&buf, b"test");
        });
    }

    #[test]
    fn write_read_start_backing_overlap() {
        let disk_file = basic_file(&valid_header_v3());
        let mut backing = QcowFile::from(disk_file).unwrap();
        backing
            .write_all(b"test first bytes")
            .expect("Failed to write test string.");
        let wrapping_disk_file = basic_file(&valid_header_v3());
        let mut wrapping = QcowFile::from(wrapping_disk_file).unwrap();
        wrapping.set_backing_file(Some(Box::new(backing)));
        wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek.");
        wrapping
            .write_all(b"TEST")
            .expect("Failed to write second test string.");
        let mut buf = [0u8; 10];
        wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek.");
        wrapping.read_exact(&mut buf).expect("Failed to read.");
        assert_eq!(&buf, b"TEST first");
    }

    #[test]
    fn offset_write_read() {
        with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
            let mut q = QcowFile::from(disk_file).unwrap();
            let b = [0x55u8; 0x1000];
            q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
            q.write_all(&b).expect("Failed to write test string.");
            let mut buf = [0u8; 4];
            q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
            q.read_exact(&mut buf).expect("Failed to read.");
            assert_eq!(buf[0], 0x55);
        });
    }

    #[test]
    fn resize_grow_within_l1() {
        with_default_file(0x10_0000, false, |mut q| {
            let original_size = q.virtual_size();
            assert_eq!(original_size, 0x10_0000);

            q.resize(original_size)
                .expect("Resize to same size should succeed");
            assert_eq!(q.virtual_size(), original_size);
        });
    }

    #[test]
    fn resize_grow_with_l1_growth() {
        let initial_size = 1024 * 1024; // 1 MB
        let new_size = 600 * 1024 * 1024; // 600 MB

        let tmp: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
        let mut q = QcowFile::new(tmp, 3, initial_size, true).unwrap();

        let original_l1_size = q.header().l1_size;
        assert_eq!(q.virtual_size(), initial_size);

        let test_data = b"Hello, QCOW resize test!";
        q.rewind().unwrap();
        q.write_all(test_data).unwrap();

        q.resize(new_size).expect("Resize should succeed");
        assert_eq!(q.virtual_size(), new_size);

        assert!(q.header().l1_size > original_l1_size);

        // Verify original data is still intact
        let mut buf = vec![0u8; test_data.len()];
        q.rewind().unwrap();
        q.read_exact(&mut buf).unwrap();
        assert_eq!(&buf, test_data);

        let new_offset = new_size - 0x10000; // 64KB before end
        q.seek(SeekFrom::Start(new_offset)).unwrap();
        let new_data = b"Data at new end!";
        q.write_all(new_data).unwrap();

        let mut buf2 = vec![0u8; new_data.len()];
        q.seek(SeekFrom::Start(new_offset)).unwrap();
        q.read_exact(&mut buf2).unwrap();
        assert_eq!(&buf2, new_data);
    }

    #[test]
    fn resize_shrink_fails() {
        with_default_file(0x10_0000, false, |mut q| {
            let original_size = q.virtual_size();
            let smaller_size = original_size / 2;

            let result = q.resize(smaller_size);
            assert!(result.is_err());
            let err = result.unwrap_err();
            assert!(matches!(err.kind(), BlockErrorKind::UnsupportedFeature));
            assert!(matches!(
                err.downcast_ref::<Error>(),
                Some(Error::ShrinkNotSupported)
            ));

            assert_eq!(q.virtual_size(), original_size);
        });
    }

    #[test]
    fn resize_with_backing_file_fails() {
        let backing_temp = TempFile::new().unwrap();
        let backing_path = backing_temp.as_path().to_str().unwrap().to_string();
        let backing_size = 1024 * 1024; // 1 MB

        {
            let backing_raw = RawFile::new(backing_temp.as_file().try_clone().unwrap(), false);
            let _backing_qcow = QcowFile::new(backing_raw, 3, backing_size, true).unwrap();
        }

        let overlay_file = TempFile::new().unwrap();
        let overlay_raw = RawFile::new(overlay_file.into_file(), false);
        let backing_config = BackingFileConfig {
            path: backing_path,
            format: Some(ImageType::Qcow2),
        };
        let mut overlay =
            QcowFile::new_from_backing(overlay_raw, 3, backing_size, &backing_config, true)
                .unwrap();

        assert_eq!(overlay.virtual_size(), backing_size);

        let result = overlay.resize(backing_size * 2);
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(matches!(err.kind(), BlockErrorKind::UnsupportedFeature));
        assert!(matches!(
            err.downcast_ref::<Error>(),
            Some(Error::ResizeWithBackingFile)
        ));

        assert_eq!(overlay.virtual_size(), backing_size);
    }

    #[test]
    fn read_beyond_backing_file_returns_zeros() {
        let backing_temp = TempFile::new().unwrap();
        let backing_path = backing_temp.as_path().to_str().unwrap().to_string();
        let backing_size = 1024 * 1024;

        {
            let backing_raw = RawFile::new(backing_temp.as_file().try_clone().unwrap(), false);
            let mut backing_qcow = QcowFile::new(backing_raw, 3, backing_size, true).unwrap();
            let data = b"BACKING_DATA";
            backing_qcow.rewind().unwrap();
            backing_qcow.write_all(data).unwrap();
            let boundary_data = [0xAAu8; 512];
            backing_qcow
                .seek(SeekFrom::Start(backing_size - 512))
                .unwrap();
            backing_qcow.write_all(&boundary_data).unwrap();
            backing_qcow.flush().unwrap();
        }

        let overlay_file = TempFile::new().unwrap();
        let overlay_raw = RawFile::new(overlay_file.into_file(), false);
        let backing_config = BackingFileConfig {
            path: backing_path,
            format: Some(ImageType::Qcow2),
        };
        let overlay_size = backing_size * 2; // 2x the backing size
        let mut overlay =
            QcowFile::new_from_backing(overlay_raw, 3, overlay_size, &backing_config, true)
                .unwrap();

        assert_eq!(overlay.virtual_size(), overlay_size);

        let mut buf = vec![0u8; 12];
        overlay.rewind().unwrap();
        overlay.read_exact(&mut buf).unwrap();
        assert_eq!(&buf, b"BACKING_DATA");

        let offset_beyond = backing_size + 4096;
        let mut beyond_buf = vec![0xFFu8; 4096];
        overlay.seek(SeekFrom::Start(offset_beyond)).unwrap();
        overlay.read_exact(&mut beyond_buf).unwrap();
        assert!(
            beyond_buf.iter().all(|&b| b == 0),
            "Read beyond backing file should return zeros"
        );

        let offset_at_boundary = backing_size - 512;
        let mut boundary_buf = vec![0xFFu8; 1024]; // 512 in backing, 512 beyond
        overlay.seek(SeekFrom::Start(offset_at_boundary)).unwrap();
        overlay.read_exact(&mut boundary_buf).unwrap();
        assert!(
            boundary_buf[..512].iter().all(|&b| b == 0xAA),
            "Portion within backing file should contain backing data"
        );
        assert!(
            boundary_buf[512..].iter().all(|&b| b == 0),
            "Portion beyond backing file should be zeros"
        );
    }

    #[test]
    fn write_zeroes_read() {
        with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
            let mut q = QcowFile::from(disk_file).unwrap();
            // Write some test data.
            let b = [0x55u8; 0x1000];
            q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
            q.write_all(&b).expect("Failed to write test string.");
            // Overwrite the test data with zeroes.
            q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
            let nwritten = q.write_zeroes(0x200).expect("Failed to write zeroes.");
            assert_eq!(nwritten, 0x200);
            // Verify that the correct part of the data was zeroed out.
            let mut buf = [0u8; 0x1000];
            q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
            q.read_exact(&mut buf).expect("Failed to read.");
            assert_eq!(buf[0], 0);
            assert_eq!(buf[0x1FF], 0);
            assert_eq!(buf[0x200], 0x55);
            assert_eq!(buf[0xFFF], 0x55);
        });
    }

    #[test]
    fn write_zeroes_full_cluster() {
        // Choose a size that is larger than a cluster.
        // valid_header uses cluster_bits = 12, which corresponds to a cluster size of 4096.
        const CHUNK_SIZE: usize = 4096 * 2 + 512;
        with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
            let mut q = QcowFile::from(disk_file).unwrap();
            // Write some test data.
            let b = [0x55u8; CHUNK_SIZE];
            q.rewind().expect("Failed to seek.");
            q.write_all(&b).expect("Failed to write test string.");
            // Overwrite the full cluster with zeroes.
            q.rewind().expect("Failed to seek.");
            let nwritten = q.write_zeroes(CHUNK_SIZE).expect("Failed to write zeroes.");
            assert_eq!(nwritten, CHUNK_SIZE);
            // Verify that the data was zeroed out.
            let mut buf = [0u8; CHUNK_SIZE];
            q.rewind().expect("Failed to seek.");
            q.read_exact(&mut buf).expect("Failed to read.");
            assert_eq!(buf[0], 0);
            assert_eq!(buf[CHUNK_SIZE - 1], 0);
        });
    }

    #[test]
    fn discard_sets_zero_flag() {
        with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
            let mut q = QcowFile::from(disk_file).unwrap();

            // Write some test data to allocate a cluster
            let test_data = [0x42u8; 4096];
            q.seek(SeekFrom::Start(0x10000)).expect("Failed to seek.");
            q.write_all(&test_data).expect("Failed to write test data.");

            // Verify data was written
            let mut buf = [0u8; 4096];
            q.seek(SeekFrom::Start(0x10000)).expect("Failed to seek.");
            q.read_exact(&mut buf).expect("Failed to read.");
            assert_eq!(buf[0], 0x42);
            assert_eq!(buf[4095], 0x42);

            // DISCARD the full cluster (via write_zeroes which calls punch_hole)
            q.seek(SeekFrom::Start(0x10000)).expect("Failed to seek.");
            let nwritten = q.write_zeroes(4096).expect("Failed to discard cluster.");
            assert_eq!(nwritten, 4096);

            // Verify reads now return zeros (due to zero flag)
            q.seek(SeekFrom::Start(0x10000)).expect("Failed to seek.");
            q.read_exact(&mut buf).expect("Failed to read.");
            assert_eq!(buf[0], 0);
            assert_eq!(buf[4095], 0);

            // Write new data to the trimmed cluster
            let new_data = [0x99u8; 4096];
            q.seek(SeekFrom::Start(0x10000)).expect("Failed to seek.");
            q.write_all(&new_data)
                .expect("Failed to write to trimmed cluster.");

            // Verify new data can be read (cluster was reallocated)
            q.seek(SeekFrom::Start(0x10000)).expect("Failed to seek.");
            q.read_exact(&mut buf).expect("Failed to read.");
            assert_eq!(buf[0], 0x99);
            assert_eq!(buf[4095], 0x99);
        });
    }

    #[test]
    fn test_header() {
        with_basic_file(&valid_header_v2(), |disk_file: RawFile| {
            let q = QcowFile::from(disk_file).unwrap();
            assert_eq!(q.virtual_size(), 0x20_0000_0000);
        });
        with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
            let q = QcowFile::from(disk_file).unwrap();
            assert_eq!(q.virtual_size(), 0x20_0000_0000);
        });
    }

    #[test]
    fn read_small_buffer() {
        with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
            let mut q = QcowFile::from(disk_file).unwrap();
            let mut b = [5u8; 16];
            q.seek(SeekFrom::Start(1000)).expect("Failed to seek.");
            q.read_exact(&mut b).expect("Failed to read.");
            assert_eq!(0, b[0]);
            assert_eq!(0, b[15]);
        });
    }

    #[test]
    fn replay_ext4() {
        with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
            let mut q = QcowFile::from(disk_file).unwrap();
            const BUF_SIZE: usize = 0x1000;
            let mut b = [0u8; BUF_SIZE];

            struct Transfer {
                pub write: bool,
                pub addr: u64,
            }

            // Write transactions from mkfs.ext4.
            let xfers: Vec<Transfer> = vec![
                Transfer {
                    write: false,
                    addr: 0xfff0000,
                },
                Transfer {
                    write: false,
                    addr: 0xfffe000,
                },
                Transfer {
                    write: false,
                    addr: 0x0,
                },
                Transfer {
                    write: false,
                    addr: 0x1000,
                },
                Transfer {
                    write: false,
                    addr: 0xffff000,
                },
                Transfer {
                    write: false,
                    addr: 0xffdf000,
                },
                Transfer {
                    write: false,
                    addr: 0xfff8000,
                },
                Transfer {
                    write: false,
                    addr: 0xffe0000,
                },
                Transfer {
                    write: false,
                    addr: 0xffce000,
                },
                Transfer {
                    write: false,
                    addr: 0xffb6000,
                },
                Transfer {
                    write: false,
                    addr: 0xffab000,
                },
                Transfer {
                    write: false,
                    addr: 0xffa4000,
                },
                Transfer {
                    write: false,
                    addr: 0xff8e000,
                },
                Transfer {
                    write: false,
                    addr: 0xff86000,
                },
                Transfer {
                    write: false,
                    addr: 0xff84000,
                },
                Transfer {
                    write: false,
                    addr: 0xff89000,
                },
                Transfer {
                    write: false,
                    addr: 0xfe7e000,
                },
                Transfer {
                    write: false,
                    addr: 0x100000,
                },
                Transfer {
                    write: false,
                    addr: 0x3000,
                },
                Transfer {
                    write: false,
                    addr: 0x7000,
                },
                Transfer {
                    write: false,
                    addr: 0xf000,
                },
                Transfer {
                    write: false,
                    addr: 0x2000,
                },
                Transfer {
                    write: false,
                    addr: 0x4000,
                },
                Transfer {
                    write: false,
                    addr: 0x5000,
                },
                Transfer {
                    write: false,
                    addr: 0x6000,
                },
                Transfer {
                    write: false,
                    addr: 0x8000,
                },
                Transfer {
                    write: false,
                    addr: 0x9000,
                },
                Transfer {
                    write: false,
                    addr: 0xa000,
                },
                Transfer {
                    write: false,
                    addr: 0xb000,
                },
                Transfer {
                    write: false,
                    addr: 0xc000,
                },
                Transfer {
                    write: false,
                    addr: 0xd000,
                },
                Transfer {
                    write: false,
                    addr: 0xe000,
                },
                Transfer {
                    write: false,
                    addr: 0x10000,
                },
                Transfer {
                    write: false,
                    addr: 0x11000,
                },
                Transfer {
                    write: false,
                    addr: 0x12000,
                },
                Transfer {
                    write: false,
                    addr: 0x13000,
                },
                Transfer {
                    write: false,
                    addr: 0x14000,
                },
                Transfer {
                    write: false,
                    addr: 0x15000,
                },
                Transfer {
                    write: false,
                    addr: 0x16000,
                },
                Transfer {
                    write: false,
                    addr: 0x17000,
                },
                Transfer {
                    write: false,
                    addr: 0x18000,
                },
                Transfer {
                    write: false,
                    addr: 0x19000,
                },
                Transfer {
                    write: false,
                    addr: 0x1a000,
                },
                Transfer {
                    write: false,
                    addr: 0x1b000,
                },
                Transfer {
                    write: false,
                    addr: 0x1c000,
                },
                Transfer {
                    write: false,
                    addr: 0x1d000,
                },
                Transfer {
                    write: false,
                    addr: 0x1e000,
                },
                Transfer {
                    write: false,
                    addr: 0x1f000,
                },
                Transfer {
                    write: false,
                    addr: 0x21000,
                },
                Transfer {
                    write: false,
                    addr: 0x22000,
                },
                Transfer {
                    write: false,
                    addr: 0x24000,
                },
                Transfer {
                    write: false,
                    addr: 0x40000,
                },
                Transfer {
                    write: false,
                    addr: 0x0,
                },
                Transfer {
                    write: false,
                    addr: 0x3000,
                },
                Transfer {
                    write: false,
                    addr: 0x7000,
                },
                Transfer {
                    write: false,
                    addr: 0x0,
                },
                Transfer {
                    write: false,
                    addr: 0x1000,
                },
                Transfer {
                    write: false,
                    addr: 0x2000,
                },
                Transfer {
                    write: false,
                    addr: 0x3000,
                },
                Transfer {
                    write: false,
                    addr: 0x0,
                },
                Transfer {
                    write: false,
                    addr: 0x449000,
                },
                Transfer {
                    write: false,
                    addr: 0x48000,
                },
                Transfer {
                    write: false,
                    addr: 0x48000,
                },
                Transfer {
                    write: false,
                    addr: 0x448000,
                },
                Transfer {
                    write: false,
                    addr: 0x44a000,
                },
                Transfer {
                    write: false,
                    addr: 0x48000,
                },
                Transfer {
                    write: false,
                    addr: 0x48000,
                },
                Transfer {
                    write: true,
                    addr: 0x0,
                },
                Transfer {
                    write: true,
                    addr: 0x448000,
                },
                Transfer {
                    write: true,
                    addr: 0x449000,
                },
                Transfer {
                    write: true,
                    addr: 0x44a000,
                },
                Transfer {
                    write: true,
                    addr: 0xfff0000,
                },
                Transfer {
                    write: true,
                    addr: 0xfff1000,
                },
                Transfer {
                    write: true,
                    addr: 0xfff2000,
                },
                Transfer {
                    write: true,
                    addr: 0xfff3000,
                },
                Transfer {
                    write: true,
                    addr: 0xfff4000,
                },
                Transfer {
                    write: true,
                    addr: 0xfff5000,
                },
                Transfer {
                    write: true,
                    addr: 0xfff6000,
                },
                Transfer {
                    write: true,
                    addr: 0xfff7000,
                },
                Transfer {
                    write: true,
                    addr: 0xfff8000,
                },
                Transfer {
                    write: true,
                    addr: 0xfff9000,
                },
                Transfer {
                    write: true,
                    addr: 0xfffa000,
                },
                Transfer {
                    write: true,
                    addr: 0xfffb000,
                },
                Transfer {
                    write: true,
                    addr: 0xfffc000,
                },
                Transfer {
                    write: true,
                    addr: 0xfffd000,
                },
                Transfer {
                    write: true,
                    addr: 0xfffe000,
                },
                Transfer {
                    write: true,
                    addr: 0xffff000,
                },
            ];

            for xfer in &xfers {
                q.seek(SeekFrom::Start(xfer.addr)).expect("Failed to seek.");
                if xfer.write {
                    q.write_all(&b).expect("Failed to write.");
                } else {
                    let read_count: usize = q.read(&mut b).expect("Failed to read.");
                    assert_eq!(read_count, BUF_SIZE);
                }
            }
        });
    }

    #[test]
    fn combo_write_read() {
        combo_write_read_common(false);
    }

    #[test]
    fn combo_write_read_direct() {
        combo_write_read_common(true);
    }

    fn combo_write_read_common(direct: bool) {
        with_default_file(1024 * 1024 * 1024 * 256, direct, |mut qcow_file| {
            const NUM_BLOCKS: usize = 555;
            const BLOCK_SIZE: usize = 0x1_0000;
            const OFFSET: usize = 0x1_0000_0020;
            let data = [0x55u8; BLOCK_SIZE];
            let mut readback = [0u8; BLOCK_SIZE];
            for i in 0..NUM_BLOCKS {
                let seek_offset = OFFSET + i * BLOCK_SIZE;
                qcow_file
                    .seek(SeekFrom::Start(seek_offset as u64))
                    .expect("Failed to seek.");
                let nwritten = qcow_file.write(&data).expect("Failed to write test data.");
                assert_eq!(nwritten, BLOCK_SIZE);
                // Read back the data to check it was written correctly.
                qcow_file
                    .seek(SeekFrom::Start(seek_offset as u64))
                    .expect("Failed to seek.");
                let nread = qcow_file.read(&mut readback).expect("Failed to read.");
                assert_eq!(nread, BLOCK_SIZE);
                for (orig, read) in data.iter().zip(readback.iter()) {
                    assert_eq!(orig, read);
                }
            }
            // Check that address 0 is still zeros.
            qcow_file.rewind().expect("Failed to seek.");
            let nread = qcow_file.read(&mut readback).expect("Failed to read.");
            assert_eq!(nread, BLOCK_SIZE);
            for read in readback.iter() {
                assert_eq!(*read, 0);
            }
            // Check the data again after the writes have happened.
            for i in 0..NUM_BLOCKS {
                let seek_offset = OFFSET + i * BLOCK_SIZE;
                qcow_file
                    .seek(SeekFrom::Start(seek_offset as u64))
                    .expect("Failed to seek.");
                let nread = qcow_file.read(&mut readback).expect("Failed to read.");
                assert_eq!(nread, BLOCK_SIZE);
                for (orig, read) in data.iter().zip(readback.iter()) {
                    assert_eq!(orig, read);
                }
            }
        });
    }

    fn seek_cur(file: &mut QcowFile) -> u64 {
        file.stream_position().unwrap()
    }

    #[test]
    fn seek_data() {
        seek_data_common(false);
    }

    #[test]
    fn seek_data_direct() {
        seek_data_common(true);
    }

    fn seek_data_common(direct: bool) {
        with_default_file(0x30000, direct, |mut file| {
            // seek_data at or after the end of the file should return None
            assert_eq!(file.seek_data(0x10000).unwrap(), None);
            assert_eq!(seek_cur(&mut file), 0);
            assert_eq!(file.seek_data(0x10001).unwrap(), None);
            assert_eq!(seek_cur(&mut file), 0);

            // Write some data to [0x10000, 0x20000)
            let b = [0x55u8; 0x10000];
            file.seek(SeekFrom::Start(0x10000)).unwrap();
            file.write_all(&b).unwrap();
            assert_eq!(file.seek_data(0).unwrap(), Some(0x10000));
            assert_eq!(seek_cur(&mut file), 0x10000);

            // seek_data within data should return the same offset
            assert_eq!(file.seek_data(0x10000).unwrap(), Some(0x10000));
            assert_eq!(seek_cur(&mut file), 0x10000);
            assert_eq!(file.seek_data(0x10001).unwrap(), Some(0x10001));
            assert_eq!(seek_cur(&mut file), 0x10001);
            assert_eq!(file.seek_data(0x1FFFF).unwrap(), Some(0x1FFFF));
            assert_eq!(seek_cur(&mut file), 0x1FFFF);

            assert_eq!(file.seek_data(0).unwrap(), Some(0x10000));
            assert_eq!(seek_cur(&mut file), 0x10000);
            assert_eq!(file.seek_data(0x1FFFF).unwrap(), Some(0x1FFFF));
            assert_eq!(seek_cur(&mut file), 0x1FFFF);
            assert_eq!(file.seek_data(0x20000).unwrap(), None);
            assert_eq!(seek_cur(&mut file), 0x1FFFF);
        });
    }

    #[test]
    fn seek_hole() {
        seek_hole_common(false);
    }

    #[test]
    fn seek_hole_direct() {
        seek_hole_common(true);
    }

    fn seek_hole_common(direct: bool) {
        with_default_file(0x30000, direct, |mut file| {
            // File consisting entirely of a hole
            assert_eq!(file.seek_hole(0).unwrap(), Some(0));
            assert_eq!(seek_cur(&mut file), 0);
            assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
            assert_eq!(seek_cur(&mut file), 0xFFFF);

            // seek_hole at or after the end of the file should return None
            file.rewind().unwrap();
            assert_eq!(file.seek_hole(0x30000).unwrap(), None);
            assert_eq!(seek_cur(&mut file), 0);
            assert_eq!(file.seek_hole(0x30001).unwrap(), None);
            assert_eq!(seek_cur(&mut file), 0);

            // Write some data to [0x10000, 0x20000)
            let b = [0x55u8; 0x10000];
            file.seek(SeekFrom::Start(0x10000)).unwrap();
            file.write_all(&b).unwrap();

            // seek_hole within a hole should return the same offset
            assert_eq!(file.seek_hole(0).unwrap(), Some(0));
            assert_eq!(seek_cur(&mut file), 0);
            assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
            assert_eq!(seek_cur(&mut file), 0xFFFF);

            // seek_hole within data should return the next hole
            file.rewind().unwrap();
            assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
            assert_eq!(seek_cur(&mut file), 0x20000);
            file.rewind().unwrap();
            assert_eq!(file.seek_hole(0x10001).unwrap(), Some(0x20000));
            assert_eq!(seek_cur(&mut file), 0x20000);
            file.rewind().unwrap();
            assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
            assert_eq!(seek_cur(&mut file), 0x20000);
            file.rewind().unwrap();
            assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
            assert_eq!(seek_cur(&mut file), 0xFFFF);
            file.rewind().unwrap();
            assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
            assert_eq!(seek_cur(&mut file), 0x20000);
            file.rewind().unwrap();
            assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
            assert_eq!(seek_cur(&mut file), 0x20000);
            file.rewind().unwrap();
            assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x20000));
            assert_eq!(seek_cur(&mut file), 0x20000);
            file.rewind().unwrap();
            assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x20001));
            assert_eq!(seek_cur(&mut file), 0x20001);

            // seek_hole at EOF should return None
            file.rewind().unwrap();
            assert_eq!(file.seek_hole(0x30000).unwrap(), None);
            assert_eq!(seek_cur(&mut file), 0);

            // Write some data to [0x20000, 0x30000)
            file.seek(SeekFrom::Start(0x20000)).unwrap();
            file.write_all(&b).unwrap();

            // seek_hole within [0x20000, 0x30000) should now find the hole at EOF
            assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x30000));
            assert_eq!(seek_cur(&mut file), 0x30000);
            file.rewind().unwrap();
            assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x30000));
            assert_eq!(seek_cur(&mut file), 0x30000);
            file.rewind().unwrap();
            assert_eq!(file.seek_hole(0x30000).unwrap(), None);
            assert_eq!(seek_cur(&mut file), 0);
        });
    }

    #[test]
    fn rebuild_refcounts() {
        with_basic_file(&valid_header_v3(), |mut disk_file: RawFile| {
            let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
            let cluster_size = 65536;
            let refcount_bits = 1u64 << header.refcount_order;
            let mut raw_file = QcowRawFile::from(disk_file, cluster_size, refcount_bits)
                .expect("Failed to create QcowRawFile.");
            QcowFile::rebuild_refcounts(&mut raw_file, header)
                .expect("Failed to rebuild recounts.");
        });
    }

    // Helper to create a v3 header with specific incompatible feature bits set
    fn header_v3_with_incompat_features(features: u64) -> Vec<u8> {
        let mut header = valid_header_v3();
        // incompatible_features is at offset 72, big-endian u64
        header[72..80].copy_from_slice(&features.to_be_bytes());
        header
    }

    // Helper to create a v3 header with specific autoclear feature bits set
    fn header_v3_with_autoclear_features(features: u64) -> Vec<u8> {
        let mut header = valid_header_v3();
        let offset = AUTOCLEAR_FEATURES_OFFSET as usize;
        header[offset..offset + 8].copy_from_slice(&features.to_be_bytes());
        header
    }

    #[test]
    fn accept_incompat_dirty_bit() {
        let header = header_v3_with_incompat_features(1 << 0);
        with_basic_file(&header, |disk_file: RawFile| {
            let result = QcowFile::from(disk_file);
            assert!(
                result.is_ok(),
                "Expected dirty bit to be accepted, got: {result:?}"
            );
        });
    }

    #[test]
    fn reject_corrupt_bit_for_writable_open() {
        // Bit 1: corrupt - image metadata is corrupted
        let header = header_v3_with_incompat_features(1 << 1);
        with_basic_file(&header, |disk_file: RawFile| {
            let result = QcowFile::from(disk_file);
            assert!(result.is_err());
            let err = result.unwrap_err();
            assert!(
                matches!(err.kind(), BlockErrorKind::CorruptImage),
                "Expected CorruptImage error, got: {err:?}"
            );
        });
    }

    #[test]
    fn reject_unsupported_incompat_external_data_bit() {
        // Bit 2: external data file
        let header = header_v3_with_incompat_features(1 << 2);
        with_basic_file(&header, |disk_file: RawFile| {
            let result = QcowFile::from(disk_file);
            assert!(result.is_err());
            let err = result.unwrap_err();
            assert!(matches!(err.kind(), BlockErrorKind::UnsupportedFeature));
            let source = StdError::source(&err).unwrap();
            let qcow_err = source.downcast_ref::<Error>().unwrap();
            assert!(
                matches!(qcow_err, Error::UnsupportedFeature(v) if v.to_string().contains("external")),
                "Expected UnsupportedFeature error mentioning external, got: {err:?}"
            );
        });
    }

    #[test]
    fn reject_unsupported_incompat_extended_l2_bit() {
        // Bit 4: extended L2 entries
        let header = header_v3_with_incompat_features(1 << 4);
        with_basic_file(&header, |disk_file: RawFile| {
            let result = QcowFile::from(disk_file);
            assert!(result.is_err());
            let err = result.unwrap_err();
            assert!(matches!(err.kind(), BlockErrorKind::UnsupportedFeature));
            let source = StdError::source(&err).unwrap();
            let qcow_err = source.downcast_ref::<Error>().unwrap();
            assert!(
                matches!(qcow_err, Error::UnsupportedFeature(v) if v.to_string().contains("extended")),
                "Expected UnsupportedFeature error mentioning extended, got: {err:?}"
            );
        });
    }

    #[test]
    fn reject_multiple_unsupported_incompat_bits() {
        // Multiple unsupported bits: external data (2) + extended L2 (4)
        let header = header_v3_with_incompat_features((1 << 2) | (1 << 4));
        with_basic_file(&header, |disk_file: RawFile| {
            let result = QcowFile::from(disk_file);
            assert!(result.is_err());
            assert!(matches!(
                result.unwrap_err().kind(),
                BlockErrorKind::UnsupportedFeature
            ));
        });
    }

    #[test]
    fn reject_unknown_incompat_bit() {
        // Unknown bit 5 (not defined in spec)
        let header = header_v3_with_incompat_features(1 << 5);
        with_basic_file(&header, |disk_file: RawFile| {
            let result = QcowFile::from(disk_file);
            assert!(result.is_err());
            let err = result.unwrap_err();
            assert!(matches!(err.kind(), BlockErrorKind::UnsupportedFeature));
            let source = StdError::source(&err).unwrap();
            let qcow_err = source.downcast_ref::<Error>().unwrap();
            assert!(
                matches!(qcow_err, Error::UnsupportedFeature(v) if v.to_string().contains("unknown")),
                "Expected UnsupportedFeature error mentioning unknown, got: {err:?}"
            );
        });
    }

    #[test]
    fn dirty_bit_set_on_open_cleared_on_close_v3() {
        // Test that the dirty bit is set when a v3 image is opened and cleared when it's closed
        let header = valid_header_v3();
        with_basic_file(&header, |mut disk_file: RawFile| {
            // Verify dirty bit is not set initially
            disk_file
                .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64))
                .unwrap();
            let features_before = u64::read_be(&mut disk_file).unwrap();
            assert_eq!(
                features_before & IncompatFeatures::DIRTY.bits(),
                0,
                "Dirty bit should not be set initially"
            );

            // Open the file - this should set the dirty bit
            disk_file.rewind().unwrap();
            {
                let qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();

                // Verify dirty bit is set while file is open
                disk_file
                    .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64))
                    .unwrap();
                let features_during = u64::read_be(&mut disk_file).unwrap();
                assert_ne!(
                    features_during & IncompatFeatures::DIRTY.bits(),
                    0,
                    "Dirty bit should be set while file is open"
                );

                drop(qcow); // Close the file
            }

            // Verify dirty bit is cleared after close
            disk_file
                .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64))
                .unwrap();
            let features_after = u64::read_be(&mut disk_file).unwrap();
            assert_eq!(
                features_after & IncompatFeatures::DIRTY.bits(),
                0,
                "Dirty bit should be cleared after close"
            );
        });
    }

    #[test]
    fn dirty_bit_not_used_for_v2() {
        // Test that v2 images don't use the dirty bit (no incompatible_features field)
        let header = valid_header_v2();
        with_basic_file(&header, |mut disk_file: RawFile| {
            // Open and close v2 file - should work without touching offset 72
            disk_file.rewind().unwrap();
            let qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
            assert_eq!(qcow.header.version, 2, "Should be a v2 file");
            drop(qcow);
        });
    }

    #[test]
    fn dirty_bit_not_set_for_readonly_v3() {
        // Test that read-only v3 files don't set the dirty bit (e.g., backing files)
        let header = valid_header_v3();

        // Create a temp file with a valid v3 qcow header
        let temp_file = TempFile::new().unwrap();
        let temp_path = temp_file.as_path().to_owned();
        {
            let mut file = temp_file.as_file().try_clone().unwrap();
            file.write_all(&header).unwrap();
            file.set_len(0x1_0000_0000).unwrap();
        }

        // Open the file read-only
        let readonly_file = OpenOptions::new()
            .read(true)
            .write(false)
            .open(&temp_path)
            .unwrap();
        let raw_file = RawFile::new(readonly_file, false);

        // Verify the file is detected as read-only
        assert!(
            !raw_file.is_writable(),
            "File should be detected as read-only"
        );

        // Open as QcowFile - should not set dirty bit for read-only files
        let qcow = QcowFile::from(raw_file).unwrap();
        assert!(
            !qcow.raw_file.file().is_writable(),
            "File should be read-only"
        );

        // Verify dirty bit was not written to disk
        let verify_file = OpenOptions::new().read(true).open(&temp_path).unwrap();
        let mut verify_raw = RawFile::new(verify_file, false);
        verify_raw
            .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64))
            .unwrap();
        let features = u64::read_be(&mut verify_raw).unwrap();
        assert_eq!(
            features & IncompatFeatures::DIRTY.bits(),
            0,
            "Dirty bit should not be written for read-only files"
        );
    }

    #[test]
    fn autoclear_features_cleared_on_open() {
        let header = header_v3_with_autoclear_features(0xFFFF_FFFF_FFFF_FFFF);
        with_basic_file(&header, |mut disk_file: RawFile| {
            disk_file
                .seek(SeekFrom::Start(AUTOCLEAR_FEATURES_OFFSET))
                .unwrap();
            let features_before = u64::read_be(&mut disk_file).unwrap();
            assert_eq!(
                features_before, 0xFFFF_FFFF_FFFF_FFFF,
                "Autoclear features should be set initially"
            );

            disk_file.rewind().unwrap();
            {
                let _qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
            }

            disk_file
                .seek(SeekFrom::Start(AUTOCLEAR_FEATURES_OFFSET))
                .unwrap();
            let features_after = u64::read_be(&mut disk_file).unwrap();
            assert_eq!(
                features_after, 0,
                "Autoclear features should be cleared after open for write"
            );
        });
    }

    #[test]
    fn autoclear_features_not_cleared_for_readonly() {
        let header = header_v3_with_autoclear_features(0xFFFF_FFFF_FFFF_FFFF);

        let temp_file = TempFile::new().unwrap();
        let temp_path = temp_file.as_path().to_owned();
        {
            let mut file = temp_file.as_file().try_clone().unwrap();
            file.write_all(&header).unwrap();
            file.set_len(0x1_0000_0000).unwrap();
        }

        let readonly_file = OpenOptions::new()
            .read(true)
            .write(false)
            .open(&temp_path)
            .unwrap();
        let raw_file = RawFile::new(readonly_file, false);
        let _qcow = QcowFile::from(raw_file).unwrap();
        drop(_qcow);

        let verify_file = OpenOptions::new().read(true).open(&temp_path).unwrap();
        let mut verify_raw = RawFile::new(verify_file, false);
        verify_raw
            .seek(SeekFrom::Start(AUTOCLEAR_FEATURES_OFFSET))
            .unwrap();
        let features = u64::read_be(&mut verify_raw).unwrap();
        assert_eq!(
            features, 0xFFFF_FFFF_FFFF_FFFF,
            "Autoclear features should NOT be cleared for read-only files"
        );
    }

    #[test]
    fn autoclear_features_v2_ignored() {
        let header = valid_header_v2();
        with_basic_file(&header, |mut disk_file: RawFile| {
            disk_file.rewind().unwrap();
            let qcow = QcowFile::from(disk_file).unwrap();
            assert_eq!(qcow.header.version, 2);
            assert_eq!(qcow.header.autoclear_features, 0);
        });
    }

    #[test]
    fn corrupt_image_rejected_for_write() {
        // Test that a corrupt image cannot be opened for writing
        let header = header_v3_with_incompat_features(IncompatFeatures::CORRUPT.bits());
        with_basic_file(&header, |disk_file: RawFile| {
            assert!(disk_file.is_writable(), "File should be writable");

            let result = QcowFile::from(disk_file);
            assert!(result.is_err());
            let err = result.unwrap_err();
            assert!(
                matches!(err.kind(), BlockErrorKind::CorruptImage),
                "Expected CorruptImage error, got: {err:?}"
            );
        });
    }

    #[test]
    fn corrupt_image_allowed_readonly() {
        // Test that a corrupt image can be opened read-only
        let header = header_v3_with_incompat_features(IncompatFeatures::CORRUPT.bits());

        // Create a temp file with the corrupt header
        let temp_file = TempFile::new().unwrap();
        let temp_path = temp_file.as_path().to_owned();
        {
            let mut file = temp_file.as_file().try_clone().unwrap();
            file.write_all(&header).unwrap();
            file.set_len(0x1_0000_0000).unwrap();
        }

        let readonly_file = OpenOptions::new()
            .read(true)
            .write(false)
            .open(&temp_path)
            .unwrap();
        let raw_file = RawFile::new(readonly_file, false);
        assert!(!raw_file.is_writable(), "File should be read-only");

        let result = QcowFile::from(raw_file);
        assert!(
            result.is_ok(),
            "Corrupt image should be openable read-only, got: {:?}",
            result.err()
        );

        let qcow = result.unwrap();
        assert!(qcow.header.is_corrupt(), "Corrupt bit should be set");
    }

    #[test]
    fn set_corrupt_bit() {
        // Test that set_corrupt_bit correctly sets the corrupt bit
        let header = valid_header_v3();
        with_basic_file(&header, |mut disk_file: RawFile| {
            let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();

            assert!(!qcow.header.is_corrupt(), "Should not be corrupt initially");

            qcow.header
                .set_corrupt_bit(qcow.raw_file.file_mut())
                .unwrap();

            // Verify in memory
            assert!(qcow.header.is_corrupt(), "Should be corrupt after set");

            // Verify on disk
            disk_file
                .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64))
                .unwrap();
            let features = u64::read_be(&mut disk_file).unwrap();
            assert!(
                IncompatFeatures::from_bits_retain(features).contains(IncompatFeatures::CORRUPT),
                "Corrupt bit should be set on disk"
            );
        });
    }

    #[test]
    fn corrupt_bit_persists_with_dirty() {
        // Test that both corrupt and dirty bits can coexist
        let header = header_v3_with_incompat_features(
            IncompatFeatures::CORRUPT.bits() | IncompatFeatures::DIRTY.bits(),
        );

        let temp_file = TempFile::new().unwrap();
        let temp_path = temp_file.as_path().to_owned();
        {
            let mut file = temp_file.as_file().try_clone().unwrap();
            file.write_all(&header).unwrap();
            file.set_len(0x1_0000_0000).unwrap();
        }

        // Writable would be rejected due to corrupt bit
        let readonly_file = OpenOptions::new()
            .read(true)
            .write(false)
            .open(&temp_path)
            .unwrap();
        let raw_file = RawFile::new(readonly_file, false);

        let qcow = QcowFile::from(raw_file).unwrap();

        let features = IncompatFeatures::from_bits_truncate(qcow.header.incompatible_features);
        assert!(
            features.contains(IncompatFeatures::CORRUPT),
            "Corrupt bit should be set"
        );
        assert!(
            features.contains(IncompatFeatures::DIRTY),
            "Dirty bit should also be set"
        );
    }

    /// Helper to check if corrupt bit is set on disk by re-reading the header
    fn is_corrupt_on_disk(disk_file: &mut RawFile) -> bool {
        disk_file.rewind().unwrap();
        QcowHeader::new(disk_file).unwrap().is_corrupt()
    }

    /// Helper to clear the corrupt bit on disk while preserving other bits
    fn clear_corrupt_bit_on_disk(disk_file: &mut RawFile) {
        disk_file
            .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64))
            .unwrap();
        let features = u64::read_be(disk_file).unwrap();
        let mut flags = IncompatFeatures::from_bits_retain(features);
        flags.remove(IncompatFeatures::CORRUPT);
        disk_file
            .seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64))
            .unwrap();
        u64::write_be(disk_file, flags.bits()).unwrap();
        assert!(
            !is_corrupt_on_disk(disk_file),
            "Corrupt bit should be cleared"
        );
    }

    /// Helper to corrupt L1 entry by making L2 table address unaligned.
    ///
    /// Returns true if corruption was applied, i.e. the L1 entry was allocated.
    fn corrupt_l1_entry(disk_file: &mut RawFile) -> bool {
        let l1_table_offset = 0x0004_0000u64;
        disk_file.seek(SeekFrom::Start(l1_table_offset)).unwrap();
        let l1_entry = u64::read_be(disk_file).unwrap();
        if l1_entry != 0 {
            let unaligned = l1_entry | 0x200; // Make unaligned
            disk_file.seek(SeekFrom::Start(l1_table_offset)).unwrap();
            u64::write_be(disk_file, unaligned).unwrap();
            disk_file.sync_all().unwrap();
            true
        } else {
            false
        }
    }

    /// Helper to corrupt L2 entry by making cluster address unaligned.
    ///
    /// Returns true if corruption was applied, i.e. an allocated non-compressed
    /// L2 entry was found.
    fn corrupt_l2_entry(disk_file: &mut RawFile) -> bool {
        let l1_table_offset = 0x0004_0000u64;
        disk_file.seek(SeekFrom::Start(l1_table_offset)).unwrap();
        let l1_entry = u64::read_be(disk_file).unwrap();
        if l1_entry == 0 {
            return false;
        }
        let l2_table_addr = l1_entry & L1_TABLE_OFFSET_MASK;
        disk_file.seek(SeekFrom::Start(l2_table_addr)).unwrap();
        let l2_entry = u64::read_be(disk_file).unwrap();
        if l2_entry != 0 && !l2_entry_is_compressed(l2_entry) {
            let unaligned = l2_entry | 0x200;
            disk_file.seek(SeekFrom::Start(l2_table_addr)).unwrap();
            u64::write_be(disk_file, unaligned).unwrap();
            disk_file.sync_all().unwrap();
            true
        } else {
            false
        }
    }

    /// Asserts that read on corrupted disk sets the corrupt bit
    fn assert_corruption_on_read(disk_file: &mut RawFile) {
        clear_corrupt_bit_on_disk(disk_file);

        disk_file.rewind().unwrap();
        let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
        let mut buf = [0u8; 16];
        let result = qcow.read(&mut buf);

        assert_eq!(
            result.map_err(|e| e.raw_os_error()),
            Err(Some(libc::EIO)),
            "read should fail with EIO on corrupted image"
        );
        assert!(
            is_corrupt_on_disk(disk_file),
            "Corrupt bit should be set after read"
        );
    }

    /// Asserts that write on corrupted disk sets the corrupt bit
    fn assert_corruption_on_write(disk_file: &mut RawFile) {
        clear_corrupt_bit_on_disk(disk_file);

        disk_file.rewind().unwrap();
        let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
        let result = qcow.write_all(b"overwrite");

        assert_eq!(
            result.map_err(|e| e.raw_os_error()),
            Err(Some(libc::EIO)),
            "write should fail with EIO on corrupted image"
        );
        assert!(
            is_corrupt_on_disk(disk_file),
            "Corrupt bit should be set after write"
        );
    }

    #[test]
    fn corrupt_bit_on_unaligned_l2_address() {
        let header = valid_header_v3();
        with_basic_file(&header, |mut disk_file: RawFile| {
            {
                let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
                qcow.write_all(b"test data").unwrap();
            }

            assert!(
                corrupt_l1_entry(&mut disk_file),
                "Failed to corrupt L1 entry - was data written?"
            );
            assert_corruption_on_read(&mut disk_file);
        });
    }

    #[test]
    fn corrupt_bit_on_unaligned_cluster_address_read() {
        let header = valid_header_v3();
        with_basic_file(&header, |mut disk_file: RawFile| {
            {
                let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
                qcow.write_all(b"test data to allocate cluster").unwrap();
            }

            assert!(
                corrupt_l2_entry(&mut disk_file),
                "Failed to corrupt L2 entry - was cluster allocated?"
            );
            assert_corruption_on_read(&mut disk_file);
        });
    }

    #[test]
    fn corrupt_bit_on_unaligned_cluster_address_write() {
        let header = valid_header_v3();
        with_basic_file(&header, |mut disk_file: RawFile| {
            {
                let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
                qcow.write_all(b"test data to allocate cluster").unwrap();
            }

            assert!(
                corrupt_l2_entry(&mut disk_file),
                "Failed to corrupt L2 entry - was cluster allocated?"
            );
            assert_corruption_on_write(&mut disk_file);
        });
    }

    #[test]
    fn corrupt_bit_not_set_on_normal_operations() {
        let header = valid_header_v3();
        with_basic_file(&header, |mut disk_file: RawFile| {
            {
                let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();

                qcow.write_all(b"test data 1234567890").unwrap();
                qcow.seek(SeekFrom::Start(0)).unwrap();

                let mut buf = [0u8; 20];
                qcow.read_exact(&mut buf).unwrap();
                assert_eq!(&buf, b"test data 1234567890");

                qcow.seek(SeekFrom::Start(0x10000)).unwrap();
                qcow.write_all(b"more data").unwrap();

                qcow.flush().unwrap();
            }

            assert!(
                !is_corrupt_on_disk(&mut disk_file),
                "Corrupt bit should NOT be set after normal operations"
            );
        });
    }

    #[test]
    fn corrupt_bit_v2_image_not_affected() {
        let header = valid_header_v2();
        with_basic_file(&header, |mut disk_file: RawFile| {
            {
                let mut qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
                qcow.write_all(b"test data").unwrap();
                qcow.seek(SeekFrom::Start(0)).unwrap();
                let mut buf = [0u8; 9];
                qcow.read_exact(&mut buf).unwrap();
                assert_eq!(&buf, b"test data");
            }

            disk_file.rewind().unwrap();
            let qcow = QcowFile::from(disk_file.try_clone().unwrap()).unwrap();
            assert_eq!(qcow.header.version, 2);
        });
    }

    #[test]
    fn test_compressed_read() {
        let cluster_size = 65536usize;
        let data: Vec<u8> = (0..=255).cycle().take(cluster_size).collect();
        let temp = TempFile::new().unwrap();
        {
            let raw_file = RawFile::new(temp.as_file().try_clone().unwrap(), false);
            let mut qcow = QcowFile::new(raw_file, 3, 100 * 1024 * 1024, false).unwrap();
            qcow.seek(SeekFrom::Start(0)).unwrap();
            qcow.write_all(&data).unwrap();
            qcow.flush().unwrap();
        }

        compress_allocated_clusters(&mut temp.as_file().try_clone().unwrap());

        let raw_file = RawFile::new(temp.as_file().try_clone().unwrap(), false);
        let mut qcow = QcowFile::from(raw_file).unwrap();
        qcow.seek(SeekFrom::Start(0)).unwrap();
        let mut buf = vec![0u8; cluster_size];
        qcow.read_exact(&mut buf).unwrap();
        assert_eq!(buf, data);
    }
}
