summaryrefslogtreecommitdiffstats
path: root/src/VBox/Storage/QCOW.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Storage/QCOW.cpp')
-rw-r--r--src/VBox/Storage/QCOW.cpp2572
1 files changed, 2572 insertions, 0 deletions
diff --git a/src/VBox/Storage/QCOW.cpp b/src/VBox/Storage/QCOW.cpp
new file mode 100644
index 00000000..b4239796
--- /dev/null
+++ b/src/VBox/Storage/QCOW.cpp
@@ -0,0 +1,2572 @@
+/* $Id: QCOW.cpp $ */
+/** @file
+ * QCOW - QCOW Disk image.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_QCOW
+#include <VBox/vd-plugin.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/alloc.h>
+#include <iprt/path.h>
+#include <iprt/list.h>
+#include <iprt/zip.h>
+
+#include "VDBackends.h"
+#include "VDBackendsInline.h"
+
+/** @page pg_storage_qcow QCOW Storage Backend
+ * The QCOW backend implements support for the qemu copy on write format (short QCOW).
+ *
+ * The official specification for qcow is available at
+ * https://github.com/qemu/qemu/blob/master/docs/interop/qcow2.txt version 2 and 3.
+ * For version 1 there is no official specification available but the format is described
+ * at http://people.gnome.org/~markmc/qcow-image-format-version-1.html.
+ *
+ * Missing things to implement:
+ * - v2 image creation and handling of the reference count table. (Blocker to enable support for V2 images)
+ * - cluster encryption
+ * - cluster compression
+ * - compaction
+ * - resizing
+ */
+
+
+/*********************************************************************************************************************************
+* Structures in a QCOW image, big endian *
+*********************************************************************************************************************************/
+
+#pragma pack(1) /* Completely unnecessary. */
+typedef struct QCowHeader
+{
+ /** Magic value. */
+ uint32_t u32Magic;
+ /** Version of the image. */
+ uint32_t u32Version;
+ /** Version dependent data. */
+ union
+ {
+ /** Version 1. */
+ struct
+ {
+ /** Backing file offset. */
+ uint64_t u64BackingFileOffset;
+ /** Size of the backing file. */
+ uint32_t u32BackingFileSize;
+ /** mtime (Modification time?) - can be ignored. */
+ uint32_t u32MTime;
+ /** Logical size of the image in bytes. */
+ uint64_t u64Size;
+ /** Number of bits in the virtual offset used as a cluster offset. */
+ uint8_t u8ClusterBits;
+ /** Number of bits in the virtual offset used for the L2 index. */
+ uint8_t u8L2Bits;
+ /** Padding because the header is not packed in the original source. */
+ uint16_t u16Padding;
+ /** Used cryptographic method. */
+ uint32_t u32CryptMethod;
+ /** Offset of the L1 table in the image in bytes. */
+ uint64_t u64L1TableOffset;
+ } v1;
+ /** Version 2 (and also containing extensions for version 3). */
+ struct
+ {
+ /** Backing file offset. */
+ uint64_t u64BackingFileOffset;
+ /** Size of the backing file. */
+ uint32_t u32BackingFileSize;
+ /** Number of bits in the virtual offset used as a cluster offset. */
+ uint32_t u32ClusterBits;
+ /** Logical size of the image. */
+ uint64_t u64Size;
+ /** Used cryptographic method. */
+ uint32_t u32CryptMethod;
+ /** Size of the L1 table in entries (each 8bytes big). */
+ uint32_t u32L1Size;
+ /** Offset of the L1 table in the image in bytes. */
+ uint64_t u64L1TableOffset;
+ /** Start of the refcount table in the image. */
+ uint64_t u64RefcountTableOffset;
+ /** Size of the refcount table in clusters. */
+ uint32_t u32RefcountTableClusters;
+ /** Number of snapshots in the image. */
+ uint32_t u32NbSnapshots;
+ /** Offset of the first snapshot header in the image. */
+ uint64_t u64SnapshotsOffset;
+ /** Version 3 additional data. */
+ struct
+ {
+ /** Incompatible features. */
+ uint64_t u64IncompatFeat;
+ /** Compatible features. */
+ uint64_t u64CompatFeat;
+ /** Autoclear features. */
+ uint64_t u64AutoClrFeat;
+ /** Width in bits of a reference count block. */
+ uint32_t u32RefCntWidth;
+ /** Lenght of the header structure in bytes (for the header extensions). */
+ uint32_t u32HdrLenBytes;
+ } v3;
+ } v2;
+ } Version;
+} QCowHeader;
+#pragma pack()
+/** Pointer to a on disk QCOW header. */
+typedef QCowHeader *PQCowHeader;
+
+/** QCOW magic value. */
+#define QCOW_MAGIC UINT32_C(0x514649fb) /* QFI\0xfb */
+/** Size of the V1 header. */
+#define QCOW_V1_HDR_SIZE (48)
+/** Size of the V2 header. */
+#define QCOW_V2_HDR_SIZE (72)
+
+/** Cluster is compressed flag for QCOW images. */
+#define QCOW_V1_COMPRESSED_FLAG RT_BIT_64(63)
+
+/** Copied flag for QCOW2 images. */
+#define QCOW_V2_COPIED_FLAG RT_BIT_64(63)
+/** Cluster is compressed flag for QCOW2 images. */
+#define QCOW_V2_COMPRESSED_FLAG RT_BIT_64(62)
+/** The mask for extracting the offset from either the L1 or L2 table. */
+#define QCOW_V2_TBL_OFFSET_MASK UINT64_C(0x00fffffffffffe00)
+
+/** Incompatible feature: Dirty bit, reference count may be inconsistent. */
+#define QCOW_V3_INCOMPAT_FEAT_F_DIRTY RT_BIT_64(0)
+/** Incompatible feature: Image is corrupt and needs repair. */
+#define QCOW_V3_INCOMPAT_FEAT_F_CORRUPT RT_BIT_64(1)
+/** Incompatible feature: External data file. */
+#define QCOW_V3_INCOMPAT_FEAT_F_EXTERNAL_DATA RT_BIT_64(2)
+/** The incompatible features we support currently. */
+#define QCOW_V3_INCOMPAT_FEAT_SUPPORTED_MASK UINT64_C(0x0)
+
+/** Compatible feature: Lazy reference counters. */
+#define QCOW_V3_COMPAT_FEAT_F_LAZY_REF_COUNT RT_BIT_64(0)
+/** The compatible features we support currently. */
+#define QCOW_V3_COMPAT_FEAT_SUPPORTED_MASK UINT64_C(0x0)
+
+/** Auto clear feature: Bitmaps extension. */
+#define QCOW_V3_AUTOCLR_FEAT_F_BITMAPS RT_BIT_64(0)
+/** Auto clear feature: The external data file is raw image which can be accessed standalone. */
+#define QCOW_V3_AUTOCLR_FEAT_F_EXT_RAW_DATA RT_BIT_64(1)
+/** The autoclear features we support currently. */
+#define QCOW_V3_AUTOCLR_FEAT_SUPPORTED_MASK UINT64_C(0x0)
+
+
+/*********************************************************************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * QCOW L2 cache entry.
+ */
+typedef struct QCOWL2CACHEENTRY
+{
+ /** List node for the search list. */
+ RTLISTNODE NodeSearch;
+ /** List node for the LRU list. */
+ RTLISTNODE NodeLru;
+ /** Reference counter. */
+ uint32_t cRefs;
+ /** The offset of the L2 table, used as search key. */
+ uint64_t offL2Tbl;
+ /** Pointer to the cached L2 table. */
+ uint64_t *paL2Tbl;
+} QCOWL2CACHEENTRY, *PQCOWL2CACHEENTRY;
+
+/** Maximum amount of memory the cache is allowed to use. */
+#define QCOW_L2_CACHE_MEMORY_MAX (2*_1M)
+
+/** QCOW default cluster size for image version 2. */
+#define QCOW2_CLUSTER_SIZE_DEFAULT (64*_1K)
+/** QCOW default cluster size for image version 1. */
+#define QCOW_CLUSTER_SIZE_DEFAULT (4*_1K)
+/** QCOW default L2 table size in clusters. */
+#define QCOW_L2_CLUSTERS_DEFAULT (1)
+
+/**
+ * QCOW image data structure.
+ */
+typedef struct QCOWIMAGE
+{
+ /** Image name. */
+ const char *pszFilename;
+ /** Storage handle. */
+ PVDIOSTORAGE pStorage;
+
+ /** Pointer to the per-disk VD interface list. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Pointer to the per-image VD interface list. */
+ PVDINTERFACE pVDIfsImage;
+ /** Error interface. */
+ PVDINTERFACEERROR pIfError;
+ /** I/O interface. */
+ PVDINTERFACEIOINT pIfIo;
+
+ /** Open flags passed by VBoxHD layer. */
+ unsigned uOpenFlags;
+ /** Image flags defined during creation or determined during open. */
+ unsigned uImageFlags;
+ /** Total size of the image. */
+ uint64_t cbSize;
+ /** Physical geometry of this image. */
+ VDGEOMETRY PCHSGeometry;
+ /** Logical geometry of this image. */
+ VDGEOMETRY LCHSGeometry;
+
+ /** Image version. */
+ unsigned uVersion;
+ /** MTime field - used only to preserve value in opened images, unmodified otherwise. */
+ uint32_t MTime;
+
+ /** Filename of the backing file if any. */
+ char *pszBackingFilename;
+ /** Offset of the filename in the image. */
+ uint64_t offBackingFilename;
+ /** Size of the backing filename excluding \0. */
+ uint32_t cbBackingFilename;
+
+ /** Next offset of a new cluster, aligned to sector size. */
+ uint64_t offNextCluster;
+ /** Cluster size in bytes. */
+ uint32_t cbCluster;
+ /** Number of bits in the virtual offset used as the cluster offset. */
+ uint32_t cClusterBits;
+ /** Bitmask to extract the offset from a compressed cluster descriptor. */
+ uint64_t fMaskCompressedClusterOffset;
+ /** Bitmask to extract the sector count from a compressed cluster descriptor. */
+ uint64_t fMaskCompressedClusterSectors;
+ /** Number of bits to shift the sector count to the right to get the final value. */
+ uint32_t cBitsShiftRCompressedClusterSectors;
+ /** Number of entries in the L1 table. */
+ uint32_t cL1TableEntries;
+ /** Size of an L1 rounded to the next cluster size. */
+ uint32_t cbL1Table;
+ /** Pointer to the L1 table. */
+ uint64_t *paL1Table;
+ /** Offset of the L1 table. */
+ uint64_t offL1Table;
+
+ /** Size of the L2 table in bytes. */
+ uint32_t cbL2Table;
+ /** Number of entries in the L2 table. */
+ uint32_t cL2TableEntries;
+ /** Memory occupied by the L2 table cache. */
+ size_t cbL2Cache;
+ /** The sorted L2 entry list used for searching. */
+ RTLISTNODE ListSearch;
+ /** The LRU L2 entry list used for eviction. */
+ RTLISTNODE ListLru;
+
+ /** Offset of the refcount table. */
+ uint64_t offRefcountTable;
+ /** Size of the refcount table in bytes. */
+ uint32_t cbRefcountTable;
+ /** Number of entries in the refcount table. */
+ uint32_t cRefcountTableEntries;
+ /** Pointer to the refcount table. */
+ uint64_t *paRefcountTable;
+
+ /** Offset mask for a cluster. */
+ uint64_t fOffsetMask;
+ /** Number of bits to shift to get the L1 index. */
+ uint32_t cL1Shift;
+ /** L2 table mask to get the L2 index. */
+ uint64_t fL2Mask;
+ /** Number of bits to shift to get the L2 index. */
+ uint32_t cL2Shift;
+
+ /** Size of compressed cluster buffer. */
+ size_t cbCompCluster;
+ /** Compressed cluster buffer. */
+ void *pvCompCluster;
+ /** Buffer to hold the uncompressed data. */
+ void *pvCluster;
+
+ /** Pointer to the L2 table we are currently allocating
+ * (can be only one at a time). */
+ PQCOWL2CACHEENTRY pL2TblAlloc;
+ /** The static region list. */
+ VDREGIONLIST RegionList;
+} QCOWIMAGE, *PQCOWIMAGE;
+
+/**
+ * State of the async cluster allocation.
+ */
+typedef enum QCOWCLUSTERASYNCALLOCSTATE
+{
+ /** Invalid. */
+ QCOWCLUSTERASYNCALLOCSTATE_INVALID = 0,
+ /** L2 table allocation. */
+ QCOWCLUSTERASYNCALLOCSTATE_L2_ALLOC,
+ /** Link L2 table into L1. */
+ QCOWCLUSTERASYNCALLOCSTATE_L2_LINK,
+ /** Allocate user data cluster. */
+ QCOWCLUSTERASYNCALLOCSTATE_USER_ALLOC,
+ /** Link user data cluster. */
+ QCOWCLUSTERASYNCALLOCSTATE_USER_LINK,
+ /** 32bit blowup. */
+ QCOWCLUSTERASYNCALLOCSTATE_32BIT_HACK = 0x7fffffff
+} QCOWCLUSTERASYNCALLOCSTATE, *PQCOWCLUSTERASYNCALLOCSTATE;
+
+/**
+ * Data needed to track async cluster allocation.
+ */
+typedef struct QCOWCLUSTERASYNCALLOC
+{
+ /** The state of the cluster allocation. */
+ QCOWCLUSTERASYNCALLOCSTATE enmAllocState;
+ /** Old image size to rollback in case of an error. */
+ uint64_t offNextClusterOld;
+ /** L1 index to link if any. */
+ uint32_t idxL1;
+ /** L2 index to link, required in any case. */
+ uint32_t idxL2;
+ /** Start offset of the allocated cluster. */
+ uint64_t offClusterNew;
+ /** L2 cache entry if a L2 table is allocated. */
+ PQCOWL2CACHEENTRY pL2Entry;
+ /** Number of bytes to write. */
+ size_t cbToWrite;
+} QCOWCLUSTERASYNCALLOC, *PQCOWCLUSTERASYNCALLOC;
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const VDFILEEXTENSION s_aQCowFileExtensions[] =
+{
+ {"qcow", VDTYPE_HDD},
+ {"qcow2", VDTYPE_HDD},
+ {NULL, VDTYPE_INVALID}
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Return power of 2 or 0 if num error.
+ *
+ * @returns The power of 2 or 0 if the given number is not a power of 2.
+ * @param u32 The number.
+ */
+static uint32_t qcowGetPowerOfTwo(uint32_t u32)
+{
+ if (u32 == 0)
+ return 0;
+ uint32_t uPower2 = 0;
+ while ((u32 & 1) == 0)
+ {
+ u32 >>= 1;
+ uPower2++;
+ }
+ return u32 == 1 ? uPower2 : 0;
+}
+
+
+/**
+ * Converts the image header to the host endianess and performs basic checks.
+ *
+ * @returns Whether the given header is valid or not.
+ * @param pHeader Pointer to the header to convert.
+ */
+static bool qcowHdrConvertToHostEndianess(PQCowHeader pHeader)
+{
+ pHeader->u32Magic = RT_BE2H_U32(pHeader->u32Magic);
+ pHeader->u32Version = RT_BE2H_U32(pHeader->u32Version);
+
+ if (pHeader->u32Magic != QCOW_MAGIC)
+ return false;
+
+ if (pHeader->u32Version == 1)
+ {
+ pHeader->Version.v1.u64BackingFileOffset = RT_BE2H_U64(pHeader->Version.v1.u64BackingFileOffset);
+ pHeader->Version.v1.u32BackingFileSize = RT_BE2H_U32(pHeader->Version.v1.u32BackingFileSize);
+ pHeader->Version.v1.u32MTime = RT_BE2H_U32(pHeader->Version.v1.u32MTime);
+ pHeader->Version.v1.u64Size = RT_BE2H_U64(pHeader->Version.v1.u64Size);
+ pHeader->Version.v1.u32CryptMethod = RT_BE2H_U32(pHeader->Version.v1.u32CryptMethod);
+ pHeader->Version.v1.u64L1TableOffset = RT_BE2H_U64(pHeader->Version.v1.u64L1TableOffset);
+ }
+ else if (pHeader->u32Version == 2 || pHeader->u32Version == 3)
+ {
+ pHeader->Version.v2.u64BackingFileOffset = RT_BE2H_U64(pHeader->Version.v2.u64BackingFileOffset);
+ pHeader->Version.v2.u32BackingFileSize = RT_BE2H_U32(pHeader->Version.v2.u32BackingFileSize);
+ pHeader->Version.v2.u32ClusterBits = RT_BE2H_U32(pHeader->Version.v2.u32ClusterBits);
+ pHeader->Version.v2.u64Size = RT_BE2H_U64(pHeader->Version.v2.u64Size);
+ pHeader->Version.v2.u32CryptMethod = RT_BE2H_U32(pHeader->Version.v2.u32CryptMethod);
+ pHeader->Version.v2.u32L1Size = RT_BE2H_U32(pHeader->Version.v2.u32L1Size);
+ pHeader->Version.v2.u64L1TableOffset = RT_BE2H_U64(pHeader->Version.v2.u64L1TableOffset);
+ pHeader->Version.v2.u64RefcountTableOffset = RT_BE2H_U64(pHeader->Version.v2.u64RefcountTableOffset);
+ pHeader->Version.v2.u32RefcountTableClusters = RT_BE2H_U32(pHeader->Version.v2.u32RefcountTableClusters);
+ pHeader->Version.v2.u32NbSnapshots = RT_BE2H_U32(pHeader->Version.v2.u32NbSnapshots);
+ pHeader->Version.v2.u64SnapshotsOffset = RT_BE2H_U64(pHeader->Version.v2.u64SnapshotsOffset);
+
+ if (pHeader->u32Version == 3)
+ {
+ pHeader->Version.v2.v3.u64IncompatFeat = RT_BE2H_U64(pHeader->Version.v2.v3.u64IncompatFeat);
+ pHeader->Version.v2.v3.u64CompatFeat = RT_BE2H_U64(pHeader->Version.v2.v3.u64CompatFeat);
+ pHeader->Version.v2.v3.u64AutoClrFeat = RT_BE2H_U64(pHeader->Version.v2.v3.u64AutoClrFeat);
+ pHeader->Version.v2.v3.u32RefCntWidth = RT_BE2H_U32(pHeader->Version.v2.v3.u32RefCntWidth);
+ pHeader->Version.v2.v3.u32HdrLenBytes = RT_BE2H_U32(pHeader->Version.v2.v3.u32HdrLenBytes);
+ }
+ }
+ else
+ return false;
+
+ return true;
+}
+
+/**
+ * Creates a QCOW header from the given image state.
+ *
+ * @returns nothing.
+ * @param pImage Image instance data.
+ * @param pHeader Pointer to the header to convert.
+ * @param pcbHeader Where to store the size of the header to write.
+ */
+static void qcowHdrConvertFromHostEndianess(PQCOWIMAGE pImage, PQCowHeader pHeader,
+ size_t *pcbHeader)
+{
+ memset(pHeader, 0, sizeof(QCowHeader));
+
+ pHeader->u32Magic = RT_H2BE_U32(QCOW_MAGIC);
+ pHeader->u32Version = RT_H2BE_U32(pImage->uVersion);
+ if (pImage->uVersion == 1)
+ {
+ pHeader->Version.v1.u64BackingFileOffset = RT_H2BE_U64(pImage->offBackingFilename);
+ pHeader->Version.v1.u32BackingFileSize = RT_H2BE_U32(pImage->cbBackingFilename);
+ pHeader->Version.v1.u32MTime = RT_H2BE_U32(pImage->MTime);
+ pHeader->Version.v1.u64Size = RT_H2BE_U64(pImage->cbSize);
+ pHeader->Version.v1.u8ClusterBits = (uint8_t)qcowGetPowerOfTwo(pImage->cbCluster);
+ pHeader->Version.v1.u8L2Bits = (uint8_t)qcowGetPowerOfTwo(pImage->cL2TableEntries);
+ pHeader->Version.v1.u32CryptMethod = RT_H2BE_U32(0);
+ pHeader->Version.v1.u64L1TableOffset = RT_H2BE_U64(pImage->offL1Table);
+ *pcbHeader = QCOW_V1_HDR_SIZE;
+ }
+ else if (pImage->uVersion == 2)
+ {
+ pHeader->Version.v2.u64BackingFileOffset = RT_H2BE_U64(pImage->offBackingFilename);
+ pHeader->Version.v2.u32BackingFileSize = RT_H2BE_U32(pImage->cbBackingFilename);
+ pHeader->Version.v2.u32ClusterBits = RT_H2BE_U32(qcowGetPowerOfTwo(pImage->cbCluster));
+ pHeader->Version.v2.u64Size = RT_H2BE_U64(pImage->cbSize);
+ pHeader->Version.v2.u32CryptMethod = RT_H2BE_U32(0);
+ pHeader->Version.v2.u32L1Size = RT_H2BE_U32(pImage->cL1TableEntries);
+ pHeader->Version.v2.u64L1TableOffset = RT_H2BE_U64(pImage->offL1Table);
+ pHeader->Version.v2.u64RefcountTableOffset = RT_H2BE_U64(pImage->offRefcountTable);
+ pHeader->Version.v2.u32RefcountTableClusters = RT_H2BE_U32(pImage->cbRefcountTable / pImage->cbCluster);
+ pHeader->Version.v2.u32NbSnapshots = RT_H2BE_U32(0);
+ pHeader->Version.v2.u64SnapshotsOffset = RT_H2BE_U64((uint64_t)0);
+ *pcbHeader = QCOW_V2_HDR_SIZE;
+ }
+ else
+ AssertMsgFailed(("Invalid version of the QCOW image format %d\n", pImage->uVersion));
+}
+
+/**
+ * Convert table entries from little endian to host endianess.
+ *
+ * @returns nothing.
+ * @param paTbl Pointer to the table.
+ * @param cEntries Number of entries in the table.
+ */
+static void qcowTableConvertToHostEndianess(uint64_t *paTbl, uint32_t cEntries)
+{
+ while(cEntries-- > 0)
+ {
+ *paTbl = RT_BE2H_U64(*paTbl);
+ paTbl++;
+ }
+}
+
+/**
+ * Convert table entries from host to little endian format.
+ *
+ * @returns nothing.
+ * @param paTblImg Pointer to the table which will store the little endian table.
+ * @param paTbl The source table to convert.
+ * @param cEntries Number of entries in the table.
+ */
+static void qcowTableConvertFromHostEndianess(uint64_t *paTblImg, uint64_t *paTbl,
+ uint32_t cEntries)
+{
+ while(cEntries-- > 0)
+ {
+ *paTblImg = RT_H2BE_U64(*paTbl);
+ paTbl++;
+ paTblImg++;
+ }
+}
+
+/**
+ * Creates the L2 table cache.
+ *
+ * @returns VBox status code.
+ * @param pImage The image instance data.
+ */
+static int qcowL2TblCacheCreate(PQCOWIMAGE pImage)
+{
+ pImage->cbL2Cache = 0;
+ RTListInit(&pImage->ListSearch);
+ RTListInit(&pImage->ListLru);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys the L2 table cache.
+ *
+ * @returns nothing.
+ * @param pImage The image instance data.
+ */
+static void qcowL2TblCacheDestroy(PQCOWIMAGE pImage)
+{
+ PQCOWL2CACHEENTRY pL2Entry;
+ PQCOWL2CACHEENTRY pL2Next;
+ RTListForEachSafe(&pImage->ListSearch, pL2Entry, pL2Next, QCOWL2CACHEENTRY, NodeSearch)
+ {
+ Assert(!pL2Entry->cRefs);
+
+ RTListNodeRemove(&pL2Entry->NodeSearch);
+ RTMemPageFree(pL2Entry->paL2Tbl, pImage->cbL2Table);
+ RTMemFree(pL2Entry);
+ }
+
+ pImage->cbL2Cache = 0;
+ RTListInit(&pImage->ListSearch);
+ RTListInit(&pImage->ListLru);
+}
+
+/**
+ * Returns the L2 table matching the given offset or NULL if none could be found.
+ *
+ * @returns Pointer to the L2 table cache entry or NULL.
+ * @param pImage The image instance data.
+ * @param offL2Tbl Offset of the L2 table to search for.
+ */
+static PQCOWL2CACHEENTRY qcowL2TblCacheRetain(PQCOWIMAGE pImage, uint64_t offL2Tbl)
+{
+ if ( pImage->pL2TblAlloc
+ && pImage->pL2TblAlloc->offL2Tbl == offL2Tbl)
+ {
+ pImage->pL2TblAlloc->cRefs++;
+ return pImage->pL2TblAlloc;
+ }
+
+ PQCOWL2CACHEENTRY pL2Entry;
+ RTListForEach(&pImage->ListSearch, pL2Entry, QCOWL2CACHEENTRY, NodeSearch)
+ {
+ if (pL2Entry->offL2Tbl == offL2Tbl)
+ break;
+ }
+
+ if (!RTListNodeIsDummy(&pImage->ListSearch, pL2Entry, QCOWL2CACHEENTRY, NodeSearch))
+ {
+ /* Update LRU list. */
+ RTListNodeRemove(&pL2Entry->NodeLru);
+ RTListPrepend(&pImage->ListLru, &pL2Entry->NodeLru);
+ pL2Entry->cRefs++;
+ return pL2Entry;
+ }
+
+ return NULL;
+}
+
+/**
+ * Releases a L2 table cache entry.
+ *
+ * @returns nothing.
+ * @param pL2Entry The L2 cache entry.
+ */
+static void qcowL2TblCacheEntryRelease(PQCOWL2CACHEENTRY pL2Entry)
+{
+ Assert(pL2Entry->cRefs > 0);
+ pL2Entry->cRefs--;
+}
+
+/**
+ * Allocates a new L2 table from the cache evicting old entries if required.
+ *
+ * @returns Pointer to the L2 cache entry or NULL.
+ * @param pImage The image instance data.
+ */
+static PQCOWL2CACHEENTRY qcowL2TblCacheEntryAlloc(PQCOWIMAGE pImage)
+{
+ PQCOWL2CACHEENTRY pL2Entry = NULL;
+
+ if (pImage->cbL2Cache + pImage->cbL2Table <= QCOW_L2_CACHE_MEMORY_MAX)
+ {
+ /* Add a new entry. */
+ pL2Entry = (PQCOWL2CACHEENTRY)RTMemAllocZ(sizeof(QCOWL2CACHEENTRY));
+ if (pL2Entry)
+ {
+ pL2Entry->paL2Tbl = (uint64_t *)RTMemPageAllocZ(pImage->cbL2Table);
+ if (RT_UNLIKELY(!pL2Entry->paL2Tbl))
+ {
+ RTMemFree(pL2Entry);
+ pL2Entry = NULL;
+ }
+ else
+ {
+ pL2Entry->cRefs = 1;
+ pImage->cbL2Cache += pImage->cbL2Table;
+ }
+ }
+ }
+ else
+ {
+ /* Evict the last not in use entry and use it */
+ Assert(!RTListIsEmpty(&pImage->ListLru));
+
+ RTListForEachReverse(&pImage->ListLru, pL2Entry, QCOWL2CACHEENTRY, NodeLru)
+ {
+ if (!pL2Entry->cRefs)
+ break;
+ }
+
+ if (!RTListNodeIsDummy(&pImage->ListSearch, pL2Entry, QCOWL2CACHEENTRY, NodeSearch))
+ {
+ RTListNodeRemove(&pL2Entry->NodeSearch);
+ RTListNodeRemove(&pL2Entry->NodeLru);
+ pL2Entry->offL2Tbl = 0;
+ pL2Entry->cRefs = 1;
+ }
+ else
+ pL2Entry = NULL;
+ }
+
+ return pL2Entry;
+}
+
+/**
+ * Frees a L2 table cache entry.
+ *
+ * @returns nothing.
+ * @param pImage The image instance data.
+ * @param pL2Entry The L2 cache entry to free.
+ */
+static void qcowL2TblCacheEntryFree(PQCOWIMAGE pImage, PQCOWL2CACHEENTRY pL2Entry)
+{
+ Assert(!pL2Entry->cRefs);
+ RTMemPageFree(pL2Entry->paL2Tbl, pImage->cbL2Table);
+ RTMemFree(pL2Entry);
+
+ pImage->cbL2Cache -= pImage->cbL2Table;
+}
+
+/**
+ * Inserts an entry in the L2 table cache.
+ *
+ * @returns nothing.
+ * @param pImage The image instance data.
+ * @param pL2Entry The L2 cache entry to insert.
+ */
+static void qcowL2TblCacheEntryInsert(PQCOWIMAGE pImage, PQCOWL2CACHEENTRY pL2Entry)
+{
+ Assert(pL2Entry->offL2Tbl > 0);
+
+ /* Insert at the top of the LRU list. */
+ RTListPrepend(&pImage->ListLru, &pL2Entry->NodeLru);
+
+ if (RTListIsEmpty(&pImage->ListSearch))
+ {
+ RTListAppend(&pImage->ListSearch, &pL2Entry->NodeSearch);
+ }
+ else
+ {
+ /* Insert into search list. */
+ PQCOWL2CACHEENTRY pIt;
+ pIt = RTListGetFirst(&pImage->ListSearch, QCOWL2CACHEENTRY, NodeSearch);
+ if (pIt->offL2Tbl > pL2Entry->offL2Tbl)
+ RTListPrepend(&pImage->ListSearch, &pL2Entry->NodeSearch);
+ else
+ {
+ bool fInserted = false;
+
+ RTListForEach(&pImage->ListSearch, pIt, QCOWL2CACHEENTRY, NodeSearch)
+ {
+ Assert(pIt->offL2Tbl != pL2Entry->offL2Tbl);
+ if (pIt->offL2Tbl < pL2Entry->offL2Tbl)
+ {
+ RTListNodeInsertAfter(&pIt->NodeSearch, &pL2Entry->NodeSearch);
+ fInserted = true;
+ break;
+ }
+ }
+ Assert(fInserted);
+ }
+ }
+}
+
+/**
+ * Fetches the L2 from the given offset trying the LRU cache first and
+ * reading it from the image after a cache miss.
+ *
+ * @returns VBox status code.
+ * @param pImage Image instance data.
+ * @param pIoCtx The I/O context.
+ * @param offL2Tbl The offset of the L2 table in the image.
+ * @param ppL2Entry Where to store the L2 table on success.
+ */
+static int qcowL2TblCacheFetch(PQCOWIMAGE pImage, PVDIOCTX pIoCtx, uint64_t offL2Tbl,
+ PQCOWL2CACHEENTRY *ppL2Entry)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Try to fetch the L2 table from the cache first. */
+ PQCOWL2CACHEENTRY pL2Entry = qcowL2TblCacheRetain(pImage, offL2Tbl);
+ if (!pL2Entry)
+ {
+ pL2Entry = qcowL2TblCacheEntryAlloc(pImage);
+
+ if (pL2Entry)
+ {
+ /* Read from the image. */
+ PVDMETAXFER pMetaXfer;
+
+ pL2Entry->offL2Tbl = offL2Tbl;
+ rc = vdIfIoIntFileReadMeta(pImage->pIfIo, pImage->pStorage,
+ offL2Tbl, pL2Entry->paL2Tbl,
+ pImage->cbL2Table, pIoCtx,
+ &pMetaXfer, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ vdIfIoIntMetaXferRelease(pImage->pIfIo, pMetaXfer);
+#if defined(RT_LITTLE_ENDIAN)
+ qcowTableConvertToHostEndianess(pL2Entry->paL2Tbl, pImage->cL2TableEntries);
+#endif
+ qcowL2TblCacheEntryInsert(pImage, pL2Entry);
+ }
+ else
+ {
+ qcowL2TblCacheEntryRelease(pL2Entry);
+ qcowL2TblCacheEntryFree(pImage, pL2Entry);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppL2Entry = pL2Entry;
+
+ return rc;
+}
+
+/**
+ * Sets the L1, L2 and offset bitmasks and L1 and L2 bit shift members.
+ *
+ * @returns nothing.
+ * @param pImage The image instance data.
+ */
+static void qcowTableMasksInit(PQCOWIMAGE pImage)
+{
+ uint32_t cClusterBits, cL2TableBits;
+
+ cClusterBits = qcowGetPowerOfTwo(pImage->cbCluster);
+ cL2TableBits = qcowGetPowerOfTwo(pImage->cL2TableEntries);
+
+ Assert(cClusterBits + cL2TableBits < 64);
+
+ pImage->fOffsetMask = ((uint64_t)pImage->cbCluster - 1);
+ pImage->fL2Mask = ((uint64_t)pImage->cL2TableEntries - 1) << cClusterBits;
+ pImage->cL2Shift = cClusterBits;
+ pImage->cL1Shift = cClusterBits + cL2TableBits;
+}
+
+/**
+ * Converts a given logical offset into the
+ *
+ * @returns nothing.
+ * @param pImage The image instance data.
+ * @param off The logical offset to convert.
+ * @param pidxL1 Where to store the index in the L1 table on success.
+ * @param pidxL2 Where to store the index in the L2 table on success.
+ * @param poffCluster Where to store the offset in the cluster on success.
+ */
+DECLINLINE(void) qcowConvertLogicalOffset(PQCOWIMAGE pImage, uint64_t off, uint32_t *pidxL1,
+ uint32_t *pidxL2, uint32_t *poffCluster)
+{
+ AssertPtr(pidxL1);
+ AssertPtr(pidxL2);
+ AssertPtr(poffCluster);
+
+ *poffCluster = off & pImage->fOffsetMask;
+ *pidxL1 = off >> pImage->cL1Shift;
+ *pidxL2 = (off & pImage->fL2Mask) >> pImage->cL2Shift;
+}
+
+/**
+ * Converts Cluster size to a byte size.
+ *
+ * @returns Number of bytes derived from the given number of clusters.
+ * @param pImage The image instance data.
+ * @param cClusters The clusters to convert.
+ */
+DECLINLINE(uint64_t) qcowCluster2Byte(PQCOWIMAGE pImage, uint64_t cClusters)
+{
+ return cClusters * pImage->cbCluster;
+}
+
+/**
+ * Converts number of bytes to cluster size rounding to the next cluster.
+ *
+ * @returns Number of bytes derived from the given number of clusters.
+ * @param pImage The image instance data.
+ * @param cb Number of bytes to convert.
+ */
+DECLINLINE(uint64_t) qcowByte2Cluster(PQCOWIMAGE pImage, uint64_t cb)
+{
+ return cb / pImage->cbCluster + (cb % pImage->cbCluster ? 1 : 0);
+}
+
+/**
+ * Allocates a new cluster in the image.
+ *
+ * @returns The start offset of the new cluster in the image.
+ * @param pImage The image instance data.
+ * @param cClusters Number of clusters to allocate.
+ */
+DECLINLINE(uint64_t) qcowClusterAllocate(PQCOWIMAGE pImage, uint32_t cClusters)
+{
+ uint64_t offCluster;
+
+ offCluster = pImage->offNextCluster;
+ pImage->offNextCluster += cClusters*pImage->cbCluster;
+
+ return offCluster;
+}
+
+/**
+ * Returns the real image offset for a given cluster or an error if the cluster is not
+ * yet allocated.
+ *
+ * @returns VBox status code.
+ * VERR_VD_BLOCK_FREE if the cluster is not yet allocated.
+ * @param pImage The image instance data.
+ * @param pIoCtx The I/O context.
+ * @param idxL1 The L1 index.
+ * @param idxL2 The L2 index.
+ * @param offCluster Offset inside the cluster.
+ * @param poffImage Where to store the image offset on success.
+ * @param pfCompressed Where to store the flag whether the cluster is compressed on success.
+ * @param pcbCompressed Where to store the size of the compressed cluster in bytes on success.
+ * Only valid when the cluster comrpessed flag is true.
+ */
+static int qcowConvertToImageOffset(PQCOWIMAGE pImage, PVDIOCTX pIoCtx,
+ uint32_t idxL1, uint32_t idxL2,
+ uint32_t offCluster, uint64_t *poffImage,
+ bool *pfCompressed, size_t *pcbCompressed)
+{
+ int rc = VERR_VD_BLOCK_FREE;
+
+ AssertReturn(idxL1 < pImage->cL1TableEntries, VERR_INVALID_PARAMETER);
+ AssertReturn(idxL2 < pImage->cL2TableEntries, VERR_INVALID_PARAMETER);
+
+ if (pImage->paL1Table[idxL1])
+ {
+ PQCOWL2CACHEENTRY pL2Entry;
+
+ uint64_t offL2Tbl = pImage->paL1Table[idxL1];
+ if (pImage->uVersion == 2)
+ offL2Tbl &= QCOW_V2_TBL_OFFSET_MASK;
+ rc = qcowL2TblCacheFetch(pImage, pIoCtx, offL2Tbl, &pL2Entry);
+ if (RT_SUCCESS(rc))
+ {
+ /* Get real file offset. */
+ if (pL2Entry->paL2Tbl[idxL2])
+ {
+ uint64_t off = pL2Entry->paL2Tbl[idxL2];
+
+ /* Strip flags */
+ if (pImage->uVersion == 2)
+ {
+ if (RT_UNLIKELY(off & QCOW_V2_COMPRESSED_FLAG))
+ {
+ size_t cCompressedClusterSectors = ((off & pImage->fMaskCompressedClusterSectors) >> pImage->cBitsShiftRCompressedClusterSectors);
+ uint64_t offImage = off & pImage->fMaskCompressedClusterOffset;
+
+ *pfCompressed = true;
+ *poffImage = offImage;
+ *pcbCompressed = (cCompressedClusterSectors + 1) * 512 - (offImage & 511ULL);
+ }
+ else
+ {
+ off &= QCOW_V2_TBL_OFFSET_MASK;
+
+ *pfCompressed = false;
+ *poffImage = off + offCluster;
+ }
+ }
+ else
+ {
+ if (RT_UNLIKELY(off & QCOW_V1_COMPRESSED_FLAG))
+ {
+ size_t cCompressedClusterSectors = (off & pImage->fMaskCompressedClusterSectors) >> pImage->cBitsShiftRCompressedClusterSectors;
+
+ *pfCompressed = true;
+ *poffImage = off & pImage->fMaskCompressedClusterOffset;
+ *pcbCompressed = cCompressedClusterSectors * 512; /* Only additional sectors */
+ /* Add remaining bytes of the sector the offset starts in. */
+ *pcbCompressed += 512 - RT_ALIGN_64(*poffImage, 512) - *poffImage;
+ }
+ else
+ {
+ off &= ~QCOW_V1_COMPRESSED_FLAG;
+
+ *pfCompressed = false;
+ *poffImage = off + offCluster;
+ }
+ }
+ }
+ else
+ rc = VERR_VD_BLOCK_FREE;
+
+ qcowL2TblCacheEntryRelease(pL2Entry);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Write the given table to image converting to the image endianess if required.
+ *
+ * @returns VBox status code.
+ * @param pImage The image instance data.
+ * @param pIoCtx The I/O context.
+ * @param offTbl The offset the table should be written to.
+ * @param paTbl The table to write.
+ * @param cbTbl Size of the table in bytes.
+ * @param cTblEntries Number entries in the table.
+ * @param pfnComplete Callback called when the write completes.
+ * @param pvUser Opaque user data to pass in the completion callback.
+ */
+static int qcowTblWrite(PQCOWIMAGE pImage, PVDIOCTX pIoCtx, uint64_t offTbl, uint64_t *paTbl,
+ size_t cbTbl, unsigned cTblEntries,
+ PFNVDXFERCOMPLETED pfnComplete, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+
+#if defined(RT_LITTLE_ENDIAN)
+ uint64_t *paTblImg = (uint64_t *)RTMemAllocZ(cbTbl);
+ if (paTblImg)
+ {
+ qcowTableConvertFromHostEndianess(paTblImg, paTbl, cTblEntries);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ offTbl, paTblImg, cbTbl,
+ pIoCtx, pfnComplete, pvUser);
+ RTMemFree(paTblImg);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+#else
+ /* Write table directly. */
+ RT_NOREF(cTblEntries);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ offTbl, paTbl, cbTbl, pIoCtx,
+ pfnComplete, pvUser);
+#endif
+
+ return rc;
+}
+
+/**
+ * Internal. Flush image data to disk.
+ */
+static int qcowFlushImage(PQCOWIMAGE pImage)
+{
+ int rc = VINF_SUCCESS;
+
+ if ( pImage->pStorage
+ && !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && pImage->cbL1Table)
+ {
+ QCowHeader Header;
+
+#if defined(RT_LITTLE_ENDIAN)
+ uint64_t *paL1TblImg = (uint64_t *)RTMemAllocZ(pImage->cbL1Table);
+ if (paL1TblImg)
+ {
+ qcowTableConvertFromHostEndianess(paL1TblImg, pImage->paL1Table,
+ pImage->cL1TableEntries);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ pImage->offL1Table, paL1TblImg,
+ pImage->cbL1Table);
+ RTMemFree(paL1TblImg);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+#else
+ /* Write L1 table directly. */
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, pImage->offL1Table,
+ pImage->paL1Table, pImage->cbL1Table);
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ /* Write header. */
+ size_t cbHeader = 0;
+ qcowHdrConvertFromHostEndianess(pImage, &Header, &cbHeader);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, 0, &Header,
+ cbHeader);
+ if (RT_SUCCESS(rc))
+ rc = vdIfIoIntFileFlushSync(pImage->pIfIo, pImage->pStorage);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Internal. Free all allocated space for representing an image except pImage,
+ * and optionally delete the image from disk.
+ */
+static int qcowFreeImage(PQCOWIMAGE pImage, bool fDelete)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Freeing a never allocated image (e.g. because the open failed) is
+ * not signalled as an error. After all nothing bad happens. */
+ if (pImage)
+ {
+ if (pImage->pStorage)
+ {
+ /* No point updating the file that is deleted anyway. */
+ if (!fDelete)
+ qcowFlushImage(pImage);
+
+ rc = vdIfIoIntFileClose(pImage->pIfIo, pImage->pStorage);
+ pImage->pStorage = NULL;
+ }
+
+ if (pImage->paRefcountTable)
+ RTMemFree(pImage->paRefcountTable);
+ pImage->paRefcountTable = NULL;
+
+ if (pImage->paL1Table)
+ RTMemFree(pImage->paL1Table);
+
+ if (pImage->pszBackingFilename)
+ {
+ RTStrFree(pImage->pszBackingFilename);
+ pImage->pszBackingFilename = NULL;
+ }
+
+ if (pImage->pvCompCluster)
+ {
+ RTMemFree(pImage->pvCompCluster);
+ pImage->pvCompCluster = NULL;
+ pImage->cbCompCluster = 0;
+ }
+
+ if (pImage->pvCluster)
+ {
+ RTMemFree(pImage->pvCluster);
+ pImage->pvCluster = NULL;
+ }
+
+ qcowL2TblCacheDestroy(pImage);
+
+ if (fDelete && pImage->pszFilename)
+ vdIfIoIntFileDelete(pImage->pIfIo, pImage->pszFilename);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Validates the header.
+ *
+ * @returns VBox status code.
+ * @param pImage Image backend instance data.
+ * @param pHdr The header to validate.
+ * @param cbFile The image file size in bytes.
+ */
+static int qcowHdrValidate(PQCOWIMAGE pImage, PQCowHeader pHdr, uint64_t cbFile)
+{
+ if (pHdr->u32Version == 1)
+ {
+ /* Check that the backing filename is contained in the file. */
+ if (pHdr->Version.v1.u64BackingFileOffset + pHdr->Version.v1.u32BackingFileSize > cbFile)
+ return vdIfError(pImage->pIfError, VERR_INVALID_STATE, RT_SRC_POS,
+ N_("QCOW: Backing file offset and size exceed size of image '%s' (%u vs %u)"),
+ pImage->pszFilename, pHdr->Version.v1.u64BackingFileOffset + pHdr->Version.v1.u32BackingFileSize,
+ cbFile);
+
+ /* Check that the cluster bits indicate at least a 512byte sector size. */
+ if (RT_BIT_32(pHdr->Version.v1.u8ClusterBits) < 512)
+ return vdIfError(pImage->pIfError, VERR_INVALID_STATE, RT_SRC_POS,
+ N_("QCOW: Cluster size is too small for image '%s' (%u vs %u)"),
+ pImage->pszFilename, RT_BIT_32(pHdr->Version.v1.u8ClusterBits), 512);
+
+ /*
+ * Check for possible overflow when multiplying cluster size and L2 entry count because it is used
+ * to calculate the number of L1 table entries later on.
+ */
+ if (RT_BIT_32(pHdr->Version.v1.u8L2Bits) * RT_BIT_32(pHdr->Version.v1.u8ClusterBits) == 0)
+ return vdIfError(pImage->pIfError, VERR_INVALID_STATE, RT_SRC_POS,
+ N_("QCOW: Overflow during L1 table size calculation for image '%s'"),
+ pImage->pszFilename);
+ }
+ else if (pHdr->u32Version == 2 || pHdr->u32Version == 3)
+ {
+ /* Check that the backing filename is contained in the file. */
+ if (pHdr->Version.v2.u64BackingFileOffset + pHdr->Version.v2.u32BackingFileSize > cbFile)
+ return vdIfError(pImage->pIfError, VERR_INVALID_STATE, RT_SRC_POS,
+ N_("QCOW: Backing file offset and size exceed size of image '%s' (%u vs %u)"),
+ pImage->pszFilename, pHdr->Version.v2.u64BackingFileOffset + pHdr->Version.v2.u32BackingFileSize,
+ cbFile);
+
+ /* Check that the cluster bits indicate at least a 512byte sector size. */
+ if (RT_BIT_32(pHdr->Version.v2.u32ClusterBits) < 512)
+ return vdIfError(pImage->pIfError, VERR_INVALID_STATE, RT_SRC_POS,
+ N_("QCOW: Cluster size is too small for image '%s' (%u vs %u)"),
+ pImage->pszFilename, RT_BIT_32(pHdr->Version.v2.u32ClusterBits), 512);
+
+ /* Some additional checks for v3 images. */
+ if (pHdr->u32Version == 3)
+ {
+ if (pHdr->Version.v2.v3.u32RefCntWidth > 6)
+ return vdIfError(pImage->pIfError, VERR_INVALID_STATE, RT_SRC_POS,
+ N_("QCOW: Reference count width too big for image '%s' (%u vs %u)"),
+ pImage->pszFilename, RT_BIT_32(pHdr->Version.v2.v3.u32RefCntWidth), 6);
+ }
+ }
+ else
+ return vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("QCOW: Version %u in image '%s' is not supported"),
+ pHdr->u32Version, pImage->pszFilename);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal: Open an image, constructing all necessary data structures.
+ */
+static int qcowOpenImage(PQCOWIMAGE pImage, unsigned uOpenFlags)
+{
+ pImage->uOpenFlags = uOpenFlags;
+
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ int rc = qcowL2TblCacheCreate(pImage);
+ if (RT_SUCCESS(rc))
+ {
+ /* Open the image. */
+ rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags,
+ false /* fCreate */),
+ &pImage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbFile;
+ rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &cbFile);
+ if ( RT_SUCCESS(rc)
+ && cbFile > sizeof(QCowHeader))
+ {
+ QCowHeader Header;
+
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, 0, &Header, sizeof(Header));
+ if ( RT_SUCCESS(rc)
+ && qcowHdrConvertToHostEndianess(&Header))
+ {
+ pImage->offNextCluster = RT_ALIGN_64(cbFile, 512); /* Align image to sector boundary. */
+ Assert(pImage->offNextCluster >= cbFile);
+
+ rc = qcowHdrValidate(pImage, &Header, cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ if (Header.u32Version == 1)
+ {
+ if (!Header.Version.v1.u32CryptMethod)
+ {
+ pImage->uVersion = 1;
+ pImage->offBackingFilename = Header.Version.v1.u64BackingFileOffset;
+ pImage->cbBackingFilename = Header.Version.v1.u32BackingFileSize;
+ pImage->MTime = Header.Version.v1.u32MTime;
+ pImage->cbSize = Header.Version.v1.u64Size;
+ pImage->cClusterBits = Header.Version.v1.u8ClusterBits;
+ pImage->cbCluster = RT_BIT_32(Header.Version.v1.u8ClusterBits);
+ pImage->cL2TableEntries = RT_BIT_32(Header.Version.v1.u8L2Bits);
+ pImage->cbL2Table = RT_ALIGN_64(pImage->cL2TableEntries * sizeof(uint64_t), pImage->cbCluster);
+ pImage->offL1Table = Header.Version.v1.u64L1TableOffset;
+ pImage->cL1TableEntries = pImage->cbSize / (pImage->cbCluster * pImage->cL2TableEntries);
+ if (pImage->cbSize % (pImage->cbCluster * pImage->cL2TableEntries))
+ pImage->cL1TableEntries++;
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("QCow: Encrypted image '%s' is not supported"),
+ pImage->pszFilename);
+ }
+ else if (Header.u32Version == 2 || Header.u32Version == 3)
+ {
+ if (Header.Version.v2.u32CryptMethod)
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("QCow: Encrypted image '%s' is not supported"),
+ pImage->pszFilename);
+ else if (Header.Version.v2.u32NbSnapshots)
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("QCow: Image '%s' contains snapshots which is not supported"),
+ pImage->pszFilename);
+ else
+ {
+ pImage->uVersion = 2;
+ pImage->offBackingFilename = Header.Version.v2.u64BackingFileOffset;
+ pImage->cbBackingFilename = Header.Version.v2.u32BackingFileSize;
+ pImage->cbSize = Header.Version.v2.u64Size;
+ pImage->cClusterBits = Header.Version.v2.u32ClusterBits;
+ pImage->cbCluster = RT_BIT_32(Header.Version.v2.u32ClusterBits);
+ pImage->cL2TableEntries = pImage->cbCluster / sizeof(uint64_t);
+ pImage->cbL2Table = pImage->cbCluster;
+ pImage->offL1Table = Header.Version.v2.u64L1TableOffset;
+ pImage->cL1TableEntries = Header.Version.v2.u32L1Size;
+ pImage->offRefcountTable = Header.Version.v2.u64RefcountTableOffset;
+ pImage->cbRefcountTable = qcowCluster2Byte(pImage, Header.Version.v2.u32RefcountTableClusters);
+ pImage->cRefcountTableEntries = pImage->cbRefcountTable / sizeof(uint64_t);
+
+ /* Init the masks to extract offset and sector count from a compressed cluster descriptor. */
+ uint32_t cBitsCompressedClusterOffset = 62 - (pImage->cClusterBits - 8);
+ pImage->fMaskCompressedClusterOffset = RT_BIT_64(cBitsCompressedClusterOffset) - 1;
+ pImage->fMaskCompressedClusterSectors = (RT_BIT_64(62) - 1) & ~pImage->fMaskCompressedClusterOffset;
+ pImage->cBitsShiftRCompressedClusterSectors = cBitsCompressedClusterOffset;
+
+ if (Header.u32Version == 3)
+ {
+ if (Header.Version.v2.v3.u64IncompatFeat & ~QCOW_V3_INCOMPAT_FEAT_SUPPORTED_MASK)
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("QCow: Image '%s' contains unsupported incompatible features (%llx vs %llx)"),
+ pImage->pszFilename, Header.Version.v2.v3.u64IncompatFeat, QCOW_V3_INCOMPAT_FEAT_SUPPORTED_MASK);
+
+ /** @todo Auto clear features need to be reset as soon as write support is added. */
+ }
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
+ N_("QCow: Image '%s' uses version %u which is not supported"),
+ pImage->pszFilename, Header.u32Version);
+
+ if (RT_SUCCESS(rc))
+ {
+ pImage->cbL1Table = RT_ALIGN_64(pImage->cL1TableEntries * sizeof(uint64_t), pImage->cbCluster);
+ if ((uint64_t)pImage->cbL1Table != RT_ALIGN_64(pImage->cL1TableEntries * sizeof(uint64_t), pImage->cbCluster))
+ rc = vdIfError(pImage->pIfError, VERR_INVALID_STATE, RT_SRC_POS,
+ N_("QCOW: L1 table size overflow in image '%s'"),
+ pImage->pszFilename);
+ }
+ }
+
+ /** @todo Check that there are no compressed clusters in the image
+ * (by traversing the L2 tables and checking each offset).
+ * Refuse to open such images.
+ */
+
+ if ( RT_SUCCESS(rc)
+ && pImage->cbBackingFilename
+ && pImage->offBackingFilename)
+ {
+ /* Load backing filename from image. */
+ pImage->pszBackingFilename = RTStrAlloc(pImage->cbBackingFilename + 1); /* +1 for \0 terminator. */
+ if (pImage->pszBackingFilename)
+ {
+ RT_BZERO(pImage->pszBackingFilename, pImage->cbBackingFilename + 1);
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ pImage->offBackingFilename, pImage->pszBackingFilename,
+ pImage->cbBackingFilename);
+ if (RT_SUCCESS(rc))
+ rc = RTStrValidateEncoding(pImage->pszBackingFilename);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pImage->cbRefcountTable
+ && pImage->offRefcountTable)
+ {
+ /* Load refcount table. */
+ Assert(pImage->cRefcountTableEntries);
+ pImage->paRefcountTable = (uint64_t *)RTMemAllocZ(pImage->cbRefcountTable);
+ if (RT_LIKELY(pImage->paRefcountTable))
+ {
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ pImage->offRefcountTable, pImage->paRefcountTable,
+ pImage->cbRefcountTable);
+ if (RT_SUCCESS(rc))
+ qcowTableConvertToHostEndianess(pImage->paRefcountTable,
+ pImage->cRefcountTableEntries);
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("QCow: Reading refcount table of image '%s' failed"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ N_("QCow: Allocating memory for refcount table of image '%s' failed"),
+ pImage->pszFilename);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ qcowTableMasksInit(pImage);
+
+ /* Allocate L1 table. */
+ pImage->paL1Table = (uint64_t *)RTMemAllocZ(pImage->cbL1Table);
+ if (pImage->paL1Table)
+ {
+ /* Read from the image. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ pImage->offL1Table, pImage->paL1Table,
+ pImage->cbL1Table);
+ if (RT_SUCCESS(rc))
+ qcowTableConvertToHostEndianess(pImage->paL1Table, pImage->cL1TableEntries);
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("QCow: Reading the L1 table for image '%s' failed"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ N_("QCow: Out of memory allocating L1 table for image '%s'"),
+ pImage->pszFilename);
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ /* else: Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("Qcow: Creating the L2 table cache for image '%s' failed"),
+ pImage->pszFilename);
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 512;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 512;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+ }
+ else
+ qcowFreeImage(pImage, false);
+ return rc;
+}
+
+/**
+ * Internal: Create a qcow image.
+ */
+static int qcowCreateImage(PQCOWIMAGE pImage, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry,
+ PCVDGEOMETRY pLCHSGeometry, unsigned uOpenFlags,
+ PVDINTERFACEPROGRESS pIfProgress,
+ unsigned uPercentStart, unsigned uPercentSpan)
+{
+ RT_NOREF1(pszComment);
+ int rc;
+ int32_t fOpen;
+
+ if (!(uImageFlags & VD_IMAGE_FLAGS_FIXED))
+ {
+ rc = qcowL2TblCacheCreate(pImage);
+ if (RT_SUCCESS(rc))
+ {
+ pImage->uOpenFlags = uOpenFlags & ~VD_OPEN_FLAGS_READONLY;
+ pImage->uImageFlags = uImageFlags;
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ pImage->LCHSGeometry = *pLCHSGeometry;
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ /* Create image file. */
+ fOpen = VDOpenFlagsToFileOpenFlags(pImage->uOpenFlags, true /* fCreate */);
+ rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename, fOpen, &pImage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ /* Init image state. */
+ pImage->uVersion = 1; /* We create only version 1 images at the moment. */
+ pImage->cbSize = cbSize;
+ pImage->cbCluster = QCOW_CLUSTER_SIZE_DEFAULT;
+ pImage->cbL2Table = qcowCluster2Byte(pImage, QCOW_L2_CLUSTERS_DEFAULT);
+ pImage->cL2TableEntries = pImage->cbL2Table / sizeof(uint64_t);
+ pImage->cL1TableEntries = cbSize / (pImage->cbCluster * pImage->cL2TableEntries);
+ if (cbSize % (pImage->cbCluster * pImage->cL2TableEntries))
+ pImage->cL1TableEntries++;
+ pImage->cbL1Table = RT_ALIGN_64(pImage->cL1TableEntries * sizeof(uint64_t), pImage->cbCluster);
+ pImage->offL1Table = QCOW_V1_HDR_SIZE;
+ pImage->cbBackingFilename = 0;
+ pImage->offBackingFilename = 0;
+ pImage->offNextCluster = RT_ALIGN_64(QCOW_V1_HDR_SIZE + pImage->cbL1Table, pImage->cbCluster);
+ qcowTableMasksInit(pImage);
+
+ /* Init L1 table. */
+ pImage->paL1Table = (uint64_t *)RTMemAllocZ(pImage->cbL1Table);
+ if (RT_LIKELY(pImage->paL1Table))
+ {
+ if (RT_SUCCESS(rc))
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan * 98 / 100);
+
+ rc = qcowFlushImage(pImage);
+ if (RT_SUCCESS(rc))
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage, pImage->offNextCluster);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS, N_("QCow: cannot allocate memory for L1 table of image '%s'"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("QCow: cannot create image '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("QCow: Failed to create L2 cache for image '%s'"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_VD_INVALID_TYPE, RT_SRC_POS, N_("QCow: cannot create fixed image '%s'"), pImage->pszFilename);
+
+ if (RT_SUCCESS(rc))
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan);
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0];
+ pImage->RegionList.fFlags = 0;
+ pImage->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 512;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 512;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pImage->cbSize;
+ }
+ else
+ qcowFreeImage(pImage, rc != VERR_ALREADY_EXISTS);
+ return rc;
+}
+
+/**
+ * Rollback anything done during async cluster allocation.
+ *
+ * @returns VBox status code.
+ * @param pImage The image instance data.
+ * @param pIoCtx The I/O context.
+ * @param pClusterAlloc The cluster allocation to rollback.
+ */
+static int qcowAsyncClusterAllocRollback(PQCOWIMAGE pImage, PVDIOCTX pIoCtx, PQCOWCLUSTERASYNCALLOC pClusterAlloc)
+{
+ RT_NOREF1(pIoCtx);
+ int rc = VINF_SUCCESS;
+
+ switch (pClusterAlloc->enmAllocState)
+ {
+ case QCOWCLUSTERASYNCALLOCSTATE_L2_ALLOC:
+ case QCOWCLUSTERASYNCALLOCSTATE_L2_LINK:
+ {
+ /* Revert the L1 table entry */
+ pImage->paL1Table[pClusterAlloc->idxL1] = 0;
+ pImage->pL2TblAlloc = NULL;
+
+ /* Assumption right now is that the L1 table is not modified on storage if the link fails. */
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage, pClusterAlloc->offNextClusterOld);
+ qcowL2TblCacheEntryRelease(pClusterAlloc->pL2Entry); /* Release L2 cache entry. */
+ Assert(!pClusterAlloc->pL2Entry->cRefs);
+ qcowL2TblCacheEntryFree(pImage, pClusterAlloc->pL2Entry); /* Free it, it is not in the cache yet. */
+ break;
+ }
+ case QCOWCLUSTERASYNCALLOCSTATE_USER_ALLOC:
+ case QCOWCLUSTERASYNCALLOCSTATE_USER_LINK:
+ {
+ /* Assumption right now is that the L2 table is not modified if the link fails. */
+ pClusterAlloc->pL2Entry->paL2Tbl[pClusterAlloc->idxL2] = 0;
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage, pClusterAlloc->offNextClusterOld);
+ qcowL2TblCacheEntryRelease(pClusterAlloc->pL2Entry); /* Release L2 cache entry. */
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid cluster allocation state %d\n", pClusterAlloc->enmAllocState));
+ rc = VERR_INVALID_STATE;
+ }
+
+ RTMemFree(pClusterAlloc);
+ return rc;
+}
+
+/**
+ * Updates the state of the async cluster allocation.
+ *
+ * @returns VBox status code.
+ * @param pBackendData The opaque backend data.
+ * @param pIoCtx I/O context associated with this request.
+ * @param pvUser Opaque user data passed during a read/write request.
+ * @param rcReq Status code for the completed request.
+ */
+static DECLCALLBACK(int) qcowAsyncClusterAllocUpdate(void *pBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+ int rc = VINF_SUCCESS;
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ PQCOWCLUSTERASYNCALLOC pClusterAlloc = (PQCOWCLUSTERASYNCALLOC)pvUser;
+
+ if (RT_FAILURE(rcReq))
+ return qcowAsyncClusterAllocRollback(pImage, pIoCtx, pClusterAlloc);
+
+ AssertPtr(pClusterAlloc->pL2Entry);
+
+ switch (pClusterAlloc->enmAllocState)
+ {
+ case QCOWCLUSTERASYNCALLOCSTATE_L2_ALLOC:
+ {
+ /* Update the link in the in memory L1 table now. */
+ pImage->paL1Table[pClusterAlloc->idxL1] = pClusterAlloc->pL2Entry->offL2Tbl;
+
+ /* Update the link in the on disk L1 table now. */
+ pClusterAlloc->enmAllocState = QCOWCLUSTERASYNCALLOCSTATE_L2_LINK;
+ rc = qcowTblWrite(pImage, pIoCtx, pImage->offL1Table, pImage->paL1Table,
+ pImage->cbL1Table, pImage->cL1TableEntries,
+ qcowAsyncClusterAllocUpdate, pClusterAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ /* Rollback. */
+ qcowAsyncClusterAllocRollback(pImage, pIoCtx, pClusterAlloc);
+ break;
+ }
+ /* Success, fall through. */
+ }
+ RT_FALL_THRU();
+ case QCOWCLUSTERASYNCALLOCSTATE_L2_LINK:
+ {
+ /* L2 link updated in L1 , save L2 entry in cache and allocate new user data cluster. */
+ uint64_t offData = qcowClusterAllocate(pImage, 1);
+
+ pImage->pL2TblAlloc = NULL;
+ qcowL2TblCacheEntryInsert(pImage, pClusterAlloc->pL2Entry);
+
+ pClusterAlloc->enmAllocState = QCOWCLUSTERASYNCALLOCSTATE_USER_ALLOC;
+ pClusterAlloc->offNextClusterOld = offData;
+ pClusterAlloc->offClusterNew = offData;
+
+ /* Write data. */
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ offData, pIoCtx, pClusterAlloc->cbToWrite,
+ qcowAsyncClusterAllocUpdate, pClusterAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ qcowAsyncClusterAllocRollback(pImage, pIoCtx, pClusterAlloc);
+ RTMemFree(pClusterAlloc);
+ break;
+ }
+ }
+ RT_FALL_THRU();
+ case QCOWCLUSTERASYNCALLOCSTATE_USER_ALLOC:
+ {
+ pClusterAlloc->enmAllocState = QCOWCLUSTERASYNCALLOCSTATE_USER_LINK;
+ pClusterAlloc->pL2Entry->paL2Tbl[pClusterAlloc->idxL2] = pClusterAlloc->offClusterNew;
+
+ /* Link L2 table and update it. */
+ rc = qcowTblWrite(pImage, pIoCtx, pImage->paL1Table[pClusterAlloc->idxL1],
+ pClusterAlloc->pL2Entry->paL2Tbl,
+ pImage->cbL2Table, pImage->cL2TableEntries,
+ qcowAsyncClusterAllocUpdate, pClusterAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ qcowAsyncClusterAllocRollback(pImage, pIoCtx, pClusterAlloc);
+ RTMemFree(pClusterAlloc);
+ break;
+ }
+ }
+ RT_FALL_THRU();
+ case QCOWCLUSTERASYNCALLOCSTATE_USER_LINK:
+ {
+ /* Everything done without errors, signal completion. */
+ qcowL2TblCacheEntryRelease(pClusterAlloc->pL2Entry);
+ RTMemFree(pClusterAlloc);
+ rc = VINF_SUCCESS;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid async cluster allocation state %d\n",
+ pClusterAlloc->enmAllocState));
+ }
+
+ return rc;
+}
+
+/**
+ * Reads a compressed cluster, inflates it and copies the amount of data requested
+ * into the given I/O context.
+ *
+ * @returns VBox status code.
+ * @param pImage The image instance data.
+ * @param pIoCtx The I/O context.
+ * @param offCluster Where to start reading in the uncompressed cluster.
+ * @param cbToRead How much to read in the uncomrpessed cluster.
+ * @param offFile Offset where the compressed cluster is stored in the image.
+ * @param cbCompressedCluster Size of the comrpessed cluster in bytes.
+ */
+static int qcowReadCompressedCluster(PQCOWIMAGE pImage, PVDIOCTX pIoCtx,
+ uint32_t offCluster, size_t cbToRead,
+ uint64_t offFile, size_t cbCompressedCluster)
+{
+ int rc = VINF_SUCCESS;
+
+ AssertReturn(!(pImage->uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO), VERR_NOT_SUPPORTED); /* Only synchronous I/O supported so far. */
+
+ if (cbCompressedCluster > pImage->cbCompCluster)
+ {
+ void *pvCompClusterNew = RTMemRealloc(pImage->pvCompCluster, cbCompressedCluster);
+ if (RT_LIKELY(pvCompClusterNew))
+ {
+ pImage->pvCompCluster = pvCompClusterNew;
+ pImage->cbCompCluster = cbCompressedCluster;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdIfIoIntFileReadMeta(pImage->pIfIo, pImage->pStorage,
+ offFile, pImage->pvCompCluster,
+ cbCompressedCluster, NULL,
+ NULL, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (!pImage->pvCluster)
+ {
+ pImage->pvCluster = RTMemAllocZ(pImage->cbCluster);
+ if (!pImage->pvCluster)
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbDecomp = 0;
+
+ rc = RTZipBlockDecompress(RTZIPTYPE_ZLIB_NO_HEADER, 0 /*fFlags*/,
+ pImage->pvCompCluster, cbCompressedCluster, NULL,
+ pImage->pvCluster, pImage->cbCluster, &cbDecomp);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbDecomp == pImage->cbCluster);
+ vdIfIoIntIoCtxCopyTo(pImage->pIfIo, pIoCtx,
+ (uint8_t *)pImage->pvCluster + offCluster,
+ cbToRead);
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnProbe */
+static DECLCALLBACK(int) qcowProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType)
+{
+ RT_NOREF(pVDIfsDisk, enmDesiredType);
+ LogFlowFunc(("pszFilename=\"%s\" pVDIfsDisk=%#p pVDIfsImage=%#p\n", pszFilename, pVDIfsDisk, pVDIfsImage));
+ PVDIOSTORAGE pStorage = NULL;
+ uint64_t cbFile;
+ int rc = VINF_SUCCESS;
+
+ /* Get I/O interface. */
+ PVDINTERFACEIOINT pIfIo = VDIfIoIntGet(pVDIfsImage);
+ AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+ /*
+ * Open the file and read the footer.
+ */
+ rc = vdIfIoIntFileOpen(pIfIo, pszFilename,
+ VDOpenFlagsToFileOpenFlags(VD_OPEN_FLAGS_READONLY,
+ false /* fCreate */),
+ &pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdIfIoIntFileGetSize(pIfIo, pStorage, &cbFile);
+ if ( RT_SUCCESS(rc)
+ && cbFile > sizeof(QCowHeader))
+ {
+ QCowHeader Header;
+
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, 0, &Header, sizeof(Header));
+ if ( RT_SUCCESS(rc)
+ && qcowHdrConvertToHostEndianess(&Header))
+ *penmType = VDTYPE_HDD;
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+ else
+ rc = VERR_VD_GEN_INVALID_HEADER;
+ }
+
+ if (pStorage)
+ vdIfIoIntFileClose(pIfIo, pStorage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnOpen */
+static DECLCALLBACK(int) qcowOpen(const char *pszFilename, unsigned uOpenFlags,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ VDTYPE enmType, void **ppBackendData)
+{
+ RT_NOREF1(enmType); /**< @todo r=klaus make use of the type info. */
+
+ LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p enmType=%u ppBackendData=%#p\n",
+ pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData));
+ int rc;
+
+ /* Check open flags. All valid flags are supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+
+ PQCOWIMAGE pImage = (PQCOWIMAGE)RTMemAllocZ(RT_UOFFSETOF(QCOWIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = qcowOpenImage(pImage, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ else
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnCreate */
+static DECLCALLBACK(int) qcowCreate(const char *pszFilename, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation, VDTYPE enmType,
+ void **ppBackendData)
+{
+ RT_NOREF1(pUuid);
+ LogFlowFunc(("pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p enmType=%u ppBackendData=%#p\n",
+ pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData));
+ int rc;
+
+ /* Check the VD container type. */
+ if (enmType != VDTYPE_HDD)
+ return VERR_VD_INVALID_TYPE;
+
+ /* Check open flags. All valid flags are supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pPCHSGeometry, VERR_INVALID_POINTER);
+ AssertPtrReturn(pLCHSGeometry, VERR_INVALID_POINTER);
+
+ PQCOWIMAGE pImage = (PQCOWIMAGE)RTMemAllocZ(RT_UOFFSETOF(QCOWIMAGE, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = qcowCreateImage(pImage, cbSize, uImageFlags, pszComment,
+ pPCHSGeometry, pLCHSGeometry, uOpenFlags,
+ pIfProgress, uPercentStart, uPercentSpan);
+ if (RT_SUCCESS(rc))
+ {
+ /* So far the image is opened in read/write mode. Make sure the
+ * image is opened in read-only mode if the caller requested that. */
+ if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ {
+ qcowFreeImage(pImage, false);
+ rc = qcowOpenImage(pImage, uOpenFlags);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pImage;
+ }
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRename */
+static DECLCALLBACK(int) qcowRename(void *pBackendData, const char *pszFilename)
+{
+ LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename));
+ int rc = VINF_SUCCESS;
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ /* Check arguments. */
+ AssertReturn((pImage && pszFilename && *pszFilename), VERR_INVALID_PARAMETER);
+
+ /* Close the image. */
+ rc = qcowFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ {
+ /* Rename the file. */
+ rc = vdIfIoIntFileMove(pImage->pIfIo, pImage->pszFilename, pszFilename, 0);
+ if (RT_SUCCESS(rc))
+ {
+ /* Update pImage with the new information. */
+ pImage->pszFilename = pszFilename;
+
+ /* Open the old image with new name. */
+ rc = qcowOpenImage(pImage, pImage->uOpenFlags);
+ }
+ else
+ {
+ /* The move failed, try to reopen the original image. */
+ int rc2 = qcowOpenImage(pImage, pImage->uOpenFlags);
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnClose */
+static DECLCALLBACK(int) qcowClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ int rc = qcowFreeImage(pImage, fDelete);
+ RTMemFree(pImage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) qcowRead(void *pBackendData, uint64_t uOffset, size_t cbToRead,
+ PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToRead=%zu pcbActuallyRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToRead, pcbActuallyRead));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ uint32_t offCluster = 0;
+ uint32_t idxL1 = 0;
+ uint32_t idxL2 = 0;
+ uint64_t offFile = 0;
+ int rc;
+
+ AssertPtr(pImage);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToRead % 512 == 0);
+ AssertPtrReturn(pIoCtx, VERR_INVALID_POINTER);
+ AssertReturn(cbToRead, VERR_INVALID_PARAMETER);
+ AssertReturn(uOffset + cbToRead <= pImage->cbSize, VERR_INVALID_PARAMETER);
+
+ qcowConvertLogicalOffset(pImage, uOffset, &idxL1, &idxL2, &offCluster);
+
+ /* Clip read size to remain in the cluster. */
+ cbToRead = RT_MIN(cbToRead, pImage->cbCluster - offCluster);
+
+ /* Get offset in image. */
+ bool fCompressedCluster = false;
+ size_t cbCompressedCluster = 0;
+ rc = qcowConvertToImageOffset(pImage, pIoCtx, idxL1, idxL2, offCluster,
+ &offFile, &fCompressedCluster, &cbCompressedCluster);
+ if (RT_SUCCESS(rc))
+ {
+ if (!fCompressedCluster)
+ rc = vdIfIoIntFileReadUser(pImage->pIfIo, pImage->pStorage, offFile,
+ pIoCtx, cbToRead);
+ else
+ rc = qcowReadCompressedCluster(pImage, pIoCtx, offCluster, cbToRead, offFile, cbCompressedCluster);
+ }
+
+ if ( ( RT_SUCCESS(rc)
+ || rc == VERR_VD_BLOCK_FREE
+ || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ && pcbActuallyRead)
+ *pcbActuallyRead = cbToRead;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) qcowWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead,
+ size_t *pcbPostRead, unsigned fWrite)
+{
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToWrite=%zu pcbWriteProcess=%#p pcbPreRead=%#p pcbPostRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ uint32_t offCluster = 0;
+ uint32_t idxL1 = 0;
+ uint32_t idxL2 = 0;
+ uint64_t offImage = 0;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ Assert(!(uOffset % 512));
+ Assert(!(cbToWrite % 512));
+ AssertPtrReturn(pIoCtx, VERR_INVALID_POINTER);
+ AssertReturn(cbToWrite, VERR_INVALID_PARAMETER);
+ AssertReturn(uOffset + cbToWrite <= pImage->cbSize, VERR_INVALID_PARAMETER);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ /* Convert offset to L1, L2 index and cluster offset. */
+ qcowConvertLogicalOffset(pImage, uOffset, &idxL1, &idxL2, &offCluster);
+
+ /* Clip write size to remain in the cluster. */
+ cbToWrite = RT_MIN(cbToWrite, pImage->cbCluster - offCluster);
+ Assert(!(cbToWrite % 512));
+
+ /* Get offset in image. */
+ bool fCompressedCluster = false;
+ size_t cbCompressedCluster = 0;
+ rc = qcowConvertToImageOffset(pImage, pIoCtx, idxL1, idxL2, offCluster,
+ &offImage, &fCompressedCluster, &cbCompressedCluster);
+ if (RT_SUCCESS(rc))
+ {
+ if (!fCompressedCluster)
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ offImage, pIoCtx, cbToWrite, NULL, NULL);
+ else
+ rc = VERR_NOT_SUPPORTED; /** @todo Support writing compressed clusters */
+ }
+ else if (rc == VERR_VD_BLOCK_FREE)
+ {
+ if ( cbToWrite == pImage->cbCluster
+ && !(fWrite & VD_WRITE_NO_ALLOC))
+ {
+ PQCOWL2CACHEENTRY pL2Entry = NULL;
+
+ /* Full cluster write to previously unallocated cluster.
+ * Allocate cluster and write data. */
+ Assert(!offCluster);
+
+ do
+ {
+ /* Check if we have to allocate a new cluster for L2 tables. */
+ if (!pImage->paL1Table[idxL1])
+ {
+ uint64_t offL2Tbl;
+ PQCOWCLUSTERASYNCALLOC pL2ClusterAlloc = NULL;
+
+ /* Allocate new async cluster allocation state. */
+ pL2ClusterAlloc = (PQCOWCLUSTERASYNCALLOC)RTMemAllocZ(sizeof(QCOWCLUSTERASYNCALLOC));
+ if (RT_UNLIKELY(!pL2ClusterAlloc))
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pL2Entry = qcowL2TblCacheEntryAlloc(pImage);
+ if (!pL2Entry)
+ {
+ rc = VERR_NO_MEMORY;
+ RTMemFree(pL2ClusterAlloc);
+ break;
+ }
+
+ offL2Tbl = qcowClusterAllocate(pImage, qcowByte2Cluster(pImage, pImage->cbL2Table));
+ pL2Entry->offL2Tbl = offL2Tbl;
+ memset(pL2Entry->paL2Tbl, 0, pImage->cbL2Table);
+
+ pL2ClusterAlloc->enmAllocState = QCOWCLUSTERASYNCALLOCSTATE_L2_ALLOC;
+ pL2ClusterAlloc->offNextClusterOld = offL2Tbl;
+ pL2ClusterAlloc->offClusterNew = offL2Tbl;
+ pL2ClusterAlloc->idxL1 = idxL1;
+ pL2ClusterAlloc->idxL2 = idxL2;
+ pL2ClusterAlloc->cbToWrite = cbToWrite;
+ pL2ClusterAlloc->pL2Entry = pL2Entry;
+
+ pImage->pL2TblAlloc = pL2Entry;
+
+ LogFlowFunc(("Allocating new L2 table at cluster offset %llu\n", offL2Tbl));
+
+ /*
+ * Write the L2 table first and link to the L1 table afterwards.
+ * If something unexpected happens the worst case which can happen
+ * is a leak of some clusters.
+ */
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ offL2Tbl, pL2Entry->paL2Tbl, pImage->cbL2Table, pIoCtx,
+ qcowAsyncClusterAllocUpdate, pL2ClusterAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ RTMemFree(pL2ClusterAlloc);
+ qcowL2TblCacheEntryFree(pImage, pL2Entry);
+ break;
+ }
+
+ rc = qcowAsyncClusterAllocUpdate(pImage, pIoCtx, pL2ClusterAlloc, rc);
+ }
+ else
+ {
+ LogFlowFunc(("Fetching L2 table at cluster offset %llu\n", pImage->paL1Table[idxL1]));
+
+ rc = qcowL2TblCacheFetch(pImage, pIoCtx, pImage->paL1Table[idxL1],
+ &pL2Entry);
+ if (RT_SUCCESS(rc))
+ {
+ PQCOWCLUSTERASYNCALLOC pDataClusterAlloc = NULL;
+
+ /* Allocate new async cluster allocation state. */
+ pDataClusterAlloc = (PQCOWCLUSTERASYNCALLOC)RTMemAllocZ(sizeof(QCOWCLUSTERASYNCALLOC));
+ if (RT_UNLIKELY(!pDataClusterAlloc))
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* Allocate new cluster for the data. */
+ uint64_t offData = qcowClusterAllocate(pImage, 1);
+
+ pDataClusterAlloc->enmAllocState = QCOWCLUSTERASYNCALLOCSTATE_USER_ALLOC;
+ pDataClusterAlloc->offNextClusterOld = offData;
+ pDataClusterAlloc->offClusterNew = offData;
+ pDataClusterAlloc->idxL1 = idxL1;
+ pDataClusterAlloc->idxL2 = idxL2;
+ pDataClusterAlloc->cbToWrite = cbToWrite;
+ pDataClusterAlloc->pL2Entry = pL2Entry;
+
+ /* Write data. */
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ offData, pIoCtx, cbToWrite,
+ qcowAsyncClusterAllocUpdate, pDataClusterAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ RTMemFree(pDataClusterAlloc);
+ break;
+ }
+
+ rc = qcowAsyncClusterAllocUpdate(pImage, pIoCtx, pDataClusterAlloc, rc);
+ }
+ }
+
+ } while (0);
+
+ *pcbPreRead = 0;
+ *pcbPostRead = 0;
+ }
+ else
+ {
+ /* Trying to do a partial write to an unallocated cluster. Don't do
+ * anything except letting the upper layer know what to do. */
+ *pcbPreRead = offCluster;
+ *pcbPostRead = pImage->cbCluster - cbToWrite - *pcbPreRead;
+ }
+ }
+
+ if (pcbWriteProcess)
+ *pcbWriteProcess = cbToWrite;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) qcowFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ AssertPtrReturn(pIoCtx, VERR_INVALID_PARAMETER);
+
+ if ( pImage->pStorage
+ && !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ QCowHeader Header;
+
+ rc = qcowTblWrite(pImage, pIoCtx, pImage->offL1Table, pImage->paL1Table,
+ pImage->cbL1Table, pImage->cL1TableEntries, NULL, NULL);
+ if (RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ /* Write header. */
+ size_t cbHeader = 0;
+ qcowHdrConvertFromHostEndianess(pImage, &Header, &cbHeader);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ 0, &Header, cbHeader,
+ pIoCtx, NULL, NULL);
+ if (RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = vdIfIoIntFileFlush(pImage->pIfIo, pImage->pStorage,
+ pIoCtx, NULL, NULL);
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetVersion */
+static DECLCALLBACK(unsigned) qcowGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ return pImage->uVersion;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetFileSize */
+static DECLCALLBACK(uint64_t) qcowGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ uint64_t cb = 0;
+
+ AssertPtrReturn(pImage, 0);
+
+ uint64_t cbFile;
+ if (pImage->pStorage)
+ {
+ int rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &cbFile);
+ if (RT_SUCCESS(rc))
+ cb += cbFile;
+ }
+
+ LogFlowFunc(("returns %lld\n", cb));
+ return cb;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetPCHSGeometry */
+static DECLCALLBACK(int) qcowGetPCHSGeometry(void *pBackendData, PVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->PCHSGeometry.cCylinders)
+ *pPCHSGeometry = pImage->PCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetPCHSGeometry */
+static DECLCALLBACK(int) qcowSetPCHSGeometry(void *pBackendData, PCVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n",
+ pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pImage->PCHSGeometry = *pPCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetLCHSGeometry */
+static DECLCALLBACK(int) qcowGetLCHSGeometry(void *pBackendData, PVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->LCHSGeometry.cCylinders)
+ *pLCHSGeometry = pImage->LCHSGeometry;
+ else
+ rc = VERR_VD_GEOMETRY_NOT_SET;
+
+ LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", rc, pLCHSGeometry->cCylinders,
+ pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetLCHSGeometry */
+static DECLCALLBACK(int) qcowSetLCHSGeometry(void *pBackendData, PCVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n", pBackendData,
+ pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pImage->LCHSGeometry = *pLCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */
+static DECLCALLBACK(int) qcowQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
+{
+ LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList));
+ PQCOWIMAGE pThis = (PQCOWIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ *ppRegionList = &pThis->RegionList;
+ LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRegionListRelease */
+static DECLCALLBACK(void) qcowRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
+{
+ RT_NOREF1(pRegionList);
+ LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList));
+ PQCOWIMAGE pThis = (PQCOWIMAGE)pBackendData;
+ AssertPtr(pThis); RT_NOREF(pThis);
+
+ /* Nothing to do here. */
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetImageFlags */
+static DECLCALLBACK(unsigned) qcowGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uImageFlags));
+ return pImage->uImageFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetOpenFlags */
+static DECLCALLBACK(unsigned) qcowGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uOpenFlags));
+ return pImage->uOpenFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetOpenFlags */
+static DECLCALLBACK(int) qcowSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p\n uOpenFlags=%#x", pBackendData, uOpenFlags));
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Image must be opened and the new flags must be valid. */
+ if (!pImage || (uOpenFlags & ~( VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO
+ | VD_OPEN_FLAGS_ASYNC_IO | VD_OPEN_FLAGS_SHAREABLE
+ | VD_OPEN_FLAGS_SEQUENTIAL | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)))
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ /* Implement this operation via reopening the image. */
+ rc = qcowFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ rc = qcowOpenImage(pImage, uOpenFlags);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetComment */
+VD_BACKEND_CALLBACK_GET_COMMENT_DEF_NOT_SUPPORTED(qcowGetComment);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetComment */
+VD_BACKEND_CALLBACK_SET_COMMENT_DEF_NOT_SUPPORTED(qcowSetComment, PQCOWIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(qcowGetUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(qcowSetUuid, PQCOWIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(qcowGetModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(qcowSetModificationUuid, PQCOWIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(qcowGetParentUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(qcowSetParentUuid, PQCOWIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(qcowGetParentModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(qcowSetParentModificationUuid, PQCOWIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnDump */
+static DECLCALLBACK(void) qcowDump(void *pBackendData)
+{
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ AssertPtrReturnVoid(pImage);
+ vdIfErrorMessage(pImage->pIfError, "Header: Geometry PCHS=%u/%u/%u LCHS=%u/%u/%u cbSector=%llu\n",
+ pImage->PCHSGeometry.cCylinders, pImage->PCHSGeometry.cHeads, pImage->PCHSGeometry.cSectors,
+ pImage->LCHSGeometry.cCylinders, pImage->LCHSGeometry.cHeads, pImage->LCHSGeometry.cSectors,
+ pImage->cbSize / 512);
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentFilename */
+static DECLCALLBACK(int) qcowGetParentFilename(void *pBackendData, char **ppszParentFilename)
+{
+ int rc = VINF_SUCCESS;
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ AssertPtr(pImage);
+ if (pImage)
+ if (pImage->pszBackingFilename)
+ *ppszParentFilename = RTStrDup(pImage->pszBackingFilename);
+ else
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentFilename */
+static DECLCALLBACK(int) qcowSetParentFilename(void *pBackendData, const char *pszParentFilename)
+{
+ int rc = VINF_SUCCESS;
+ PQCOWIMAGE pImage = (PQCOWIMAGE)pBackendData;
+
+ AssertPtr(pImage);
+ if (pImage)
+ {
+ if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else if ( pImage->pszBackingFilename
+ && (strlen(pszParentFilename) > pImage->cbBackingFilename))
+ rc = VERR_NOT_SUPPORTED; /* The new filename is longer than the old one. */
+ else
+ {
+ if (pImage->pszBackingFilename)
+ RTStrFree(pImage->pszBackingFilename);
+ pImage->pszBackingFilename = RTStrDup(pszParentFilename);
+ if (!pImage->pszBackingFilename)
+ rc = VERR_NO_STR_MEMORY;
+ else
+ {
+ if (!pImage->offBackingFilename)
+ {
+ /* Allocate new cluster. */
+ uint64_t offData = qcowClusterAllocate(pImage, 1);
+
+ Assert((offData & UINT32_MAX) == offData);
+ pImage->offBackingFilename = (uint32_t)offData;
+ pImage->cbBackingFilename = (uint32_t)strlen(pszParentFilename);
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage,
+ offData + pImage->cbCluster);
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ pImage->offBackingFilename,
+ pImage->pszBackingFilename,
+ strlen(pImage->pszBackingFilename));
+ }
+ }
+ }
+ else
+ rc = VERR_VD_NOT_OPENED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+
+const VDIMAGEBACKEND g_QCowBackend =
+{
+ /* u32Version */
+ VD_IMGBACKEND_VERSION,
+ /* pszBackendName */
+ "QCOW",
+ /* uBackendCaps */
+ VD_CAP_FILE | VD_CAP_VFS | VD_CAP_CREATE_DYNAMIC | VD_CAP_DIFF | VD_CAP_ASYNC,
+ /* paFileExtensions */
+ s_aQCowFileExtensions,
+ /* paConfigInfo */
+ NULL,
+ /* pfnProbe */
+ qcowProbe,
+ /* pfnOpen */
+ qcowOpen,
+ /* pfnCreate */
+ qcowCreate,
+ /* pfnRename */
+ qcowRename,
+ /* pfnClose */
+ qcowClose,
+ /* pfnRead */
+ qcowRead,
+ /* pfnWrite */
+ qcowWrite,
+ /* pfnFlush */
+ qcowFlush,
+ /* pfnDiscard */
+ NULL,
+ /* pfnGetVersion */
+ qcowGetVersion,
+ /* pfnGetFileSize */
+ qcowGetFileSize,
+ /* pfnGetPCHSGeometry */
+ qcowGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ qcowSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ qcowGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ qcowSetLCHSGeometry,
+ /* pfnQueryRegions */
+ qcowQueryRegions,
+ /* pfnRegionListRelease */
+ qcowRegionListRelease,
+ /* pfnGetImageFlags */
+ qcowGetImageFlags,
+ /* pfnGetOpenFlags */
+ qcowGetOpenFlags,
+ /* pfnSetOpenFlags */
+ qcowSetOpenFlags,
+ /* pfnGetComment */
+ qcowGetComment,
+ /* pfnSetComment */
+ qcowSetComment,
+ /* pfnGetUuid */
+ qcowGetUuid,
+ /* pfnSetUuid */
+ qcowSetUuid,
+ /* pfnGetModificationUuid */
+ qcowGetModificationUuid,
+ /* pfnSetModificationUuid */
+ qcowSetModificationUuid,
+ /* pfnGetParentUuid */
+ qcowGetParentUuid,
+ /* pfnSetParentUuid */
+ qcowSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ qcowGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ qcowSetParentModificationUuid,
+ /* pfnDump */
+ qcowDump,
+ /* pfnGetTimestamp */
+ NULL,
+ /* pfnGetParentTimestamp */
+ NULL,
+ /* pfnSetParentTimestamp */
+ NULL,
+ /* pfnGetParentFilename */
+ qcowGetParentFilename,
+ /* pfnSetParentFilename */
+ qcowSetParentFilename,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName,
+ /* pfnCompact */
+ NULL,
+ /* pfnResize */
+ NULL,
+ /* pfnRepair */
+ NULL,
+ /* pfnTraverseMetadata */
+ NULL,
+ /* u32VersionEnd */
+ VD_IMGBACKEND_VERSION
+};