summaryrefslogtreecommitdiffstats
path: root/src/VBox/Storage/VDI.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Storage/VDI.cpp
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Storage/VDI.cpp')
-rw-r--r--src/VBox/Storage/VDI.cpp3269
1 files changed, 3269 insertions, 0 deletions
diff --git a/src/VBox/Storage/VDI.cpp b/src/VBox/Storage/VDI.cpp
new file mode 100644
index 00000000..6e38c593
--- /dev/null
+++ b/src/VBox/Storage/VDI.cpp
@@ -0,0 +1,3269 @@
+/* $Id: VDI.cpp $ */
+/** @file
+ * Virtual Disk Image (VDI), Core Code.
+ */
+
+/*
+ * Copyright (C) 2006-2023 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_VDI
+#include <VBox/vd-plugin.h>
+#include "VDICore.h"
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+
+#include "VDBackends.h"
+
+#define VDI_IMAGE_DEFAULT_BLOCK_SIZE _1M
+
+/** Macros for endianess conversion. */
+#define SET_ENDIAN_U32(conv, u32) (conv == VDIECONV_H2F ? RT_H2LE_U32(u32) : RT_LE2H_U32(u32))
+#define SET_ENDIAN_U64(conv, u64) (conv == VDIECONV_H2F ? RT_H2LE_U64(u64) : RT_LE2H_U64(u64))
+
+static const char *vdiAllocationBlockSize = "1048576";
+
+static const VDCONFIGINFO vdiConfigInfo[] =
+{
+ { "AllocationBlockSize", vdiAllocationBlockSize, VDCFGVALUETYPE_INTEGER, VD_CFGKEY_CREATEONLY },
+ { NULL, NULL, VDCFGVALUETYPE_INTEGER, 0 }
+};
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const VDFILEEXTENSION s_aVdiFileExtensions[] =
+{
+ {"vdi", VDTYPE_HDD},
+ {NULL, VDTYPE_INVALID}
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static unsigned getPowerOfTwo(unsigned uNumber);
+static void vdiInitPreHeader(PVDIPREHEADER pPreHdr);
+static int vdiValidatePreHeader(PVDIPREHEADER pPreHdr);
+static int vdiValidateHeader(PVDIHEADER pHeader);
+static void vdiSetupImageDesc(PVDIIMAGEDESC pImage);
+static int vdiUpdateHeader(PVDIIMAGEDESC pImage);
+static int vdiUpdateBlockInfo(PVDIIMAGEDESC pImage, unsigned uBlock);
+static int vdiUpdateHeaderAsync(PVDIIMAGEDESC pImage, PVDIOCTX pIoCtx);
+static int vdiUpdateBlockInfoAsync(PVDIIMAGEDESC pImage, unsigned uBlock, PVDIOCTX pIoCtx,
+ bool fUpdateHdr);
+
+/**
+ * Internal: Convert the PreHeader fields to the appropriate endianess.
+ * @param enmConv Direction of the conversion.
+ * @param pPreHdrConv Where to store the converted pre header.
+ * @param pPreHdr PreHeader pointer.
+ */
+static void vdiConvPreHeaderEndianess(VDIECONV enmConv, PVDIPREHEADER pPreHdrConv,
+ PVDIPREHEADER pPreHdr)
+{
+ memcpy(pPreHdrConv->szFileInfo, pPreHdr->szFileInfo, sizeof(pPreHdr->szFileInfo));
+ pPreHdrConv->u32Signature = SET_ENDIAN_U32(enmConv, pPreHdr->u32Signature);
+ pPreHdrConv->u32Version = SET_ENDIAN_U32(enmConv, pPreHdr->u32Version);
+}
+
+/**
+ * Internal: Convert the VDIDISKGEOMETRY fields to the appropriate endianess.
+ * @param enmConv Direction of the conversion.
+ * @param pDiskGeoConv Where to store the converted geometry.
+ * @param pDiskGeo Pointer to the disk geometry to convert.
+ */
+static void vdiConvGeometryEndianess(VDIECONV enmConv, PVDIDISKGEOMETRY pDiskGeoConv,
+ PVDIDISKGEOMETRY pDiskGeo)
+{
+ pDiskGeoConv->cCylinders = SET_ENDIAN_U32(enmConv, pDiskGeo->cCylinders);
+ pDiskGeoConv->cHeads = SET_ENDIAN_U32(enmConv, pDiskGeo->cHeads);
+ pDiskGeoConv->cSectors = SET_ENDIAN_U32(enmConv, pDiskGeo->cSectors);
+ pDiskGeoConv->cbSector = SET_ENDIAN_U32(enmConv, pDiskGeo->cbSector);
+}
+
+/**
+ * Internal: Convert the Header - version 0 fields to the appropriate endianess.
+ * @param enmConv Direction of the conversion.
+ * @param pHdrConv Where to store the converted header.
+ * @param pHdr Pointer to the version 0 header.
+ */
+static void vdiConvHeaderEndianessV0(VDIECONV enmConv, PVDIHEADER0 pHdrConv,
+ PVDIHEADER0 pHdr)
+{
+ memmove(pHdrConv->szComment, pHdr->szComment, sizeof(pHdr->szComment));
+ pHdrConv->u32Type = SET_ENDIAN_U32(enmConv, pHdr->u32Type);
+ pHdrConv->fFlags = SET_ENDIAN_U32(enmConv, pHdr->fFlags);
+ vdiConvGeometryEndianess(enmConv, &pHdrConv->LegacyGeometry, &pHdr->LegacyGeometry);
+ pHdrConv->cbDisk = SET_ENDIAN_U64(enmConv, pHdr->cbDisk);
+ pHdrConv->cbBlock = SET_ENDIAN_U32(enmConv, pHdr->cbBlock);
+ pHdrConv->cBlocks = SET_ENDIAN_U32(enmConv, pHdr->cBlocks);
+ pHdrConv->cBlocksAllocated = SET_ENDIAN_U32(enmConv, pHdr->cBlocksAllocated);
+ /* Don't convert the RTUUID fields. */
+ pHdrConv->uuidCreate = pHdr->uuidCreate;
+ pHdrConv->uuidModify = pHdr->uuidModify;
+ pHdrConv->uuidLinkage = pHdr->uuidLinkage;
+}
+
+/**
+ * Internal: Set the Header - version 1 fields to the appropriate endianess.
+ * @param enmConv Direction of the conversion.
+ * @param pHdrConv Where to store the converted header.
+ * @param pHdr Version 1 Header pointer.
+ */
+static void vdiConvHeaderEndianessV1(VDIECONV enmConv, PVDIHEADER1 pHdrConv,
+ PVDIHEADER1 pHdr)
+{
+ memmove(pHdrConv->szComment, pHdr->szComment, sizeof(pHdr->szComment));
+ pHdrConv->cbHeader = SET_ENDIAN_U32(enmConv, pHdr->cbHeader);
+ pHdrConv->u32Type = SET_ENDIAN_U32(enmConv, pHdr->u32Type);
+ pHdrConv->fFlags = SET_ENDIAN_U32(enmConv, pHdr->fFlags);
+ pHdrConv->offBlocks = SET_ENDIAN_U32(enmConv, pHdr->offBlocks);
+ pHdrConv->offData = SET_ENDIAN_U32(enmConv, pHdr->offData);
+ vdiConvGeometryEndianess(enmConv, &pHdrConv->LegacyGeometry, &pHdr->LegacyGeometry);
+ pHdrConv->u32Dummy = SET_ENDIAN_U32(enmConv, pHdr->u32Dummy);
+ pHdrConv->cbDisk = SET_ENDIAN_U64(enmConv, pHdr->cbDisk);
+ pHdrConv->cbBlock = SET_ENDIAN_U32(enmConv, pHdr->cbBlock);
+ pHdrConv->cbBlockExtra = SET_ENDIAN_U32(enmConv, pHdr->cbBlockExtra);
+ pHdrConv->cBlocks = SET_ENDIAN_U32(enmConv, pHdr->cBlocks);
+ pHdrConv->cBlocksAllocated = SET_ENDIAN_U32(enmConv, pHdr->cBlocksAllocated);
+ /* Don't convert the RTUUID fields. */
+ pHdrConv->uuidCreate = pHdr->uuidCreate;
+ pHdrConv->uuidModify = pHdr->uuidModify;
+ pHdrConv->uuidLinkage = pHdr->uuidLinkage;
+ pHdrConv->uuidParentModify = pHdr->uuidParentModify;
+}
+
+/**
+ * Internal: Set the Header - version 1plus fields to the appropriate endianess.
+ * @param enmConv Direction of the conversion.
+ * @param pHdrConv Where to store the converted header.
+ * @param pHdr Version 1+ Header pointer.
+ */
+static void vdiConvHeaderEndianessV1p(VDIECONV enmConv, PVDIHEADER1PLUS pHdrConv,
+ PVDIHEADER1PLUS pHdr)
+{
+ memmove(pHdrConv->szComment, pHdr->szComment, sizeof(pHdr->szComment));
+ pHdrConv->cbHeader = SET_ENDIAN_U32(enmConv, pHdr->cbHeader);
+ pHdrConv->u32Type = SET_ENDIAN_U32(enmConv, pHdr->u32Type);
+ pHdrConv->fFlags = SET_ENDIAN_U32(enmConv, pHdr->fFlags);
+ pHdrConv->offBlocks = SET_ENDIAN_U32(enmConv, pHdr->offBlocks);
+ pHdrConv->offData = SET_ENDIAN_U32(enmConv, pHdr->offData);
+ vdiConvGeometryEndianess(enmConv, &pHdrConv->LegacyGeometry, &pHdr->LegacyGeometry);
+ pHdrConv->u32Dummy = SET_ENDIAN_U32(enmConv, pHdr->u32Dummy);
+ pHdrConv->cbDisk = SET_ENDIAN_U64(enmConv, pHdr->cbDisk);
+ pHdrConv->cbBlock = SET_ENDIAN_U32(enmConv, pHdr->cbBlock);
+ pHdrConv->cbBlockExtra = SET_ENDIAN_U32(enmConv, pHdr->cbBlockExtra);
+ pHdrConv->cBlocks = SET_ENDIAN_U32(enmConv, pHdr->cBlocks);
+ pHdrConv->cBlocksAllocated = SET_ENDIAN_U32(enmConv, pHdr->cBlocksAllocated);
+ /* Don't convert the RTUUID fields. */
+ pHdrConv->uuidCreate = pHdr->uuidCreate;
+ pHdrConv->uuidModify = pHdr->uuidModify;
+ pHdrConv->uuidLinkage = pHdr->uuidLinkage;
+ pHdrConv->uuidParentModify = pHdr->uuidParentModify;
+ vdiConvGeometryEndianess(enmConv, &pHdrConv->LCHSGeometry, &pHdr->LCHSGeometry);
+}
+
+
+/**
+ * Internal: Set the appropriate endianess on all the Blocks pointed.
+ * @param enmConv Direction of the conversion.
+ * @param paBlocks Pointer to the block array.
+ * @param cEntries Number of entries in the block array.
+ *
+ * @note Unlike the other conversion functions this method does an in place conversion
+ * to avoid temporary memory allocations when writing the block array.
+ */
+static void vdiConvBlocksEndianess(VDIECONV enmConv, PVDIIMAGEBLOCKPOINTER paBlocks,
+ unsigned cEntries)
+{
+ for (unsigned i = 0; i < cEntries; i++)
+ paBlocks[i] = SET_ENDIAN_U32(enmConv, paBlocks[i]);
+}
+
+/**
+ * Internal: Flush the image file to disk.
+ */
+static void vdiFlushImage(PVDIIMAGEDESC pImage)
+{
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ /* Save header. */
+ int rc = vdiUpdateHeader(pImage);
+ AssertMsgRC(rc, ("vdiUpdateHeader() failed, filename=\"%s\", rc=%Rrc\n",
+ pImage->pszFilename, rc));
+ vdIfIoIntFileFlushSync(pImage->pIfIo, pImage->pStorage);
+ }
+}
+
+/**
+ * Internal: Free all allocated space for representing an image, and optionally
+ * delete the image from disk.
+ */
+static int vdiFreeImage(PVDIIMAGEDESC 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)
+ vdiFlushImage(pImage);
+
+ rc = vdIfIoIntFileClose(pImage->pIfIo, pImage->pStorage);
+ pImage->pStorage = NULL;
+ }
+
+ if (pImage->paBlocks)
+ {
+ RTMemFree(pImage->paBlocks);
+ pImage->paBlocks = NULL;
+ }
+
+ if (pImage->paBlocksRev)
+ {
+ RTMemFree(pImage->paBlocksRev);
+ pImage->paBlocksRev = NULL;
+ }
+
+ if (fDelete && pImage->pszFilename)
+ {
+ int rc2 = vdIfIoIntFileDelete(pImage->pIfIo, pImage->pszFilename);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * internal: return power of 2 or 0 if num error.
+ */
+static unsigned getPowerOfTwo(unsigned uNumber)
+{
+ if (uNumber == 0)
+ return 0;
+ unsigned uPower2 = 0;
+ while ((uNumber & 1) == 0)
+ {
+ uNumber >>= 1;
+ uPower2++;
+ }
+ return uNumber == 1 ? uPower2 : 0;
+}
+
+/**
+ * Internal: Init VDI preheader.
+ */
+static void vdiInitPreHeader(PVDIPREHEADER pPreHdr)
+{
+ pPreHdr->u32Signature = VDI_IMAGE_SIGNATURE;
+ pPreHdr->u32Version = VDI_IMAGE_VERSION;
+ memset(pPreHdr->szFileInfo, 0, sizeof(pPreHdr->szFileInfo));
+ strncat(pPreHdr->szFileInfo, VDI_IMAGE_FILE_INFO, sizeof(pPreHdr->szFileInfo)-1);
+}
+
+/**
+ * Internal: check VDI preheader.
+ */
+static int vdiValidatePreHeader(PVDIPREHEADER pPreHdr)
+{
+ if (pPreHdr->u32Signature != VDI_IMAGE_SIGNATURE)
+ return VERR_VD_VDI_INVALID_HEADER;
+
+ if ( VDI_GET_VERSION_MAJOR(pPreHdr->u32Version) != VDI_IMAGE_VERSION_MAJOR
+ && pPreHdr->u32Version != 0x00000002) /* old version. */
+ return VERR_VD_VDI_UNSUPPORTED_VERSION;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal: translate VD image flags to VDI image type enum.
+ */
+static VDIIMAGETYPE vdiTranslateImageFlags2VDI(unsigned uImageFlags)
+{
+ if (uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ return VDI_IMAGE_TYPE_FIXED;
+ else if (uImageFlags & VD_IMAGE_FLAGS_DIFF)
+ return VDI_IMAGE_TYPE_DIFF;
+ else
+ return VDI_IMAGE_TYPE_NORMAL;
+}
+
+/**
+ * Internal: translate VDI image type enum to VD image type enum.
+ */
+static unsigned vdiTranslateVDI2ImageFlags(VDIIMAGETYPE enmType)
+{
+ switch (enmType)
+ {
+ case VDI_IMAGE_TYPE_NORMAL:
+ return VD_IMAGE_FLAGS_NONE;
+ case VDI_IMAGE_TYPE_FIXED:
+ return VD_IMAGE_FLAGS_FIXED;
+ case VDI_IMAGE_TYPE_DIFF:
+ return VD_IMAGE_FLAGS_DIFF;
+ default:
+ AssertMsgFailed(("invalid VDIIMAGETYPE enmType=%d\n", (int)enmType));
+ return VD_IMAGE_FLAGS_NONE;
+ }
+}
+
+/**
+ * Internal: Init VDI header. Always use latest header version.
+ *
+ * @param pHeader Assumes it was initially initialized to all zeros.
+ * @param uImageFlags Flags for this image.
+ * @param pszComment Optional comment to set for the image.
+ * @param cbDisk Size of the disk in bytes.
+ * @param cbBlock Size of one block in the image.
+ * @param cbBlockExtra Extra data for one block private to the image.
+ * @param cbDataAlign The alignment for all data structures.
+ */
+static void vdiInitHeader(PVDIHEADER pHeader, uint32_t uImageFlags,
+ const char *pszComment, uint64_t cbDisk,
+ uint32_t cbBlock, uint32_t cbBlockExtra,
+ uint32_t cbDataAlign)
+{
+ pHeader->uVersion = VDI_IMAGE_VERSION;
+ pHeader->u.v1plus.cbHeader = sizeof(VDIHEADER1PLUS);
+ pHeader->u.v1plus.u32Type = (uint32_t)vdiTranslateImageFlags2VDI(uImageFlags);
+ pHeader->u.v1plus.fFlags = (uImageFlags & VD_VDI_IMAGE_FLAGS_ZERO_EXPAND) ? 1 : 0;
+#ifdef VBOX_STRICT
+ char achZero[VDI_IMAGE_COMMENT_SIZE] = {0};
+ Assert(!memcmp(pHeader->u.v1plus.szComment, achZero, VDI_IMAGE_COMMENT_SIZE));
+#endif
+ pHeader->u.v1plus.szComment[0] = '\0';
+ if (pszComment)
+ {
+ AssertMsg(strlen(pszComment) < sizeof(pHeader->u.v1plus.szComment),
+ ("HDD Comment is too long, cb=%d\n", strlen(pszComment)));
+ strncat(pHeader->u.v1plus.szComment, pszComment, sizeof(pHeader->u.v1plus.szComment)-1);
+ }
+
+ /* Mark the legacy geometry not-calculated. */
+ pHeader->u.v1plus.LegacyGeometry.cCylinders = 0;
+ pHeader->u.v1plus.LegacyGeometry.cHeads = 0;
+ pHeader->u.v1plus.LegacyGeometry.cSectors = 0;
+ pHeader->u.v1plus.LegacyGeometry.cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+ pHeader->u.v1plus.u32Dummy = 0; /* used to be the translation value */
+
+ pHeader->u.v1plus.cbDisk = cbDisk;
+ pHeader->u.v1plus.cbBlock = cbBlock;
+ pHeader->u.v1plus.cBlocks = (uint32_t)(cbDisk / cbBlock);
+ if (cbDisk % cbBlock)
+ pHeader->u.v1plus.cBlocks++;
+ pHeader->u.v1plus.cbBlockExtra = cbBlockExtra;
+ pHeader->u.v1plus.cBlocksAllocated = 0;
+
+ /* Init offsets. */
+ pHeader->u.v1plus.offBlocks = RT_ALIGN_32(sizeof(VDIPREHEADER) + sizeof(VDIHEADER1PLUS), cbDataAlign);
+ pHeader->u.v1plus.offData = RT_ALIGN_32(pHeader->u.v1plus.offBlocks + (pHeader->u.v1plus.cBlocks * sizeof(VDIIMAGEBLOCKPOINTER)), cbDataAlign);
+
+ /* Init uuids. */
+#ifdef _MSC_VER
+# pragma warning(disable:4366) /* (harmless "misalignment") */
+#endif
+ RTUuidCreate(&pHeader->u.v1plus.uuidCreate);
+ RTUuidClear(&pHeader->u.v1plus.uuidModify);
+ RTUuidClear(&pHeader->u.v1plus.uuidLinkage);
+ RTUuidClear(&pHeader->u.v1plus.uuidParentModify);
+#ifdef _MSC_VER
+# pragma warning(default:4366)
+#endif
+
+ /* Mark LCHS geometry not-calculated. */
+ pHeader->u.v1plus.LCHSGeometry.cCylinders = 0;
+ pHeader->u.v1plus.LCHSGeometry.cHeads = 0;
+ pHeader->u.v1plus.LCHSGeometry.cSectors = 0;
+ pHeader->u.v1plus.LCHSGeometry.cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+}
+
+/**
+ * Internal: Check VDI header.
+ */
+static int vdiValidateHeader(PVDIHEADER pHeader)
+{
+ /* Check version-dependent header parameters. */
+ switch (GET_MAJOR_HEADER_VERSION(pHeader))
+ {
+ case 0:
+ {
+ /* Old header version. */
+ break;
+ }
+ case 1:
+ {
+ /* Current header version. */
+
+ if (pHeader->u.v1.cbHeader < sizeof(VDIHEADER1))
+ {
+ LogRel(("VDI: v1 header size wrong (%d < %d)\n",
+ pHeader->u.v1.cbHeader, sizeof(VDIHEADER1)));
+ return VERR_VD_VDI_INVALID_HEADER;
+ }
+
+ if (getImageBlocksOffset(pHeader) < (sizeof(VDIPREHEADER) + sizeof(VDIHEADER1)))
+ {
+ LogRel(("VDI: v1 blocks offset wrong (%d < %d)\n",
+ getImageBlocksOffset(pHeader), sizeof(VDIPREHEADER) + sizeof(VDIHEADER1)));
+ return VERR_VD_VDI_INVALID_HEADER;
+ }
+
+ if (getImageDataOffset(pHeader) < (getImageBlocksOffset(pHeader) + getImageBlocks(pHeader) * sizeof(VDIIMAGEBLOCKPOINTER)))
+ {
+ LogRel(("VDI: v1 image data offset wrong (%d < %d)\n",
+ getImageDataOffset(pHeader), getImageBlocksOffset(pHeader) + getImageBlocks(pHeader) * sizeof(VDIIMAGEBLOCKPOINTER)));
+ return VERR_VD_VDI_INVALID_HEADER;
+ }
+
+ break;
+ }
+ default:
+ /* Unsupported. */
+ return VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+
+ /* Check common header parameters. */
+
+ bool fFailed = false;
+
+ if ( getImageType(pHeader) < VDI_IMAGE_TYPE_FIRST
+ || getImageType(pHeader) > VDI_IMAGE_TYPE_LAST)
+ {
+ LogRel(("VDI: bad image type %d\n", getImageType(pHeader)));
+ fFailed = true;
+ }
+
+ if (getImageFlags(pHeader) & ~VD_VDI_IMAGE_FLAGS_MASK)
+ {
+ LogRel(("VDI: bad image flags %08x\n", getImageFlags(pHeader)));
+ fFailed = true;
+ }
+
+ if ( getImageLCHSGeometry(pHeader)
+ && (getImageLCHSGeometry(pHeader))->cbSector != VDI_GEOMETRY_SECTOR_SIZE)
+ {
+ LogRel(("VDI: wrong sector size (%d != %d)\n",
+ (getImageLCHSGeometry(pHeader))->cbSector, VDI_GEOMETRY_SECTOR_SIZE));
+ fFailed = true;
+ }
+
+ if ( getImageDiskSize(pHeader) == 0
+ || getImageBlockSize(pHeader) == 0
+ || getImageBlocks(pHeader) == 0
+ || getPowerOfTwo(getImageBlockSize(pHeader)) == 0)
+ {
+ LogRel(("VDI: wrong size (%lld, %d, %d, %d)\n",
+ getImageDiskSize(pHeader), getImageBlockSize(pHeader),
+ getImageBlocks(pHeader), getPowerOfTwo(getImageBlockSize(pHeader))));
+ fFailed = true;
+ }
+
+ if (getImageBlocksAllocated(pHeader) > getImageBlocks(pHeader))
+ {
+ LogRel(("VDI: too many blocks allocated (%d > %d)\n"
+ " blocksize=%d disksize=%lld\n",
+ getImageBlocksAllocated(pHeader), getImageBlocks(pHeader),
+ getImageBlockSize(pHeader), getImageDiskSize(pHeader)));
+ fFailed = true;
+ }
+
+ if ( getImageExtraBlockSize(pHeader) != 0
+ && getPowerOfTwo(getImageExtraBlockSize(pHeader)) == 0)
+ {
+ LogRel(("VDI: wrong extra size (%d, %d)\n",
+ getImageExtraBlockSize(pHeader), getPowerOfTwo(getImageExtraBlockSize(pHeader))));
+ fFailed = true;
+ }
+
+ if ((uint64_t)getImageBlockSize(pHeader) * getImageBlocks(pHeader) < getImageDiskSize(pHeader))
+ {
+ LogRel(("VDI: wrong disk size (%d, %d, %lld)\n",
+ getImageBlockSize(pHeader), getImageBlocks(pHeader), getImageDiskSize(pHeader)));
+ fFailed = true;
+ }
+
+ if (RTUuidIsNull(getImageCreationUUID(pHeader)))
+ {
+ LogRel(("VDI: uuid of creator is 0\n"));
+ fFailed = true;
+ }
+
+ if (RTUuidIsNull(getImageModificationUUID(pHeader)))
+ {
+ LogRel(("VDI: uuid of modifier is 0\n"));
+ fFailed = true;
+ }
+
+ return fFailed ? VERR_VD_VDI_INVALID_HEADER : VINF_SUCCESS;
+}
+
+/**
+ * Internal: Set up VDIIMAGEDESC structure by image header.
+ */
+static void vdiSetupImageDesc(PVDIIMAGEDESC pImage)
+{
+ pImage->uImageFlags = getImageFlags(&pImage->Header);
+ pImage->uImageFlags |= vdiTranslateVDI2ImageFlags(getImageType(&pImage->Header));
+ pImage->offStartBlocks = getImageBlocksOffset(&pImage->Header);
+ pImage->offStartData = getImageDataOffset(&pImage->Header);
+ pImage->uBlockMask = getImageBlockSize(&pImage->Header) - 1;
+ pImage->uShiftOffset2Index = getPowerOfTwo(getImageBlockSize(&pImage->Header));
+ pImage->offStartBlockData = getImageExtraBlockSize(&pImage->Header);
+ pImage->cbAllocationBlock = getImageBlockSize(&pImage->Header);
+ pImage->cbTotalBlockData = pImage->offStartBlockData
+ + getImageBlockSize(&pImage->Header);
+}
+
+/**
+ * Sets up the complete image state from the given parameters.
+ *
+ * @returns VBox status code.
+ * @param pImage The VDI image descriptor.
+ * @param uImageFlags Image flags.
+ * @param pszComment The comment for the image (optional).
+ * @param cbSize Size of the resulting image in bytes.
+ * @param cbAllocationBlock Size of blocks allocated
+ * @param cbDataAlign Data alignment in bytes.
+ * @param pPCHSGeometry Physical CHS geometry for the image.
+ * @param pLCHSGeometry Logical CHS geometry for the image.
+ */
+static int vdiSetupImageState(PVDIIMAGEDESC pImage, unsigned uImageFlags, const char *pszComment,
+ uint64_t cbSize, uint32_t cbAllocationBlock, uint32_t cbDataAlign, PCVDGEOMETRY pPCHSGeometry,
+ PCVDGEOMETRY pLCHSGeometry)
+{
+ int rc = VINF_SUCCESS;
+
+ vdiInitPreHeader(&pImage->PreHeader);
+ vdiInitHeader(&pImage->Header, uImageFlags, pszComment, cbSize, cbAllocationBlock, 0,
+ cbDataAlign);
+ /* Save PCHS geometry. Not much work, and makes the flow of information
+ * quite a bit clearer - relying on the higher level isn't obvious. */
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ /* Set LCHS geometry (legacy geometry is ignored for the current 1.1+). */
+ pImage->Header.u.v1plus.LCHSGeometry.cCylinders = pLCHSGeometry->cCylinders;
+ pImage->Header.u.v1plus.LCHSGeometry.cHeads = pLCHSGeometry->cHeads;
+ pImage->Header.u.v1plus.LCHSGeometry.cSectors = pLCHSGeometry->cSectors;
+ pImage->Header.u.v1plus.LCHSGeometry.cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+
+ pImage->paBlocks = (PVDIIMAGEBLOCKPOINTER)RTMemAlloc(sizeof(VDIIMAGEBLOCKPOINTER) * getImageBlocks(&pImage->Header));
+ if (RT_LIKELY(pImage->paBlocks))
+ {
+ if (!(uImageFlags & VD_IMAGE_FLAGS_FIXED))
+ {
+ /* for growing images mark all blocks in paBlocks as free. */
+ for (unsigned i = 0; i < pImage->Header.u.v1.cBlocks; i++)
+ pImage->paBlocks[i] = VDI_IMAGE_BLOCK_FREE;
+ }
+ else
+ {
+ /* for fixed images mark all blocks in paBlocks as allocated */
+ for (unsigned i = 0; i < pImage->Header.u.v1.cBlocks; i++)
+ pImage->paBlocks[i] = i;
+ pImage->Header.u.v1.cBlocksAllocated = pImage->Header.u.v1.cBlocks;
+ }
+
+ /* Setup image parameters. */
+ vdiSetupImageDesc(pImage);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/**
+ * Creates the image file from the given descriptor.
+ *
+ * @returns VBox status code.
+ * @param pImage The VDI image descriptor.
+ * @param uOpenFlags Open flags.
+ * @param pIfProgress The progress interface.
+ * @param uPercentStart Progress starting point.
+ * @param uPercentSpan How many percent for this part of the operation is used.
+ */
+static int vdiImageCreateFile(PVDIIMAGEDESC pImage, unsigned uOpenFlags,
+ PVDINTERFACEPROGRESS pIfProgress, unsigned uPercentStart,
+ unsigned uPercentSpan)
+{
+ int rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags & ~VD_OPEN_FLAGS_READONLY,
+ true /* fCreate */),
+ &pImage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ if (pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ {
+ uint64_t cbTotal = pImage->offStartData
+ + (uint64_t)getImageBlocks(&pImage->Header) * pImage->cbTotalBlockData;
+
+ /* Check the free space on the disk and leave early if there is not
+ * sufficient space available. */
+ int64_t cbFree = 0;
+ rc = vdIfIoIntFileGetFreeSpace(pImage->pIfIo, pImage->pszFilename, &cbFree);
+ if (RT_SUCCESS(rc) /* ignore errors */ && ((uint64_t)cbFree < cbTotal))
+ rc = vdIfError(pImage->pIfError, VERR_DISK_FULL, RT_SRC_POS,
+ N_("VDI: disk would overflow creating image '%s'"), pImage->pszFilename);
+ else
+ {
+ /*
+ * Allocate & commit whole file if fixed image, it must be more
+ * effective than expanding file by write operations.
+ */
+ rc = vdIfIoIntFileSetAllocationSize(pImage->pIfIo, pImage->pStorage, cbTotal, 0 /* fFlags */,
+ pIfProgress, uPercentStart, uPercentSpan);
+ pImage->cbImage = cbTotal;
+ }
+ }
+ else
+ {
+ /* Set file size to hold header and blocks array. */
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage, pImage->offStartData);
+ pImage->cbImage = pImage->offStartData;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /* Write pre-header. */
+ VDIPREHEADER PreHeader;
+ vdiConvPreHeaderEndianess(VDIECONV_H2F, &PreHeader, &pImage->PreHeader);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, 0,
+ &PreHeader, sizeof(PreHeader));
+ if (RT_SUCCESS(rc))
+ {
+ /* Write header. */
+ VDIHEADER1PLUS Hdr;
+ vdiConvHeaderEndianessV1p(VDIECONV_H2F, &Hdr, &pImage->Header.u.v1plus);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, sizeof(pImage->PreHeader),
+ &Hdr, sizeof(Hdr));
+ if (RT_SUCCESS(rc))
+ {
+ vdiConvBlocksEndianess(VDIECONV_H2F, pImage->paBlocks, getImageBlocks(&pImage->Header));
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, pImage->offStartBlocks, pImage->paBlocks,
+ getImageBlocks(&pImage->Header) * sizeof(VDIIMAGEBLOCKPOINTER));
+ vdiConvBlocksEndianess(VDIECONV_F2H, pImage->paBlocks, getImageBlocks(&pImage->Header));
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: writing block pointers failed for '%s'"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: writing header failed for '%s'"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: writing pre-header failed for '%s'"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: setting image size failed for '%s'"),
+ pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: cannot create image '%s'"),
+ pImage->pszFilename);
+
+ return rc;
+}
+
+/**
+ * Internal: Create VDI image file.
+ */
+static int vdiCreateImage(PVDIIMAGEDESC pImage, uint64_t cbSize,
+ unsigned uImageFlags, const char *pszComment,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ PCRTUUID pUuid, unsigned uOpenFlags,
+ PVDINTERFACEPROGRESS pIfProgress, unsigned uPercentStart,
+ unsigned uPercentSpan, PVDINTERFACECONFIG pIfCfg)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t cbDataAlign = VDI_DATA_ALIGN;
+ AssertPtr(pPCHSGeometry);
+ AssertPtr(pLCHSGeometry);
+
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ /* Special check for comment length. */
+ if ( RT_VALID_PTR(pszComment)
+ && strlen(pszComment) >= VDI_IMAGE_COMMENT_SIZE)
+ rc = vdIfError(pImage->pIfError, VERR_VD_VDI_COMMENT_TOO_LONG, RT_SRC_POS,
+ N_("VDI: comment is too long for '%s'"), pImage->pszFilename);
+
+ PVDINTERFACECONFIG pImgCfg = VDIfConfigGet(pImage->pVDIfsImage);
+ if (pImgCfg)
+ {
+ rc = VDCFGQueryU32Def(pImgCfg, "AllocationBlockSize",
+ &pImage->cbAllocationBlock, VDI_IMAGE_DEFAULT_BLOCK_SIZE);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VDI: Getting AllocationBlockSize for '%s' failed (%Rrc)"), pImage->pszFilename, rc);
+ } else
+ pImage->cbAllocationBlock = VDI_IMAGE_DEFAULT_BLOCK_SIZE;
+
+ if (pIfCfg)
+ {
+ rc = VDCFGQueryU32Def(pIfCfg, "DataAlignment", &cbDataAlign, VDI_DATA_ALIGN);
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS,
+ N_("VDI: Getting data alignment for '%s' failed (%Rrc)"), pImage->pszFilename, rc);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+
+ rc = vdiSetupImageState(pImage, uImageFlags, pszComment, cbSize,
+ pImage->cbAllocationBlock, cbDataAlign, pPCHSGeometry, pLCHSGeometry);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Use specified image uuid */
+ *getImageCreationUUID(&pImage->Header) = *pUuid;
+ /* Generate image last-modify uuid */
+ RTUuidCreate(getImageModificationUUID(&pImage->Header));
+
+ rc = vdiImageCreateFile(pImage, uOpenFlags, 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 = getImageDiskSize(&pImage->Header);
+
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan);
+ }
+
+ if (RT_FAILURE(rc))
+ vdiFreeImage(pImage, rc != VERR_ALREADY_EXISTS);
+ return rc;
+}
+
+/**
+ * Reads and validates the header for the given image descriptor.
+ *
+ * @returns VBox status code.
+ * @param pImage The VDI image descriptor.
+ */
+static int vdiImageReadHeader(PVDIIMAGEDESC pImage)
+{
+ /* Get file size. */
+ int rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage,
+ &pImage->cbImage);
+ if (RT_SUCCESS(rc))
+ {
+ /* Read pre-header. */
+ VDIPREHEADER PreHeader;
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, 0,
+ &PreHeader, sizeof(PreHeader));
+ if (RT_SUCCESS(rc))
+ {
+ vdiConvPreHeaderEndianess(VDIECONV_F2H, &pImage->PreHeader, &PreHeader);
+ rc = vdiValidatePreHeader(&pImage->PreHeader);
+ if (RT_SUCCESS(rc))
+ {
+ /* Read header. */
+ pImage->Header.uVersion = pImage->PreHeader.u32Version;
+ switch (GET_MAJOR_HEADER_VERSION(&pImage->Header))
+ {
+ case 0:
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, sizeof(pImage->PreHeader),
+ &pImage->Header.u.v0, sizeof(pImage->Header.u.v0));
+ if (RT_SUCCESS(rc))
+ vdiConvHeaderEndianessV0(VDIECONV_F2H, &pImage->Header.u.v0, &pImage->Header.u.v0);
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: error reading v0 header in '%s'"), pImage->pszFilename);
+ break;
+ case 1:
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, sizeof(pImage->PreHeader),
+ &pImage->Header.u.v1, sizeof(pImage->Header.u.v1));
+ if (RT_SUCCESS(rc))
+ {
+ vdiConvHeaderEndianessV1(VDIECONV_F2H, &pImage->Header.u.v1, &pImage->Header.u.v1);
+ /* Convert VDI 1.1 images to VDI 1.1+ on open in read/write mode.
+ * Conversion is harmless, as any VirtualBox version supporting VDI
+ * 1.1 doesn't touch fields it doesn't know about. */
+ if ( !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && GET_MINOR_HEADER_VERSION(&pImage->Header) == 1
+ && pImage->Header.u.v1.cbHeader < sizeof(pImage->Header.u.v1plus))
+ {
+ pImage->Header.u.v1plus.cbHeader = sizeof(pImage->Header.u.v1plus);
+ /* Mark LCHS geometry not-calculated. */
+ pImage->Header.u.v1plus.LCHSGeometry.cCylinders = 0;
+ pImage->Header.u.v1plus.LCHSGeometry.cHeads = 0;
+ pImage->Header.u.v1plus.LCHSGeometry.cSectors = 0;
+ pImage->Header.u.v1plus.LCHSGeometry.cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+ }
+ else if (pImage->Header.u.v1.cbHeader >= sizeof(pImage->Header.u.v1plus))
+ {
+ /* Read the actual VDI 1.1+ header completely. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, sizeof(pImage->PreHeader),
+ &pImage->Header.u.v1plus,
+ sizeof(pImage->Header.u.v1plus));
+ if (RT_SUCCESS(rc))
+ vdiConvHeaderEndianessV1p(VDIECONV_F2H, &pImage->Header.u.v1plus, &pImage->Header.u.v1plus);
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: error reading v1.1+ header in '%s'"), pImage->pszFilename);
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: error reading v1 header in '%s'"), pImage->pszFilename);
+ break;
+ default:
+ rc = vdIfError(pImage->pIfError, VERR_VD_VDI_UNSUPPORTED_VERSION, RT_SRC_POS,
+ N_("VDI: unsupported major version %u in '%s'"), GET_MAJOR_HEADER_VERSION(&pImage->Header), pImage->pszFilename);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdiValidateHeader(&pImage->Header);
+ if (RT_SUCCESS(rc))
+ {
+ /* Setup image parameters by header. */
+ vdiSetupImageDesc(pImage);
+
+ /*
+ * Until revision r111992 there was no check that the size was sector aligned
+ * when creating a new image and a bug in the VirtualBox GUI on OS X resulted
+ * in such images being created which caused issues when writing to the
+ * end of the image.
+ *
+ * Detect such images and repair the small damage by rounding down to the next
+ * aligned size. This is no problem as the guest would see a sector count
+ * only anyway from the device emulations so it already sees only the smaller
+ * size as result of the integer division of the size and sector size.
+ *
+ * This might not be written to the image if it is opened readonly
+ * which is not much of a problem because only writing to the last block
+ * causes trouble.
+ */
+ uint64_t cbDisk = getImageDiskSize(&pImage->Header);
+ if (cbDisk & 0x1ff)
+ setImageDiskSize(&pImage->Header, cbDisk & ~UINT64_C(0x1ff));
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_VD_VDI_INVALID_HEADER, RT_SRC_POS,
+ N_("VDI: invalid header in '%s'"), pImage->pszFilename);
+ }
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: invalid pre-header in '%s'"), pImage->pszFilename);
+ }
+ else
+ {
+ vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: error reading pre-header in '%s'"), pImage->pszFilename);
+ rc = VERR_VD_VDI_INVALID_HEADER;
+ }
+ }
+ else
+ {
+ vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: error getting the image size in '%s'"), pImage->pszFilename);
+ rc = VERR_VD_VDI_INVALID_HEADER;
+ }
+
+ return rc;
+}
+
+/**
+ * Creates the back resolving table for the image for the discard operation.
+ *
+ * @returns VBox status code.
+ * @param pImage The VDI image descriptor.
+ */
+static int vdiImageBackResolvTblCreate(PVDIIMAGEDESC pImage)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Any error or inconsistency results in a fail because this might
+ * get us into trouble later on.
+ */
+ pImage->paBlocksRev = (unsigned *)RTMemAllocZ(sizeof(unsigned) * getImageBlocks(&pImage->Header));
+ if (pImage->paBlocksRev)
+ {
+ unsigned cBlocksAllocated = getImageBlocksAllocated(&pImage->Header);
+ unsigned cBlocks = getImageBlocks(&pImage->Header);
+
+ for (unsigned i = 0; i < cBlocks; i++)
+ pImage->paBlocksRev[i] = VDI_IMAGE_BLOCK_FREE;
+
+ for (unsigned i = 0; i < cBlocks; i++)
+ {
+ VDIIMAGEBLOCKPOINTER ptrBlock = pImage->paBlocks[i];
+ if (IS_VDI_IMAGE_BLOCK_ALLOCATED(ptrBlock))
+ {
+ if (ptrBlock < cBlocksAllocated)
+ {
+ if (pImage->paBlocksRev[ptrBlock] == VDI_IMAGE_BLOCK_FREE)
+ pImage->paBlocksRev[ptrBlock] = i;
+ else
+ {
+ rc = VERR_VD_VDI_INVALID_HEADER;
+ break;
+ }
+ }
+ else
+ {
+ rc = VERR_VD_VDI_INVALID_HEADER;
+ break;
+ }
+ }
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/**
+ * Internal: Open a VDI image.
+ */
+static int vdiOpenImage(PVDIIMAGEDESC pImage, unsigned uOpenFlags)
+{
+ pImage->uOpenFlags = uOpenFlags;
+
+ pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk);
+ pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage);
+ AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER);
+
+ /*
+ * Open the image.
+ */
+ int rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags, false /* fCreate */),
+ &pImage->pStorage);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdiImageReadHeader(pImage);
+ if (RT_SUCCESS(rc))
+ {
+ /* Allocate memory for blocks array. */
+ pImage->paBlocks = (PVDIIMAGEBLOCKPOINTER)RTMemAlloc(sizeof(VDIIMAGEBLOCKPOINTER) * getImageBlocks(&pImage->Header));
+ if (RT_LIKELY(pImage->paBlocks))
+ {
+ /* Read blocks array. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, pImage->offStartBlocks, pImage->paBlocks,
+ getImageBlocks(&pImage->Header) * sizeof(VDIIMAGEBLOCKPOINTER));
+ if (RT_SUCCESS(rc))
+ {
+ vdiConvBlocksEndianess(VDIECONV_F2H, pImage->paBlocks, getImageBlocks(&pImage->Header));
+
+ if (uOpenFlags & VD_OPEN_FLAGS_DISCARD)
+ rc = vdiImageBackResolvTblCreate(pImage);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("VDI: Error reading the block table in '%s'"), pImage->pszFilename);
+ }
+ else
+ rc = vdIfError(pImage->pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ N_("VDI: Error allocating memory for the block table in '%s'"), pImage->pszFilename);;
+ }
+ }
+ /* else: Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+
+ 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 = getImageDiskSize(&pImage->Header);
+ if (uOpenFlags & VD_OPEN_FLAGS_INFO)
+ {
+ PVDINTERFACECONFIG pImgCfg = VDIfConfigGet(pImage->pVDIfsImage);
+ if (pImgCfg)
+ {
+ rc = VDCFGUpdateU64(pImgCfg, true, "AllocationBlockSize", pImage->cbAllocationBlock);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+ }
+ else
+ vdiFreeImage(pImage, false);
+ return rc;
+}
+
+/**
+ * Internal: Save header to file.
+ */
+static int vdiUpdateHeader(PVDIIMAGEDESC pImage)
+{
+ int rc;
+ switch (GET_MAJOR_HEADER_VERSION(&pImage->Header))
+ {
+ case 0:
+ {
+ VDIHEADER0 Hdr;
+ vdiConvHeaderEndianessV0(VDIECONV_H2F, &Hdr, &pImage->Header.u.v0);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, sizeof(VDIPREHEADER),
+ &Hdr, sizeof(Hdr));
+ break;
+ }
+ case 1:
+ if (pImage->Header.u.v1plus.cbHeader < sizeof(pImage->Header.u.v1plus))
+ {
+ VDIHEADER1 Hdr;
+ vdiConvHeaderEndianessV1(VDIECONV_H2F, &Hdr, &pImage->Header.u.v1);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, sizeof(VDIPREHEADER),
+ &Hdr, sizeof(Hdr));
+ }
+ else
+ {
+ VDIHEADER1PLUS Hdr;
+ vdiConvHeaderEndianessV1p(VDIECONV_H2F, &Hdr, &pImage->Header.u.v1plus);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, sizeof(VDIPREHEADER),
+ &Hdr, sizeof(Hdr));
+ }
+ break;
+ default:
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ break;
+ }
+ AssertMsgRC(rc, ("vdiUpdateHeader failed, filename=\"%s\" rc=%Rrc\n", pImage->pszFilename, rc));
+ return rc;
+}
+
+/**
+ * Internal: Save header to file - async version.
+ */
+static int vdiUpdateHeaderAsync(PVDIIMAGEDESC pImage, PVDIOCTX pIoCtx)
+{
+ int rc;
+ switch (GET_MAJOR_HEADER_VERSION(&pImage->Header))
+ {
+ case 0:
+ {
+ VDIHEADER0 Hdr;
+ vdiConvHeaderEndianessV0(VDIECONV_H2F, &Hdr, &pImage->Header.u.v0);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ sizeof(VDIPREHEADER), &Hdr, sizeof(Hdr),
+ pIoCtx, NULL, NULL);
+ break;
+ }
+ case 1:
+ if (pImage->Header.u.v1plus.cbHeader < sizeof(pImage->Header.u.v1plus))
+ {
+ VDIHEADER1 Hdr;
+ vdiConvHeaderEndianessV1(VDIECONV_H2F, &Hdr, &pImage->Header.u.v1);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ sizeof(VDIPREHEADER), &Hdr, sizeof(Hdr),
+ pIoCtx, NULL, NULL);
+ }
+ else
+ {
+ VDIHEADER1PLUS Hdr;
+ vdiConvHeaderEndianessV1p(VDIECONV_H2F, &Hdr, &pImage->Header.u.v1plus);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ sizeof(VDIPREHEADER), &Hdr, sizeof(Hdr),
+ pIoCtx, NULL, NULL);
+ }
+ break;
+ default:
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ break;
+ }
+ AssertMsg(RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS,
+ ("vdiUpdateHeader failed, filename=\"%s\" rc=%Rrc\n", pImage->pszFilename, rc));
+ return rc;
+}
+
+/**
+ * Internal: Save block pointer to file, save header to file.
+ */
+static int vdiUpdateBlockInfo(PVDIIMAGEDESC pImage, unsigned uBlock)
+{
+ /* Update image header. */
+ int rc = vdiUpdateHeader(pImage);
+ if (RT_SUCCESS(rc))
+ {
+ /* write only one block pointer. */
+ VDIIMAGEBLOCKPOINTER ptrBlock = RT_H2LE_U32(pImage->paBlocks[uBlock]);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ pImage->offStartBlocks + uBlock * sizeof(VDIIMAGEBLOCKPOINTER),
+ &ptrBlock, sizeof(VDIIMAGEBLOCKPOINTER));
+ AssertMsgRC(rc, ("vdiUpdateBlockInfo failed to update block=%u, filename=\"%s\", rc=%Rrc\n",
+ uBlock, pImage->pszFilename, rc));
+ }
+ return rc;
+}
+
+/**
+ * Internal: Save block pointer to file, save header to file - async version.
+ */
+static int vdiUpdateBlockInfoAsync(PVDIIMAGEDESC pImage, unsigned uBlock,
+ PVDIOCTX pIoCtx, bool fUpdateHdr)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Update image header. */
+ if (fUpdateHdr)
+ rc = vdiUpdateHeaderAsync(pImage, pIoCtx);
+
+ if (RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ {
+ /* write only one block pointer. */
+ VDIIMAGEBLOCKPOINTER ptrBlock = RT_H2LE_U32(pImage->paBlocks[uBlock]);
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ pImage->offStartBlocks + uBlock * sizeof(VDIIMAGEBLOCKPOINTER),
+ &ptrBlock, sizeof(VDIIMAGEBLOCKPOINTER),
+ pIoCtx, NULL, NULL);
+ AssertMsg(RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS,
+ ("vdiUpdateBlockInfo failed to update block=%u, filename=\"%s\", rc=%Rrc\n",
+ uBlock, pImage->pszFilename, rc));
+ }
+ return rc;
+}
+
+/**
+ * Internal: Flush the image file to disk - async version.
+ */
+static int vdiFlushImageIoCtx(PVDIIMAGEDESC pImage, PVDIOCTX pIoCtx)
+{
+ int rc = VINF_SUCCESS;
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ /* Save header. */
+ rc = vdiUpdateHeaderAsync(pImage, pIoCtx);
+ AssertMsg(RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS,
+ ("vdiUpdateHeaderAsync() failed, filename=\"%s\", rc=%Rrc\n",
+ pImage->pszFilename, rc));
+ rc = vdIfIoIntFileFlush(pImage->pIfIo, pImage->pStorage, pIoCtx, NULL, NULL);
+ AssertMsg(RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS,
+ ("Flushing data to disk failed rc=%Rrc\n", rc));
+ }
+
+ return rc;
+}
+
+/**
+ * Completion callback for meta/userdata reads or writes.
+ *
+ * @return VBox status code.
+ * VINF_SUCCESS if everything was successful and the transfer can continue.
+ * VERR_VD_ASYNC_IO_IN_PROGRESS if there is another data transfer pending.
+ * @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) vdiDiscardBlockAsyncUpdate(void *pBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+ RT_NOREF1(rcReq);
+ int rc = VINF_SUCCESS;
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ PVDIBLOCKDISCARDASYNC pDiscardAsync = (PVDIBLOCKDISCARDASYNC)pvUser;
+
+ switch (pDiscardAsync->enmState)
+ {
+ case VDIBLOCKDISCARDSTATE_READ_BLOCK:
+ {
+ PVDMETAXFER pMetaXfer;
+ uint64_t u64Offset = (uint64_t)pDiscardAsync->idxLastBlock * pImage->cbTotalBlockData + pImage->offStartData;
+ rc = vdIfIoIntFileReadMeta(pImage->pIfIo, pImage->pStorage, u64Offset,
+ pDiscardAsync->pvBlock, pImage->cbTotalBlockData, pIoCtx,
+ &pMetaXfer, vdiDiscardBlockAsyncUpdate, pDiscardAsync);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Release immediately and go to next step. */
+ vdIfIoIntMetaXferRelease(pImage->pIfIo, pMetaXfer);
+ pDiscardAsync->enmState = VDIBLOCKDISCARDSTATE_WRITE_BLOCK;
+ }
+ RT_FALL_THRU();
+ case VDIBLOCKDISCARDSTATE_WRITE_BLOCK:
+ {
+ /* Block read complete. Write to the new location (discarded block). */
+ uint64_t u64Offset = (uint64_t)pDiscardAsync->ptrBlockDiscard * pImage->cbTotalBlockData + pImage->offStartData;
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage, u64Offset,
+ pDiscardAsync->pvBlock, pImage->cbTotalBlockData, pIoCtx,
+ vdiDiscardBlockAsyncUpdate, pDiscardAsync);
+
+ pDiscardAsync->enmState = VDIBLOCKDISCARDSTATE_UPDATE_METADATA;
+ if (RT_FAILURE(rc))
+ break;
+ }
+ RT_FALL_THRU();
+ case VDIBLOCKDISCARDSTATE_UPDATE_METADATA:
+ {
+ int rc2;
+
+ /* Block write complete. Update metadata. */
+ pImage->paBlocksRev[pDiscardAsync->idxLastBlock] = VDI_IMAGE_BLOCK_FREE;
+ pImage->paBlocks[pDiscardAsync->uBlock] = VDI_IMAGE_BLOCK_ZERO;
+
+ if (pDiscardAsync->idxLastBlock != pDiscardAsync->ptrBlockDiscard)
+ {
+ pImage->paBlocks[pDiscardAsync->uBlockLast] = pDiscardAsync->ptrBlockDiscard;
+ pImage->paBlocksRev[pDiscardAsync->ptrBlockDiscard] = pDiscardAsync->uBlockLast;
+
+ rc = vdiUpdateBlockInfoAsync(pImage, pDiscardAsync->uBlockLast, pIoCtx, false /* fUpdateHdr */);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ }
+
+ setImageBlocksAllocated(&pImage->Header, pDiscardAsync->idxLastBlock);
+ rc = vdiUpdateBlockInfoAsync(pImage, pDiscardAsync->uBlock, pIoCtx, true /* fUpdateHdr */);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+
+ pImage->cbImage -= pImage->cbTotalBlockData;
+ LogFlowFunc(("Set new size %llu\n", pImage->cbImage));
+ rc2 = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage, pImage->cbImage);
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+
+ /* Free discard state. */
+ RTMemFree(pDiscardAsync->pvBlock);
+ RTMemFree(pDiscardAsync);
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid state %d\n", pDiscardAsync->enmState));
+ }
+
+ if (rc == VERR_VD_NOT_ENOUGH_METADATA)
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+
+ return rc;
+}
+
+/**
+ * Internal: Discard a whole block from the image filling the created hole with
+ * data from another block - async I/O version.
+ *
+ * @returns VBox status code.
+ * @param pImage VDI image instance data.
+ * @param pIoCtx I/O context associated with this request.
+ * @param uBlock The block to discard.
+ * @param pvBlock Memory to use for the I/O.
+ */
+static int vdiDiscardBlockAsync(PVDIIMAGEDESC pImage, PVDIOCTX pIoCtx,
+ unsigned uBlock, void *pvBlock)
+{
+ int rc = VINF_SUCCESS;
+ PVDIBLOCKDISCARDASYNC pDiscardAsync = NULL;
+
+ LogFlowFunc(("pImage=%#p uBlock=%u pvBlock=%#p\n",
+ pImage, uBlock, pvBlock));
+
+ pDiscardAsync = (PVDIBLOCKDISCARDASYNC)RTMemAllocZ(sizeof(VDIBLOCKDISCARDASYNC));
+ if (RT_UNLIKELY(!pDiscardAsync))
+ return VERR_NO_MEMORY;
+
+ /* Init block discard state. */
+ pDiscardAsync->uBlock = uBlock;
+ pDiscardAsync->pvBlock = pvBlock;
+ pDiscardAsync->ptrBlockDiscard = pImage->paBlocks[uBlock];
+ pDiscardAsync->idxLastBlock = getImageBlocksAllocated(&pImage->Header) - 1;
+ pDiscardAsync->uBlockLast = pImage->paBlocksRev[pDiscardAsync->idxLastBlock];
+
+ /*
+ * The block is empty, remove it.
+ * Read the last block of the image first.
+ */
+ if (pDiscardAsync->idxLastBlock != pDiscardAsync->ptrBlockDiscard)
+ {
+ LogFlowFunc(("Moving block [%u]=%u into [%u]=%u\n",
+ pDiscardAsync->uBlockLast, pDiscardAsync->idxLastBlock,
+ uBlock, pImage->paBlocks[uBlock]));
+ pDiscardAsync->enmState = VDIBLOCKDISCARDSTATE_READ_BLOCK;
+ }
+ else
+ {
+ pDiscardAsync->enmState = VDIBLOCKDISCARDSTATE_UPDATE_METADATA; /* Start immediately to shrink the image. */
+ LogFlowFunc(("Discard last block [%u]=%u\n", uBlock, pImage->paBlocks[uBlock]));
+ }
+
+ /* Call the update callback directly. */
+ rc = vdiDiscardBlockAsyncUpdate(pImage, pIoCtx, pDiscardAsync, VINF_SUCCESS);
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Internal: Creates a allocation bitmap from the given data.
+ * Sectors which contain only 0 are marked as unallocated and sectors with
+ * other data as allocated.
+ *
+ * @returns Pointer to the allocation bitmap or NULL on failure.
+ * @param pvData The data to create the allocation bitmap for.
+ * @param cbData Number of bytes in the buffer.
+ */
+static void *vdiAllocationBitmapCreate(void *pvData, size_t cbData)
+{
+ Assert(cbData <= UINT32_MAX / 8);
+ uint32_t cSectors = (uint32_t)(cbData / 512);
+ uint32_t uSectorCur = 0;
+ void *pbmAllocationBitmap = NULL;
+
+ Assert(!(cbData % 512));
+ Assert(!(cSectors % 8));
+
+ pbmAllocationBitmap = RTMemAllocZ(cSectors / 8);
+ if (!pbmAllocationBitmap)
+ return NULL;
+
+ while (uSectorCur < cSectors)
+ {
+ int idxSet = ASMBitFirstSet((uint8_t *)pvData + uSectorCur * 512, (uint32_t)cbData * 8);
+
+ if (idxSet != -1)
+ {
+ unsigned idxSectorAlloc = idxSet / 8 / 512;
+ ASMBitSet(pbmAllocationBitmap, uSectorCur + idxSectorAlloc);
+
+ uSectorCur += idxSectorAlloc + 1;
+ cbData -= (idxSectorAlloc + 1) * 512;
+ }
+ else
+ break;
+ }
+
+ return pbmAllocationBitmap;
+}
+
+
+/**
+ * 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) vdiBlockAllocUpdate(void *pBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+ int rc = VINF_SUCCESS;
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ PVDIASYNCBLOCKALLOC pBlockAlloc = (PVDIASYNCBLOCKALLOC)pvUser;
+
+ if (RT_SUCCESS(rcReq))
+ {
+ pImage->cbImage += pImage->cbTotalBlockData;
+ pImage->paBlocks[pBlockAlloc->uBlock] = pBlockAlloc->cBlocksAllocated;
+
+ if (pImage->paBlocksRev)
+ pImage->paBlocksRev[pBlockAlloc->cBlocksAllocated] = pBlockAlloc->uBlock;
+
+ setImageBlocksAllocated(&pImage->Header, pBlockAlloc->cBlocksAllocated + 1);
+ rc = vdiUpdateBlockInfoAsync(pImage, pBlockAlloc->uBlock, pIoCtx,
+ true /* fUpdateHdr */);
+ }
+ /* else: I/O error don't update the block table. */
+
+ RTMemFree(pBlockAlloc);
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnProbe */
+static DECLCALLBACK(int) vdiProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType)
+{
+ RT_NOREF(enmDesiredType);
+ LogFlowFunc(("pszFilename=\"%s\"\n", pszFilename));
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER);
+
+
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)RTMemAllocZ(RT_UOFFSETOF(VDIIMAGEDESC, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->paBlocks = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = vdiOpenImage(pImage, VD_OPEN_FLAGS_INFO | VD_OPEN_FLAGS_READONLY);
+ vdiFreeImage(pImage, false);
+ RTMemFree(pImage);
+
+ if (RT_SUCCESS(rc))
+ *penmType = VDTYPE_HDD;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnOpen */
+static DECLCALLBACK(int) vdiOpen(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);
+
+
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)RTMemAllocZ(RT_UOFFSETOF(VDIIMAGEDESC, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->paBlocks = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = vdiOpenImage(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) vdiCreate(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)
+{
+ 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 and image flags. */
+ if ( enmType != VDTYPE_HDD
+ || (uImageFlags & ~VD_VDI_IMAGE_FLAGS_MASK) != 0)
+ return VERR_VD_INVALID_TYPE;
+
+ /* Check size. Maximum 4PB-3M. No tricks with adjusting the 1M block size
+ * so far, which would extend the size. */
+ if ( !cbSize
+ || cbSize >= _1P * 4 - _1M * 3
+ || cbSize < VDI_IMAGE_DEFAULT_BLOCK_SIZE
+ || (cbSize % 512))
+ return VERR_VD_INVALID_SIZE;
+
+ /* 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);
+
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)RTMemAllocZ(RT_UOFFSETOF(VDIIMAGEDESC, RegionList.aRegions[1]));
+ if (RT_LIKELY(pImage))
+ {
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+ PVDINTERFACECONFIG pIfCfg = VDIfConfigGet(pVDIfsOperation);
+ pImage->pszFilename = pszFilename;
+ pImage->pStorage = NULL;
+ pImage->paBlocks = NULL;
+ pImage->pVDIfsDisk = pVDIfsDisk;
+ pImage->pVDIfsImage = pVDIfsImage;
+
+ rc = vdiCreateImage(pImage, cbSize, uImageFlags, pszComment,
+ pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags,
+ pIfProgress, uPercentStart, uPercentSpan, pIfCfg);
+ 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)
+ {
+ vdiFreeImage(pImage, false);
+ rc = vdiOpenImage(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) vdiRename(void *pBackendData, const char *pszFilename)
+{
+ LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename));
+ int rc = VINF_SUCCESS;
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ /* Check arguments. */
+ AssertReturn((pImage && pszFilename && *pszFilename), VERR_INVALID_PARAMETER);
+
+ /* Close the image. */
+ rc = vdiFreeImage(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 new image. */
+ rc = vdiOpenImage(pImage, pImage->uOpenFlags);
+ }
+ else
+ {
+ /* The move failed, try to reopen the original image. */
+ int rc2 = vdiOpenImage(pImage, pImage->uOpenFlags);
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnClose */
+static DECLCALLBACK(int) vdiClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ int rc = vdiFreeImage(pImage, fDelete);
+ RTMemFree(pImage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) vdiRead(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));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ unsigned uBlock;
+ unsigned offRead;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ Assert(!(uOffset % 512));
+ Assert(!(cbToRead % 512));
+ AssertPtrReturn(pIoCtx, VERR_INVALID_POINTER);
+ AssertReturn(cbToRead, VERR_INVALID_PARAMETER);
+ AssertReturn(uOffset + cbToRead <= getImageDiskSize(&pImage->Header), VERR_INVALID_PARAMETER);
+
+ /* Calculate starting block number and offset inside it. */
+ uBlock = (unsigned)(uOffset >> pImage->uShiftOffset2Index);
+ offRead = (unsigned)uOffset & pImage->uBlockMask;
+
+ /* Clip read range to at most the rest of the block. */
+ cbToRead = RT_MIN(cbToRead, getImageBlockSize(&pImage->Header) - offRead);
+ Assert(!(cbToRead % 512));
+
+ if (pImage->paBlocks[uBlock] == VDI_IMAGE_BLOCK_FREE)
+ rc = VERR_VD_BLOCK_FREE;
+ else if (pImage->paBlocks[uBlock] == VDI_IMAGE_BLOCK_ZERO)
+ {
+ size_t cbSet;
+
+ cbSet = vdIfIoIntIoCtxSet(pImage->pIfIo, pIoCtx, 0, cbToRead);
+ Assert(cbSet == cbToRead);
+ }
+ else
+ {
+ /* Block present in image file, read relevant data. */
+ uint64_t u64Offset = (uint64_t)pImage->paBlocks[uBlock] * pImage->cbTotalBlockData
+ + (pImage->offStartData + pImage->offStartBlockData + offRead);
+
+ if (u64Offset + cbToRead <= pImage->cbImage)
+ rc = vdIfIoIntFileReadUser(pImage->pIfIo, pImage->pStorage, u64Offset,
+ pIoCtx, cbToRead);
+ else
+ {
+ LogRel(("VDI: Out of range access (%llu) in image %s, image size %llu\n",
+ u64Offset, pImage->pszFilename, pImage->cbImage));
+ vdIfIoIntIoCtxSet(pImage->pIfIo, pIoCtx, 0, cbToRead);
+ rc = VERR_VD_READ_OUT_OF_RANGE;
+ }
+ }
+
+ if (pcbActuallyRead)
+ *pcbActuallyRead = cbToRead;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) vdiWrite(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));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ unsigned uBlock;
+ unsigned offWrite;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pImage);
+ Assert(!(uOffset % 512));
+ Assert(!(cbToWrite % 512));
+ AssertPtrReturn(pIoCtx, VERR_INVALID_POINTER);
+ AssertReturn(cbToWrite, VERR_INVALID_PARAMETER);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ /* No size check here, will do that later. For dynamic images which are
+ * not multiples of the block size in length, this would prevent writing to
+ * the last block. */
+
+ /* Calculate starting block number and offset inside it. */
+ uBlock = (unsigned)(uOffset >> pImage->uShiftOffset2Index);
+ offWrite = (unsigned)uOffset & pImage->uBlockMask;
+
+ /* Clip write range to at most the rest of the block. */
+ cbToWrite = RT_MIN(cbToWrite, getImageBlockSize(&pImage->Header) - offWrite);
+ Assert(!(cbToWrite % 512));
+
+ do
+ {
+ if (!IS_VDI_IMAGE_BLOCK_ALLOCATED(pImage->paBlocks[uBlock]))
+ {
+ /* Block is either free or zero. */
+ if ( !(pImage->uOpenFlags & VD_OPEN_FLAGS_HONOR_ZEROES)
+ && ( pImage->paBlocks[uBlock] == VDI_IMAGE_BLOCK_ZERO
+ || cbToWrite == getImageBlockSize(&pImage->Header)))
+ {
+ /* If the destination block is unallocated at this point, it's
+ * either a zero block or a block which hasn't been used so far
+ * (which also means that it's a zero block. Don't need to write
+ * anything to this block if the data consists of just zeroes. */
+ if (vdIfIoIntIoCtxIsZero(pImage->pIfIo, pIoCtx, cbToWrite, true))
+ {
+ pImage->paBlocks[uBlock] = VDI_IMAGE_BLOCK_ZERO;
+ *pcbPreRead = 0;
+ *pcbPostRead = 0;
+ break;
+ }
+ }
+
+ if ( cbToWrite == getImageBlockSize(&pImage->Header)
+ && !(fWrite & VD_WRITE_NO_ALLOC))
+ {
+ /* Full block write to previously unallocated block.
+ * Allocate block and write data. */
+ Assert(!offWrite);
+ PVDIASYNCBLOCKALLOC pBlockAlloc = (PVDIASYNCBLOCKALLOC)RTMemAllocZ(sizeof(VDIASYNCBLOCKALLOC));
+ if (!pBlockAlloc)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ unsigned cBlocksAllocated = getImageBlocksAllocated(&pImage->Header);
+ uint64_t u64Offset = (uint64_t)cBlocksAllocated * pImage->cbTotalBlockData
+ + (pImage->offStartData + pImage->offStartBlockData);
+
+ pBlockAlloc->cBlocksAllocated = cBlocksAllocated;
+ pBlockAlloc->uBlock = uBlock;
+
+ *pcbPreRead = 0;
+ *pcbPostRead = 0;
+
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ u64Offset, pIoCtx, cbToWrite,
+ vdiBlockAllocUpdate, pBlockAlloc);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ break;
+ else if (RT_FAILURE(rc))
+ {
+ RTMemFree(pBlockAlloc);
+ break;
+ }
+
+ rc = vdiBlockAllocUpdate(pImage, pIoCtx, pBlockAlloc, rc);
+ }
+ else
+ {
+ /* Trying to do a partial write to an unallocated block. Don't do
+ * anything except letting the upper layer know what to do. */
+ *pcbPreRead = offWrite % getImageBlockSize(&pImage->Header);
+ *pcbPostRead = getImageBlockSize(&pImage->Header) - cbToWrite - *pcbPreRead;
+ rc = VERR_VD_BLOCK_FREE;
+ }
+ }
+ else
+ {
+ /* Block present in image file, write relevant data. */
+ uint64_t u64Offset = (uint64_t)pImage->paBlocks[uBlock] * pImage->cbTotalBlockData
+ + (pImage->offStartData + pImage->offStartBlockData + offWrite);
+ rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage,
+ u64Offset, pIoCtx, cbToWrite, NULL, NULL);
+ }
+ } while (0);
+
+ if (pcbWriteProcess)
+ *pcbWriteProcess = cbToWrite;
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+static DECLCALLBACK(int) vdiFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ Assert(pImage);
+
+ rc = vdiFlushImageIoCtx(pImage, pIoCtx);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetVersion */
+static DECLCALLBACK(unsigned) vdiGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->PreHeader.u32Version));
+ return pImage->PreHeader.u32Version;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetFileSize */
+static DECLCALLBACK(uint64_t) vdiGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ uint64_t cb = 0;
+
+ AssertPtrReturn(pImage, 0);
+
+ if (pImage->pStorage)
+ {
+ uint64_t cbFile;
+ 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) vdiGetPCHSGeometry(void *pBackendData, PVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)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) vdiSetPCHSGeometry(void *pBackendData, PCVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n",
+ pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)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) vdiGetLCHSGeometry(void *pBackendData, PVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc = VINF_SUCCESS;
+ VDIDISKGEOMETRY DummyGeo = { 0, 0, 0, VDI_GEOMETRY_SECTOR_SIZE };
+ PVDIDISKGEOMETRY pGeometry = getImageLCHSGeometry(&pImage->Header);
+ if (!pGeometry)
+ pGeometry = &DummyGeo;
+
+ if ( pGeometry->cCylinders > 0
+ && pGeometry->cHeads > 0
+ && pGeometry->cSectors > 0)
+ {
+ pLCHSGeometry->cCylinders = pGeometry->cCylinders;
+ pLCHSGeometry->cHeads = pGeometry->cHeads;
+ pLCHSGeometry->cSectors = pGeometry->cSectors;
+ }
+ 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) vdiSetLCHSGeometry(void *pBackendData, PCVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n",
+ pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ PVDIDISKGEOMETRY pGeometry;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ pGeometry = getImageLCHSGeometry(&pImage->Header);
+ if (pGeometry)
+ {
+ pGeometry->cCylinders = pLCHSGeometry->cCylinders;
+ pGeometry->cHeads = pLCHSGeometry->cHeads;
+ pGeometry->cSectors = pLCHSGeometry->cSectors;
+ pGeometry->cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+
+ /* Update header information in base image file. */
+ vdiFlushImage(pImage);
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */
+static DECLCALLBACK(int) vdiQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
+{
+ LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList));
+ PVDIIMAGEDESC pThis = (PVDIIMAGEDESC)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) vdiRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
+{
+ RT_NOREF1(pRegionList);
+ LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList));
+ PVDIIMAGEDESC pThis = (PVDIIMAGEDESC)pBackendData;
+ AssertPtr(pThis); RT_NOREF(pThis);
+
+ /* Nothing to do here. */
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetImageFlags */
+static DECLCALLBACK(unsigned) vdiGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uImageFlags));
+ return pImage->uImageFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetOpenFlags */
+static DECLCALLBACK(unsigned) vdiGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, 0);
+
+ LogFlowFunc(("returns %#x\n", pImage->uOpenFlags));
+ return pImage->uOpenFlags;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetOpenFlags */
+static DECLCALLBACK(int) vdiSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p uOpenFlags=%#x\n", pBackendData, uOpenFlags));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc;
+ const char *pszFilename;
+
+ /* 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_DISCARD
+ | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)))
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ /* Implement this operation via reopening the image. */
+ pszFilename = pImage->pszFilename;
+ rc = vdiFreeImage(pImage, false);
+ if (RT_SUCCESS(rc))
+ rc = vdiOpenImage(pImage, uOpenFlags);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetComment */
+static DECLCALLBACK(int) vdiGetComment(void *pBackendData, char *pszComment,
+ size_t cbComment)
+{
+ LogFlowFunc(("pBackendData=%#p pszComment=%#p cbComment=%zu\n", pBackendData, pszComment, cbComment));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc = VINF_SUCCESS;
+ char *pszTmp = getImageComment(&pImage->Header);
+ /* Make this foolproof even if the image doesn't have the zero
+ * termination. With some luck the repaired header will be saved. */
+ size_t cb = RTStrNLen(pszTmp, VDI_IMAGE_COMMENT_SIZE);
+ if (cb == VDI_IMAGE_COMMENT_SIZE)
+ {
+ pszTmp[VDI_IMAGE_COMMENT_SIZE-1] = '\0';
+ cb--;
+ }
+ if (cb < cbComment)
+ {
+ /* memcpy is much better than strncpy. */
+ memcpy(pszComment, pszTmp, cb + 1);
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+
+ LogFlowFunc(("returns %Rrc comment=\"%s\"\n", rc, pszComment));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetComment */
+static DECLCALLBACK(int) vdiSetComment(void *pBackendData, const char *pszComment)
+{
+ LogFlowFunc(("pBackendData=%#p pszComment=\"%s\"\n", pBackendData, pszComment));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ size_t cchComment = pszComment ? strlen(pszComment) : 0;
+ if (cchComment < VDI_IMAGE_COMMENT_SIZE)
+ {
+ /* we don't support old style images */
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 1)
+ {
+ /*
+ * Update the comment field, making sure to zero out all of the previous comment.
+ */
+ memset(pImage->Header.u.v1.szComment, '\0', VDI_IMAGE_COMMENT_SIZE);
+ memcpy(pImage->Header.u.v1.szComment, pszComment, cchComment);
+
+ /* write out new the header */
+ rc = vdiUpdateHeader(pImage);
+ }
+ else
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+ else
+ {
+ LogFunc(("pszComment is too long, %d bytes!\n", cchComment));
+ rc = VERR_VD_VDI_COMMENT_TOO_LONG;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetUuid */
+static DECLCALLBACK(int) vdiGetUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ *pUuid = *getImageCreationUUID(&pImage->Header);
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VINF_SUCCESS, pUuid));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetUuid */
+static DECLCALLBACK(int) vdiSetUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc = VINF_SUCCESS;
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 1)
+ pImage->Header.u.v1.uuidCreate = *pUuid;
+ /* Make it possible to clone old VDIs. */
+ else if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 0)
+ pImage->Header.u.v0.uuidCreate = *pUuid;
+ else
+ {
+ LogFunc(("Version is not supported!\n"));
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */
+static DECLCALLBACK(int) vdiGetModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ *pUuid = *getImageModificationUUID(&pImage->Header);
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VINF_SUCCESS, pUuid));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */
+static DECLCALLBACK(int) vdiSetModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc = VINF_SUCCESS;
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 1)
+ pImage->Header.u.v1.uuidModify = *pUuid;
+ /* Make it possible to clone old VDIs. */
+ else if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 0)
+ pImage->Header.u.v0.uuidModify = *pUuid;
+ else
+ {
+ LogFunc(("Version is not supported!\n"));
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */
+static DECLCALLBACK(int) vdiGetParentUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ *pUuid = *getImageParentUUID(&pImage->Header);
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VINF_SUCCESS, pUuid));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */
+static DECLCALLBACK(int) vdiSetParentUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc = VINF_SUCCESS;
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 1)
+ pImage->Header.u.v1.uuidLinkage = *pUuid;
+ /* Make it possible to clone old VDIs. */
+ else if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 0)
+ pImage->Header.u.v0.uuidLinkage = *pUuid;
+ else
+ {
+ LogFunc(("Version is not supported!\n"));
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */
+static DECLCALLBACK(int) vdiGetParentModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ *pUuid = *getImageParentModificationUUID(&pImage->Header);
+
+ LogFlowFunc(("returns %Rrc (%RTuuid)\n", VINF_SUCCESS, pUuid));
+ return VINF_SUCCESS;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */
+static DECLCALLBACK(int) vdiSetParentModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+ LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid));
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturn(pImage, VERR_VD_NOT_OPENED);
+
+ int rc = VINF_SUCCESS;
+ if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) == 1)
+ pImage->Header.u.v1.uuidParentModify = *pUuid;
+ else
+ {
+ LogFunc(("Version is not supported!\n"));
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ }
+ }
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnDump */
+static DECLCALLBACK(void) vdiDump(void *pBackendData)
+{
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+
+ AssertPtrReturnVoid(pImage);
+ vdIfErrorMessage(pImage->pIfError, "Dumping VDI image \"%s\" mode=%s uOpenFlags=%X File=%#p\n",
+ pImage->pszFilename,
+ (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) ? "r/o" : "r/w",
+ pImage->uOpenFlags,
+ pImage->pStorage);
+ vdIfErrorMessage(pImage->pIfError, "Header: Version=%08X Type=%X Flags=%X Size=%llu\n",
+ pImage->PreHeader.u32Version,
+ getImageType(&pImage->Header),
+ getImageFlags(&pImage->Header),
+ getImageDiskSize(&pImage->Header));
+ vdIfErrorMessage(pImage->pIfError, "Header: cbBlock=%u cbBlockExtra=%u cBlocks=%u cBlocksAllocated=%u\n",
+ getImageBlockSize(&pImage->Header),
+ getImageExtraBlockSize(&pImage->Header),
+ getImageBlocks(&pImage->Header),
+ getImageBlocksAllocated(&pImage->Header));
+ vdIfErrorMessage(pImage->pIfError, "Header: offBlocks=%u offData=%u\n",
+ getImageBlocksOffset(&pImage->Header),
+ getImageDataOffset(&pImage->Header));
+ PVDIDISKGEOMETRY pg = getImageLCHSGeometry(&pImage->Header);
+ if (pg)
+ vdIfErrorMessage(pImage->pIfError, "Header: Geometry: C/H/S=%u/%u/%u cbSector=%u\n",
+ pg->cCylinders, pg->cHeads, pg->cSectors, pg->cbSector);
+ vdIfErrorMessage(pImage->pIfError, "Header: uuidCreation={%RTuuid}\n", getImageCreationUUID(&pImage->Header));
+ vdIfErrorMessage(pImage->pIfError, "Header: uuidModification={%RTuuid}\n", getImageModificationUUID(&pImage->Header));
+ vdIfErrorMessage(pImage->pIfError, "Header: uuidParent={%RTuuid}\n", getImageParentUUID(&pImage->Header));
+ if (GET_MAJOR_HEADER_VERSION(&pImage->Header) >= 1)
+ vdIfErrorMessage(pImage->pIfError, "Header: uuidParentModification={%RTuuid}\n", getImageParentModificationUUID(&pImage->Header));
+ vdIfErrorMessage(pImage->pIfError, "Image: fFlags=%08X offStartBlocks=%u offStartData=%u\n",
+ pImage->uImageFlags, pImage->offStartBlocks, pImage->offStartData);
+ vdIfErrorMessage(pImage->pIfError, "Image: uBlockMask=%08X cbTotalBlockData=%u uShiftOffset2Index=%u offStartBlockData=%u\n",
+ pImage->uBlockMask,
+ pImage->cbTotalBlockData,
+ pImage->uShiftOffset2Index,
+ pImage->offStartBlockData);
+
+ unsigned uBlock, cBlocksNotFree, cBadBlocks, cBlocks = getImageBlocks(&pImage->Header);
+ for (uBlock=0, cBlocksNotFree=0, cBadBlocks=0; uBlock<cBlocks; uBlock++)
+ {
+ if (IS_VDI_IMAGE_BLOCK_ALLOCATED(pImage->paBlocks[uBlock]))
+ {
+ cBlocksNotFree++;
+ if (pImage->paBlocks[uBlock] >= cBlocks)
+ cBadBlocks++;
+ }
+ }
+ if (cBlocksNotFree != getImageBlocksAllocated(&pImage->Header))
+ {
+ vdIfErrorMessage(pImage->pIfError, "!! WARNING: %u blocks actually allocated (cBlocksAllocated=%u) !!\n",
+ cBlocksNotFree, getImageBlocksAllocated(&pImage->Header));
+ }
+ if (cBadBlocks)
+ {
+ vdIfErrorMessage(pImage->pIfError, "!! WARNING: %u bad blocks found !!\n",
+ cBadBlocks);
+ }
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnCompact */
+static DECLCALLBACK(int) vdiCompact(void *pBackendData, unsigned uPercentStart,
+ unsigned uPercentSpan, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, PVDINTERFACE pVDIfsOperation)
+{
+ RT_NOREF2(pVDIfsDisk, pVDIfsImage);
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+ void *pvBuf = NULL, *pvTmp = NULL;
+ unsigned *paBlocks2 = NULL;
+
+ PFNVDPARENTREAD pfnParentRead = NULL;
+ void *pvParent = NULL;
+ PVDINTERFACEPARENTSTATE pIfParentState = VDIfParentStateGet(pVDIfsOperation);
+ if (pIfParentState)
+ {
+ pfnParentRead = pIfParentState->pfnParentRead;
+ pvParent = pIfParentState->Core.pvUser;
+ }
+
+ PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation);
+ PVDINTERFACEQUERYRANGEUSE pIfQueryRangeUse = VDIfQueryRangeUseGet(pVDIfsOperation);
+
+ do
+ {
+ AssertBreakStmt(pImage, rc = VERR_INVALID_PARAMETER);
+
+ AssertBreakStmt(!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY),
+ rc = VERR_VD_IMAGE_READ_ONLY);
+
+ unsigned cBlocks;
+ unsigned cBlocksToMove = 0;
+ size_t cbBlock;
+ cBlocks = getImageBlocks(&pImage->Header);
+ cbBlock = getImageBlockSize(&pImage->Header);
+ if (pfnParentRead)
+ {
+ pvBuf = RTMemTmpAlloc(cbBlock);
+ AssertBreakStmt(pvBuf, rc = VERR_NO_MEMORY);
+ }
+ pvTmp = RTMemTmpAlloc(cbBlock);
+ AssertBreakStmt(pvTmp, rc = VERR_NO_MEMORY);
+
+ uint64_t cbFile;
+ rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &cbFile);
+ AssertRCBreak(rc);
+ unsigned cBlocksAllocated = (unsigned)((cbFile - pImage->offStartData - pImage->offStartBlockData) >> pImage->uShiftOffset2Index);
+ if (cBlocksAllocated == 0)
+ {
+ /* No data blocks in this image, no need to compact. */
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ /* Allocate block array for back resolving. */
+ paBlocks2 = (unsigned *)RTMemAlloc(sizeof(unsigned *) * cBlocksAllocated);
+ AssertBreakStmt(paBlocks2, rc = VERR_NO_MEMORY);
+ /* Fill out back resolving, check/fix allocation errors before
+ * compacting the image, just to be on the safe side. Update the
+ * image contents straight away, as this enables cancelling. */
+ for (unsigned i = 0; i < cBlocksAllocated; i++)
+ paBlocks2[i] = VDI_IMAGE_BLOCK_FREE;
+ rc = VINF_SUCCESS;
+ for (unsigned i = 0; i < cBlocks; i++)
+ {
+ VDIIMAGEBLOCKPOINTER ptrBlock = pImage->paBlocks[i];
+ if (IS_VDI_IMAGE_BLOCK_ALLOCATED(ptrBlock))
+ {
+ if (ptrBlock < cBlocksAllocated)
+ {
+ if (paBlocks2[ptrBlock] == VDI_IMAGE_BLOCK_FREE)
+ paBlocks2[ptrBlock] = i;
+ else
+ {
+ LogFunc(("Freed cross-linked block %u in file \"%s\"\n",
+ i, pImage->pszFilename));
+ pImage->paBlocks[i] = VDI_IMAGE_BLOCK_FREE;
+ rc = vdiUpdateBlockInfo(pImage, i);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ else
+ {
+ LogFunc(("Freed out of bounds reference for block %u in file \"%s\"\n",
+ i, pImage->pszFilename));
+ pImage->paBlocks[i] = VDI_IMAGE_BLOCK_FREE;
+ rc = vdiUpdateBlockInfo(pImage, i);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ }
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Find redundant information and update the block pointers
+ * accordingly, creating bubbles. Keep disk up to date, as this
+ * enables cancelling. */
+ for (unsigned i = 0; i < cBlocks; i++)
+ {
+ VDIIMAGEBLOCKPOINTER ptrBlock = pImage->paBlocks[i];
+ if (IS_VDI_IMAGE_BLOCK_ALLOCATED(ptrBlock))
+ {
+ /* Block present in image file, read relevant data. */
+ uint64_t u64Offset = (uint64_t)ptrBlock * pImage->cbTotalBlockData
+ + (pImage->offStartData + pImage->offStartBlockData);
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, u64Offset, pvTmp, cbBlock);
+ if (RT_FAILURE(rc))
+ break;
+
+ if (ASMBitFirstSet((volatile void *)pvTmp, (uint32_t)cbBlock * 8) == -1)
+ {
+ pImage->paBlocks[i] = VDI_IMAGE_BLOCK_ZERO;
+ rc = vdiUpdateBlockInfo(pImage, i);
+ if (RT_FAILURE(rc))
+ break;
+ paBlocks2[ptrBlock] = VDI_IMAGE_BLOCK_FREE;
+ /* Adjust progress info, one block to be relocated. */
+ cBlocksToMove++;
+ }
+ else if (pfnParentRead)
+ {
+ rc = pfnParentRead(pvParent, (uint64_t)i * cbBlock, pvBuf, cbBlock);
+ if (RT_FAILURE(rc))
+ break;
+ if (!memcmp(pvTmp, pvBuf, cbBlock))
+ {
+ pImage->paBlocks[i] = VDI_IMAGE_BLOCK_FREE;
+ rc = vdiUpdateBlockInfo(pImage, i);
+ if (RT_FAILURE(rc))
+ break;
+ paBlocks2[ptrBlock] = VDI_IMAGE_BLOCK_FREE;
+ /* Adjust progress info, one block to be relocated. */
+ cBlocksToMove++;
+ }
+ }
+ }
+
+ /* Check if the range is in use if the block is still allocated. */
+ ptrBlock = pImage->paBlocks[i];
+ if ( IS_VDI_IMAGE_BLOCK_ALLOCATED(ptrBlock)
+ && pIfQueryRangeUse)
+ {
+ bool fUsed = true;
+
+ rc = vdIfQueryRangeUse(pIfQueryRangeUse, (uint64_t)i * cbBlock, cbBlock, &fUsed);
+ if (RT_FAILURE(rc))
+ break;
+ if (!fUsed)
+ {
+ pImage->paBlocks[i] = VDI_IMAGE_BLOCK_ZERO;
+ rc = vdiUpdateBlockInfo(pImage, i);
+ if (RT_FAILURE(rc))
+ break;
+ paBlocks2[ptrBlock] = VDI_IMAGE_BLOCK_FREE;
+ /* Adjust progress info, one block to be relocated. */
+ cBlocksToMove++;
+ }
+ }
+
+ vdIfProgress(pIfProgress, (uint64_t)i * uPercentSpan / (cBlocks + cBlocksToMove) + uPercentStart);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Fill bubbles with other data (if available). */
+ unsigned cBlocksMoved = 0;
+ unsigned uBlockUsedPos = cBlocksAllocated;
+ for (unsigned i = 0; i < cBlocksAllocated; i++)
+ {
+ unsigned uBlock = paBlocks2[i];
+ if (uBlock == VDI_IMAGE_BLOCK_FREE)
+ {
+ unsigned uBlockData = VDI_IMAGE_BLOCK_FREE;
+ while (uBlockUsedPos > i && uBlockData == VDI_IMAGE_BLOCK_FREE)
+ {
+ uBlockUsedPos--;
+ uBlockData = paBlocks2[uBlockUsedPos];
+ }
+ /* Terminate early if there is no block which needs copying. */
+ if (uBlockUsedPos == i)
+ break;
+ uint64_t u64Offset = (uint64_t)uBlockUsedPos * pImage->cbTotalBlockData
+ + (pImage->offStartData + pImage->offStartBlockData);
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, u64Offset,
+ pvTmp, cbBlock);
+ u64Offset = (uint64_t)i * pImage->cbTotalBlockData
+ + (pImage->offStartData + pImage->offStartBlockData);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, u64Offset,
+ pvTmp, cbBlock);
+ pImage->paBlocks[uBlockData] = i;
+ setImageBlocksAllocated(&pImage->Header, cBlocksAllocated - cBlocksMoved);
+ rc = vdiUpdateBlockInfo(pImage, uBlockData);
+ if (RT_FAILURE(rc))
+ break;
+ paBlocks2[i] = uBlockData;
+ paBlocks2[uBlockUsedPos] = VDI_IMAGE_BLOCK_FREE;
+ cBlocksMoved++;
+ }
+
+ rc = vdIfProgress(pIfProgress, (uint64_t)(cBlocks + cBlocksMoved) * uPercentSpan / (cBlocks + cBlocksToMove) + uPercentStart);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Update image header. */
+ setImageBlocksAllocated(&pImage->Header, uBlockUsedPos);
+ vdiUpdateHeader(pImage);
+
+ /* Truncate the image to the proper size to finish compacting. */
+ rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage,
+ (uint64_t)uBlockUsedPos * pImage->cbTotalBlockData
+ + pImage->offStartData + pImage->offStartBlockData);
+ } while (0);
+
+ if (paBlocks2)
+ RTMemTmpFree(paBlocks2);
+ if (pvTmp)
+ RTMemTmpFree(pvTmp);
+ if (pvBuf)
+ RTMemTmpFree(pvBuf);
+
+ if (RT_SUCCESS(rc))
+ vdIfProgress(pIfProgress, uPercentStart + uPercentSpan);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/** @copydoc VDIMAGEBACKEND::pfnResize */
+static DECLCALLBACK(int) vdiResize(void *pBackendData, uint64_t cbSize,
+ PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry,
+ unsigned uPercentStart, unsigned uPercentSpan,
+ PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
+ PVDINTERFACE pVDIfsOperation)
+{
+ RT_NOREF5(uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation);
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Check size. Maximum 4PB-3M. No tricks with adjusting the 1M block size
+ * so far, which would extend the size. */
+ if ( !cbSize
+ || cbSize >= _1P * 4 - _1M * 3
+ || cbSize < VDI_IMAGE_DEFAULT_BLOCK_SIZE)
+ return VERR_VD_INVALID_SIZE;
+
+ /*
+ * Making the image smaller is not supported at the moment.
+ * Resizing is also not supported for fixed size images and
+ * very old images.
+ */
+ /** @todo implement making the image smaller, it is the responsibility of
+ * the user to know what he's doing. */
+ if (cbSize < getImageDiskSize(&pImage->Header))
+ rc = VERR_VD_SHRINK_NOT_SUPPORTED;
+ else if ( GET_MAJOR_HEADER_VERSION(&pImage->Header) == 0
+ || pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED)
+ rc = VERR_NOT_SUPPORTED;
+ else if (cbSize > getImageDiskSize(&pImage->Header))
+ {
+ unsigned cBlocksAllocated = getImageBlocksAllocated(&pImage->Header); /** < Blocks currently allocated, doesn't change during resize */
+ unsigned const cbBlock = RT_MAX(getImageBlockSize(&pImage->Header), 1);
+ uint32_t cBlocksNew = cbSize / cbBlock; /** < New number of blocks in the image after the resize */
+ if (cbSize % cbBlock)
+ cBlocksNew++;
+
+ uint32_t cBlocksOld = getImageBlocks(&pImage->Header); /** < Number of blocks before the resize. */
+ uint64_t cbBlockspaceNew = cBlocksNew * sizeof(VDIIMAGEBLOCKPOINTER); /** < Required space for the block array after the resize. */
+ uint64_t offStartDataNew = RT_ALIGN_32(pImage->offStartBlocks + cbBlockspaceNew, VDI_DATA_ALIGN); /** < New start offset for block data after the resize */
+
+ if (pImage->offStartData < offStartDataNew)
+ {
+ if (cBlocksAllocated > 0)
+ {
+ /* Calculate how many sectors need to be relocated. */
+ uint64_t cbOverlapping = offStartDataNew - pImage->offStartData;
+ unsigned cBlocksReloc = cbOverlapping / cbBlock;
+ if (cbOverlapping % cbBlock)
+ cBlocksReloc++;
+
+ /* Since only full blocks can be relocated the new data start is
+ * determined by moving it block by block. */
+ cBlocksReloc = RT_MIN(cBlocksReloc, cBlocksAllocated);
+ offStartDataNew = pImage->offStartData;
+
+ /* Do the relocation. */
+ LogFlow(("Relocating %u blocks\n", cBlocksReloc));
+
+ /*
+ * Get the blocks we need to relocate first, they are appended to the end
+ * of the image.
+ */
+ void *pvBuf = NULL, *pvZero = NULL;
+ do
+ {
+ /* Allocate data buffer. */
+ pvBuf = RTMemAllocZ(pImage->cbTotalBlockData);
+ if (!pvBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* Allocate buffer for overwriting with zeroes. */
+ pvZero = RTMemAllocZ(pImage->cbTotalBlockData);
+ if (!pvZero)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ for (unsigned i = 0; i < cBlocksReloc; i++)
+ {
+ /* Search the index in the block table. */
+ for (unsigned idxBlock = 0; idxBlock < cBlocksOld; idxBlock++)
+ {
+ if (!pImage->paBlocks[idxBlock])
+ {
+ /* Read data and append to the end of the image. */
+ rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage,
+ offStartDataNew, pvBuf,
+ pImage->cbTotalBlockData);
+ if (RT_FAILURE(rc))
+ break;
+
+ uint64_t offBlockAppend;
+ rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &offBlockAppend);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ offBlockAppend, pvBuf,
+ pImage->cbTotalBlockData);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Zero out the old block area. */
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage,
+ offStartDataNew, pvZero,
+ pImage->cbTotalBlockData);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Update block counter. */
+ pImage->paBlocks[idxBlock] = cBlocksAllocated - 1;
+
+ /*
+ * Decrease the block number of all other entries in the array.
+ * They were moved one block to the front.
+ * Doing it as a separate step iterating over the array again
+ * because an error while relocating the block might end up
+ * in a corrupted image otherwise.
+ */
+ for (unsigned idxBlock2 = 0; idxBlock2 < cBlocksOld; idxBlock2++)
+ {
+ if ( idxBlock2 != idxBlock
+ && IS_VDI_IMAGE_BLOCK_ALLOCATED(pImage->paBlocks[idxBlock2]))
+ pImage->paBlocks[idxBlock2]--;
+ }
+
+ /* Continue with the next block. */
+ break;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ offStartDataNew += pImage->cbTotalBlockData;
+ }
+ } while (0);
+
+ if (pvBuf)
+ RTMemFree(pvBuf);
+ if (pvZero)
+ RTMemFree(pvZero);
+ }
+
+ /*
+ * We need to update the new offsets for the image data in the out of memory
+ * case too because we relocated the blocks already.
+ */
+ pImage->offStartData = offStartDataNew;
+ setImageDataOffset(&pImage->Header, offStartDataNew);
+ }
+
+ /*
+ * Relocation done, expand the block array and update the header with
+ * the new data.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ PVDIIMAGEBLOCKPOINTER paBlocksNew = (PVDIIMAGEBLOCKPOINTER)RTMemRealloc(pImage->paBlocks, cbBlockspaceNew);
+ if (paBlocksNew)
+ {
+ pImage->paBlocks = paBlocksNew;
+
+ /* Mark the new blocks as unallocated. */
+ for (unsigned idxBlock = cBlocksOld; idxBlock < cBlocksNew; idxBlock++)
+ pImage->paBlocks[idxBlock] = VDI_IMAGE_BLOCK_FREE;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ /* Write the block array before updating the rest. */
+ vdiConvBlocksEndianess(VDIECONV_H2F, pImage->paBlocks, cBlocksNew);
+ rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, pImage->offStartBlocks,
+ pImage->paBlocks, cbBlockspaceNew);
+ vdiConvBlocksEndianess(VDIECONV_F2H, pImage->paBlocks, cBlocksNew);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Update size and new block count. */
+ setImageDiskSize(&pImage->Header, cbSize);
+ setImageBlocks(&pImage->Header, cBlocksNew);
+ /* Update geometry. */
+ pImage->PCHSGeometry = *pPCHSGeometry;
+ pImage->cbImage = cbSize;
+
+ PVDIDISKGEOMETRY pGeometry = getImageLCHSGeometry(&pImage->Header);
+ if (pGeometry)
+ {
+ pGeometry->cCylinders = pLCHSGeometry->cCylinders;
+ pGeometry->cHeads = pLCHSGeometry->cHeads;
+ pGeometry->cSectors = pLCHSGeometry->cSectors;
+ pGeometry->cbSector = VDI_GEOMETRY_SECTOR_SIZE;
+ }
+ }
+ }
+
+ /* Update header information in base image file. */
+ vdiFlushImage(pImage);
+ }
+ /* Same size doesn't change the image at all. */
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnDiscard */
+static DECLCALLBACK(int) vdiDiscard(void *pBackendData, PVDIOCTX pIoCtx,
+ uint64_t uOffset, size_t cbDiscard,
+ size_t *pcbPreAllocated, size_t *pcbPostAllocated,
+ size_t *pcbActuallyDiscarded, void **ppbmAllocationBitmap,
+ unsigned fDiscard)
+{
+ PVDIIMAGEDESC pImage = (PVDIIMAGEDESC)pBackendData;
+ unsigned uBlock;
+ unsigned offDiscard;
+ int rc = VINF_SUCCESS;
+ void *pvBlock = NULL;
+
+ LogFlowFunc(("pBackendData=%#p pIoCtx=%#p uOffset=%llu cbDiscard=%zu pcbPreAllocated=%#p pcbPostAllocated=%#p pcbActuallyDiscarded=%#p ppbmAllocationBitmap=%#p fDiscard=%#x\n",
+ pBackendData, pIoCtx, uOffset, cbDiscard, pcbPreAllocated, pcbPostAllocated, pcbActuallyDiscarded, ppbmAllocationBitmap, fDiscard));
+
+ AssertPtr(pImage);
+ Assert(!(uOffset % 512));
+ Assert(!(cbDiscard % 512));
+
+ AssertMsgReturn(!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY),
+ ("Image is readonly\n"), VERR_VD_IMAGE_READ_ONLY);
+ AssertMsgReturn( uOffset + cbDiscard <= getImageDiskSize(&pImage->Header)
+ && cbDiscard,
+ ("Invalid parameters uOffset=%llu cbDiscard=%zu\n",
+ uOffset, cbDiscard),
+ VERR_INVALID_PARAMETER);
+
+ do
+ {
+ AssertMsgBreakStmt(!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY),
+ ("Image is opened readonly\n"),
+ rc = VERR_VD_IMAGE_READ_ONLY);
+
+ AssertMsgBreakStmt(cbDiscard,
+ ("cbDiscard=%u\n", cbDiscard),
+ rc = VERR_INVALID_PARAMETER);
+
+ /* Calculate starting block number and offset inside it. */
+ uBlock = (unsigned)(uOffset >> pImage->uShiftOffset2Index);
+ offDiscard = (unsigned)uOffset & pImage->uBlockMask;
+
+ /* Clip range to at most the rest of the block. */
+ cbDiscard = RT_MIN(cbDiscard, getImageBlockSize(&pImage->Header) - offDiscard);
+ Assert(!(cbDiscard % 512));
+
+ if (pcbPreAllocated)
+ *pcbPreAllocated = 0;
+
+ if (pcbPostAllocated)
+ *pcbPostAllocated = 0;
+
+ if (IS_VDI_IMAGE_BLOCK_ALLOCATED(pImage->paBlocks[uBlock]))
+ {
+ unsigned const cbBlock = RT_MAX(getImageBlockSize(&pImage->Header), 1);
+ size_t const cbPreAllocated = offDiscard % cbBlock;
+ size_t const cbPostAllocated = getImageBlockSize(&pImage->Header) - cbDiscard - cbPreAllocated;
+ uint8_t *pbBlockData;
+
+ /* Read the block data. */
+ pvBlock = RTMemAlloc(pImage->cbTotalBlockData);
+ if (!pvBlock)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ if (!cbPreAllocated && !cbPostAllocated)
+ {
+ /*
+ * Discarding a whole block, don't check for allocated sectors.
+ * It is possible to just remove the whole block which avoids
+ * one read and checking the whole block for data.
+ */
+ rc = vdiDiscardBlockAsync(pImage, pIoCtx, uBlock, pvBlock);
+ }
+ else if (fDiscard & VD_DISCARD_MARK_UNUSED)
+ {
+ /* Just zero out the given range. */
+ memset(pvBlock, 0, cbDiscard);
+
+ uint64_t u64Offset = (uint64_t)pImage->paBlocks[uBlock] * pImage->cbTotalBlockData + pImage->offStartData + offDiscard;
+ rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage,
+ u64Offset, pvBlock, cbDiscard, pIoCtx,
+ NULL, NULL);
+ RTMemFree(pvBlock);
+ }
+ else
+ {
+ /*
+ * Read complete block as metadata, the I/O context has no memory buffer
+ * and we need to access the content directly anyway.
+ */
+ PVDMETAXFER pMetaXfer;
+ pbBlockData = (uint8_t *)pvBlock + pImage->offStartBlockData;
+
+ uint64_t u64Offset = (uint64_t)pImage->paBlocks[uBlock] * pImage->cbTotalBlockData + pImage->offStartData;
+ rc = vdIfIoIntFileReadMeta(pImage->pIfIo, pImage->pStorage, u64Offset,
+ pbBlockData, pImage->cbTotalBlockData,
+ pIoCtx, &pMetaXfer, NULL, NULL);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pvBlock);
+ break;
+ }
+
+ vdIfIoIntMetaXferRelease(pImage->pIfIo, pMetaXfer);
+
+ /* Clear data. */
+ memset(pbBlockData + offDiscard , 0, cbDiscard);
+
+ Assert(!(cbDiscard % 4));
+ Assert(getImageBlockSize(&pImage->Header) * 8 <= UINT32_MAX);
+ if (ASMBitFirstSet((volatile void *)pbBlockData, getImageBlockSize(&pImage->Header) * 8) == -1)
+ rc = vdiDiscardBlockAsync(pImage, pIoCtx, uBlock, pvBlock);
+ else
+ {
+ /* Block has data, create allocation bitmap. */
+ *pcbPreAllocated = cbPreAllocated;
+ *pcbPostAllocated = cbPostAllocated;
+ *ppbmAllocationBitmap = vdiAllocationBitmapCreate(pbBlockData, getImageBlockSize(&pImage->Header));
+ if (RT_UNLIKELY(!*ppbmAllocationBitmap))
+ rc = VERR_NO_MEMORY;
+ else
+ rc = VERR_VD_DISCARD_ALIGNMENT_NOT_MET;
+
+ RTMemFree(pvBlock);
+ }
+ } /* if: no complete block discarded */
+ } /* if: Block is allocated. */
+ /* else: nothing to do. */
+ } while (0);
+
+ if (pcbActuallyDiscarded)
+ *pcbActuallyDiscarded = cbDiscard;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnRepair */
+static DECLCALLBACK(int) vdiRepair(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, uint32_t fFlags)
+{
+ LogFlowFunc(("pszFilename=\"%s\" pVDIfsDisk=%#p pVDIfsImage=%#p\n", pszFilename, pVDIfsDisk, pVDIfsImage));
+ int rc;
+ PVDINTERFACEERROR pIfError;
+ PVDINTERFACEIOINT pIfIo;
+ PVDIOSTORAGE pStorage = NULL;
+ uint64_t cbFile;
+ PVDIIMAGEBLOCKPOINTER paBlocks = NULL;
+ uint32_t *pu32BlockBitmap = NULL;
+ VDIPREHEADER PreHdr;
+ VDIHEADER Hdr;
+
+ pIfIo = VDIfIoIntGet(pVDIfsImage);
+ AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER);
+
+ pIfError = VDIfErrorGet(pVDIfsDisk);
+
+ do
+ {
+ bool fRepairBlockArray = false;
+ bool fRepairHdr = false;
+
+ rc = vdIfIoIntFileOpen(pIfIo, pszFilename,
+ VDOpenFlagsToFileOpenFlags( fFlags & VD_REPAIR_DRY_RUN
+ ? VD_OPEN_FLAGS_READONLY
+ : 0,
+ false /* fCreate */),
+ &pStorage);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, "VDI: Failed to open image \"%s\"", pszFilename);
+ break;
+ }
+
+ rc = vdIfIoIntFileGetSize(pIfIo, pStorage, &cbFile);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, "VDI: Failed to query image size");
+ break;
+ }
+
+ /* Read pre-header. */
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, 0, &PreHdr, sizeof(PreHdr));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, N_("VDI: Error reading pre-header in '%s'"), pszFilename);
+ break;
+ }
+ vdiConvPreHeaderEndianess(VDIECONV_F2H, &PreHdr, &PreHdr);
+ rc = vdiValidatePreHeader(&PreHdr);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ N_("VDI: invalid pre-header in '%s'"), pszFilename);
+ break;
+ }
+
+ /* Read header. */
+ Hdr.uVersion = PreHdr.u32Version;
+ switch (GET_MAJOR_HEADER_VERSION(&Hdr))
+ {
+ case 0:
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, sizeof(PreHdr),
+ &Hdr.u.v0, sizeof(Hdr.u.v0));
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, N_("VDI: error reading v0 header in '%s'"),
+ pszFilename);
+ vdiConvHeaderEndianessV0(VDIECONV_F2H, &Hdr.u.v0, &Hdr.u.v0);
+ break;
+ case 1:
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, sizeof(PreHdr),
+ &Hdr.u.v1, sizeof(Hdr.u.v1));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, N_("VDI: error reading v1 header in '%s'"),
+ pszFilename);
+ }
+ vdiConvHeaderEndianessV1(VDIECONV_F2H, &Hdr.u.v1, &Hdr.u.v1);
+ if (Hdr.u.v1.cbHeader >= sizeof(Hdr.u.v1plus))
+ {
+ /* Read the VDI 1.1+ header completely. */
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, sizeof(PreHdr),
+ &Hdr.u.v1plus, sizeof(Hdr.u.v1plus));
+ if (RT_FAILURE(rc))
+ rc = vdIfError(pIfError, rc, RT_SRC_POS, N_("VDI: error reading v1.1+ header in '%s'"),
+ pszFilename);
+ vdiConvHeaderEndianessV1p(VDIECONV_F2H, &Hdr.u.v1plus, &Hdr.u.v1plus);
+ }
+ break;
+ default:
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ N_("VDI: unsupported major version %u in '%s'"),
+ GET_MAJOR_HEADER_VERSION(&Hdr), pszFilename);
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = vdiValidateHeader(&Hdr);
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ N_("VDI: invalid header in '%s'"), pszFilename);
+ break;
+ }
+ }
+
+ /*
+ * Check that the disk size is correctly aligned,
+ * see comment above the same check in vdiImageReadHeader().
+ */
+ uint64_t cbDisk = getImageDiskSize(&Hdr);
+ if (cbDisk & 0x1ff)
+ {
+ uint64_t cbDiskNew = cbDisk & ~UINT64_C(0x1ff);
+ vdIfErrorMessage(pIfError, "Disk size in the header is not sector aligned, rounding down (%llu -> %llu)\n",
+ cbDisk, cbDiskNew);
+ setImageDiskSize(&Hdr, cbDiskNew);
+ fRepairHdr = true;
+ }
+
+ /* Setup image parameters by header. */
+ uint64_t offStartBlocks, offStartData;
+ size_t cbTotalBlockData;
+
+ offStartBlocks = getImageBlocksOffset(&Hdr);
+ offStartData = getImageDataOffset(&Hdr);
+ cbTotalBlockData = getImageExtraBlockSize(&Hdr) + getImageBlockSize(&Hdr);
+
+ /* Allocate memory for blocks array. */
+ paBlocks = (PVDIIMAGEBLOCKPOINTER)RTMemAlloc(sizeof(VDIIMAGEBLOCKPOINTER) * getImageBlocks(&Hdr));
+ if (!paBlocks)
+ {
+ rc = vdIfError(pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ "Failed to allocate memory for block array");
+ break;
+ }
+
+ /* Read blocks array. */
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, offStartBlocks, paBlocks,
+ getImageBlocks(&Hdr) * sizeof(VDIIMAGEBLOCKPOINTER));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ "Failed to read block array (at %llu), %Rrc",
+ offStartBlocks, rc);
+ break;
+ }
+ vdiConvBlocksEndianess(VDIECONV_F2H, paBlocks, getImageBlocks(&Hdr));
+
+ pu32BlockBitmap = (uint32_t *)RTMemAllocZ(RT_ALIGN_Z(getImageBlocks(&Hdr) / 8, 4));
+ if (!pu32BlockBitmap)
+ {
+ rc = vdIfError(pIfError, VERR_NO_MEMORY, RT_SRC_POS,
+ "Failed to allocate memory for block bitmap");
+ break;
+ }
+
+ for (uint32_t i = 0; i < getImageBlocks(&Hdr); i++)
+ {
+ if (IS_VDI_IMAGE_BLOCK_ALLOCATED(paBlocks[i]))
+ {
+ uint64_t offBlock = (uint64_t)paBlocks[i] * cbTotalBlockData
+ + offStartData;
+
+ /*
+ * Check that the offsets are valid (inside of the image) and
+ * that there are no double references.
+ */
+ if (offBlock + cbTotalBlockData > cbFile)
+ {
+ vdIfErrorMessage(pIfError, "Entry %u points to invalid offset %llu, clearing\n",
+ i, offBlock);
+ paBlocks[i] = VDI_IMAGE_BLOCK_FREE;
+ fRepairBlockArray = true;
+ }
+ else if (ASMBitTestAndSet(pu32BlockBitmap, paBlocks[i]))
+ {
+ vdIfErrorMessage(pIfError, "Entry %u points to an already referenced data block, clearing\n",
+ i);
+ paBlocks[i] = VDI_IMAGE_BLOCK_FREE;
+ fRepairBlockArray = true;
+ }
+ }
+ }
+
+ /* Write repaired structures now. */
+ if (!fRepairBlockArray && !fRepairHdr)
+ vdIfErrorMessage(pIfError, "VDI image is in a consistent state, no repair required\n");
+ else if (!(fFlags & VD_REPAIR_DRY_RUN))
+ {
+ if (fRepairHdr)
+ {
+ switch (GET_MAJOR_HEADER_VERSION(&Hdr))
+ {
+ case 0:
+ {
+ VDIHEADER0 Hdr0;
+ vdiConvHeaderEndianessV0(VDIECONV_H2F, &Hdr0, &Hdr.u.v0);
+ rc = vdIfIoIntFileWriteSync(pIfIo, pStorage, sizeof(VDIPREHEADER),
+ &Hdr0, sizeof(Hdr0));
+ break;
+ }
+ case 1:
+ if (Hdr.u.v1plus.cbHeader < sizeof(Hdr.u.v1plus))
+ {
+ VDIHEADER1 Hdr1;
+ vdiConvHeaderEndianessV1(VDIECONV_H2F, &Hdr1, &Hdr.u.v1);
+ rc = vdIfIoIntFileWriteSync(pIfIo, pStorage, sizeof(VDIPREHEADER),
+ &Hdr1, sizeof(Hdr1));
+ }
+ else
+ {
+ VDIHEADER1PLUS Hdr1plus;
+ vdiConvHeaderEndianessV1p(VDIECONV_H2F, &Hdr1plus, &Hdr.u.v1plus);
+ rc = vdIfIoIntFileWriteSync(pIfIo, pStorage, sizeof(VDIPREHEADER),
+ &Hdr1plus, sizeof(Hdr1plus));
+ }
+ break;
+ default:
+ AssertMsgFailed(("Header indicates unsupported version which should not happen here!\n"));
+ rc = VERR_VD_VDI_UNSUPPORTED_VERSION;
+ break;
+ }
+ }
+
+ if (fRepairBlockArray)
+ {
+ vdIfErrorMessage(pIfError, "Writing repaired block allocation table...\n");
+
+ vdiConvBlocksEndianess(VDIECONV_H2F, paBlocks, getImageBlocks(&Hdr));
+ rc = vdIfIoIntFileWriteSync(pIfIo, pStorage, offStartBlocks, paBlocks,
+ getImageBlocks(&Hdr) * sizeof(VDIIMAGEBLOCKPOINTER));
+ if (RT_FAILURE(rc))
+ {
+ rc = vdIfError(pIfError, VERR_VD_IMAGE_REPAIR_IMPOSSIBLE, RT_SRC_POS,
+ "Could not write repaired block allocation table (at %llu), %Rrc",
+ offStartBlocks, rc);
+ break;
+ }
+ }
+ }
+
+ vdIfErrorMessage(pIfError, "Corrupted VDI image repaired successfully\n");
+ } while(0);
+
+ if (paBlocks)
+ RTMemFree(paBlocks);
+
+ if (pu32BlockBitmap)
+ RTMemFree(pu32BlockBitmap);
+
+ if (pStorage)
+ {
+ int rc2 = vdIfIoIntFileClose(pIfIo, pStorage);
+ if (RT_SUCCESS(rc))
+ rc = rc2; /* Propagate error code only if repairing was successful. */
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+const VDIMAGEBACKEND g_VDIBackend =
+{
+ /* u32Version */
+ VD_IMGBACKEND_VERSION,
+ /* pszBackendName */
+ "VDI",
+ /* uBackendCaps */
+ VD_CAP_UUID | VD_CAP_CREATE_FIXED | VD_CAP_CREATE_DYNAMIC
+ | VD_CAP_DIFF | VD_CAP_FILE | VD_CAP_ASYNC | VD_CAP_VFS | VD_CAP_DISCARD
+ | VD_CAP_PREFERRED,
+ /* paFileExtensions */
+ s_aVdiFileExtensions,
+ /* paConfigInfo */
+ vdiConfigInfo,
+ /* pfnProbe */
+ vdiProbe,
+ /* pfnOpen */
+ vdiOpen,
+ /* pfnCreate */
+ vdiCreate,
+ /* pfnRename */
+ vdiRename,
+ /* pfnClose */
+ vdiClose,
+ /* pfnRead */
+ vdiRead,
+ /* pfnWrite */
+ vdiWrite,
+ /* pfnFlush */
+ vdiFlush,
+ /* pfnDiscard */
+ vdiDiscard,
+ /* pfnGetVersion */
+ vdiGetVersion,
+ /* pfnGetFileSize */
+ vdiGetFileSize,
+ /* pfnGetPCHSGeometry */
+ vdiGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ vdiSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ vdiGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ vdiSetLCHSGeometry,
+ /* pfnQueryRegions */
+ vdiQueryRegions,
+ /* pfnRegionListRelease */
+ vdiRegionListRelease,
+ /* pfnGetImageFlags */
+ vdiGetImageFlags,
+ /* pfnGetOpenFlags */
+ vdiGetOpenFlags,
+ /* pfnSetOpenFlags */
+ vdiSetOpenFlags,
+ /* pfnGetComment */
+ vdiGetComment,
+ /* pfnSetComment */
+ vdiSetComment,
+ /* pfnGetUuid */
+ vdiGetUuid,
+ /* pfnSetUuid */
+ vdiSetUuid,
+ /* pfnGetModificationUuid */
+ vdiGetModificationUuid,
+ /* pfnSetModificationUuid */
+ vdiSetModificationUuid,
+ /* pfnGetParentUuid */
+ vdiGetParentUuid,
+ /* pfnSetParentUuid */
+ vdiSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ vdiGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ vdiSetParentModificationUuid,
+ /* pfnDump */
+ vdiDump,
+ /* pfnGetTimestamp */
+ NULL,
+ /* pfnGetParentTimestamp */
+ NULL,
+ /* pfnSetParentTimestamp */
+ NULL,
+ /* pfnGetParentFilename */
+ NULL,
+ /* pfnSetParentFilename */
+ NULL,
+ /* pfnComposeLocation */
+ genericFileComposeLocation,
+ /* pfnComposeName */
+ genericFileComposeName,
+ /* pfnCompact */
+ vdiCompact,
+ /* pfnResize */
+ vdiResize,
+ /* pfnRepair */
+ vdiRepair,
+ /* pfnTraverseMetadata */
+ NULL,
+ /* u32VersionEnd */
+ VD_IMGBACKEND_VERSION
+};