Index: trunk/src/VBox/Devices/Builtins.cpp
===================================================================
--- trunk/src/VBox/Devices/Builtins.cpp	(revision 2335)
+++ trunk/src/VBox/Devices/Builtins.cpp	(revision 2358)
@@ -150,5 +150,5 @@
     if (VBOX_FAILURE(rc))
         return rc;
-    rc = pCallbacks->pfnRegister(pCallbacks, &g_DrvVmdkHDD);
+    rc = pCallbacks->pfnRegister(pCallbacks, &g_DrvVD);
     if (VBOX_FAILURE(rc))
         return rc;
Index: trunk/src/VBox/Devices/Builtins.h
===================================================================
--- trunk/src/VBox/Devices/Builtins.h	(revision 2335)
+++ trunk/src/VBox/Devices/Builtins.h	(revision 2358)
@@ -60,5 +60,5 @@
 extern const PDMDRVREG g_DrvBlock;
 extern const PDMDRVREG g_DrvVBoxHDD;
-extern const PDMDRVREG g_DrvVmdkHDD;
+extern const PDMDRVREG g_DrvVD;
 extern const PDMDRVREG g_DrvHostDVD;
 extern const PDMDRVREG g_DrvHostFloppy;
Index: trunk/src/VBox/Devices/Makefile
===================================================================
--- trunk/src/VBox/Devices/Makefile	(revision 2335)
+++ trunk/src/VBox/Devices/Makefile	(revision 2358)
@@ -76,6 +76,7 @@
 endif
 VBoxDDU_SOURCES          = \
-	Storage/VDICore.cpp
-#	Storage/VmdkHDDCore.cpp
+	Storage/VBoxHDD-new.cpp \
+	Storage/VDICore.cpp \
+	Storage/VmdkHDDCore.cpp
 VBoxDDU_LIBS             = \
 	$(LIB_RUNTIME)
@@ -380,6 +381,6 @@
 	Storage/DrvMediaISO.cpp \
 	Storage/DrvRawImage.cpp \
-	Storage/VBoxHDD.cpp \
-	Storage/VmdkHDD.cpp
+	Storage/DrvVD.cpp \
+	Storage/VBoxHDD.cpp
 
 # -- OS specific --
@@ -408,9 +409,9 @@
 	Storage/VBoxHDD.cpp \
 	Storage/DrvBlock.cpp \
-	Storage/VmdkHDD.cpp \
 	Storage/DrvMediaISO.cpp \
 	Storage/DrvRawImage.cpp \
 	Storage/DrvHostBase.cpp \
 	Storage/DrvHostDVD.cpp \
+	Storage/DrvVD.cpp \
 	PC/DrvACPI.cpp \
 	Network/DrvIntNet.cpp \
Index: trunk/src/VBox/Devices/Storage/DrvVD.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/DrvVD.cpp	(revision 2358)
+++ trunk/src/VBox/Devices/Storage/DrvVD.cpp	(revision 2358)
@@ -0,0 +1,521 @@
+/** $Id$ */
+/** @file
+ *
+ * VBox storage devices:
+ * Media implementation for VBox disk container
+ */
+
+/*
+ * Copyright (C) 2006 InnoTek Systemberatung GmbH
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file 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 2 as it comes in the "COPYING" file of the VirtualBox OSE
+ * distribution. VirtualBox OSE is distributed in the hope that it will
+ * be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * If you received this file as part of a commercial VirtualBox
+ * distribution, then only the terms of your commercial VirtualBox
+ * license agreement apply instead of the previous paragraph.
+ */
+
+
+/*******************************************************************************
+*   Header files                                                               *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_VD
+#include <VBox/VBoxHDD-new.h>
+#include <VBox/pdm.h>
+#include <VBox/mm.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+
+#include "Builtins.h"
+
+
+/*******************************************************************************
+*   Defined types, constants and macros                                        *
+*******************************************************************************/
+
+/** Converts a pointer to VDIDISK::IMedia to a PVBOXDISK. */
+#define PDMIMEDIA_2_VBOXDISK(pInterface) \
+    ( (PVBOXDISK)((uintptr_t)pInterface - RT_OFFSETOF(VBOXDISK, IMedia)) )
+
+/** Converts a pointer to PDMDRVINS::IBase to a PPDMDRVINS. */
+#define PDMIBASE_2_DRVINS(pInterface) \
+    ( (PPDMDRVINS)((uintptr_t)pInterface - RT_OFFSETOF(PDMDRVINS, IBase)) )
+
+/** Converts a pointer to PDMDRVINS::IBase to a PVBOXDISK. */
+#define PDMIBASE_2_VBOXDISK(pInterface) \
+    ( PDMINS2DATA(PDMIBASE_2_DRVINS(pInterface), PVBOXDISK) )
+
+
+/**
+ * VBox disk container media main structure, private part.
+ */
+typedef struct VBOXDISK
+{
+    /** The VBox disk container. */
+    PVBOXHDD        pDisk;
+    /** The media interface. */
+    PDMIMEDIA       IMedia;
+    /** Pointer to the driver instance. */
+    PPDMDRVINS      pDrvIns;
+    /** Name of the image format backend. */
+    char            szFormat[16];
+    /** Flag whether suspend has changed image open mode to read only. */
+    bool            fTempReadOnly;
+} VBOXDISK, *PVBOXDISK;
+
+/*******************************************************************************
+*   Error reporting callback                                                   *
+*******************************************************************************/
+
+static void vdErrorCallback(void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list va)
+{
+    PPDMDRVINS pDrvIns = (PPDMDRVINS)pvUser;
+    pDrvIns->pDrvHlp->pfnVMSetErrorV(pDrvIns, rc, RT_SRC_POS_ARGS, pszFormat, va);
+}
+
+/*******************************************************************************
+*   Media interface methods                                                    *
+*******************************************************************************/
+
+/** @copydoc PDMIMEDIA::pfnRead */
+static DECLCALLBACK(int) vdRead(PPDMIMEDIA pInterface,
+                                uint64_t off, void *pvBuf, size_t cbRead)
+{
+    LogFlow(("%s: off=%#llx pvBuf=%p cbRead=%d\n", __FUNCTION__,
+             off, pvBuf, cbRead));
+    PVBOXDISK pData = PDMIMEDIA_2_VBOXDISK(pInterface);
+    int rc = VDRead(pData->pDisk, off, pvBuf, cbRead);
+    if (VBOX_SUCCESS(rc))
+        Log2(("%s: off=%#llx pvBuf=%p cbRead=%d %.*Vhxd\n", __FUNCTION__,
+              off, pvBuf, cbRead, cbRead, pvBuf));
+    LogFlow(("%s: returns %Vrc\n", __FUNCTION__, rc));
+    return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnWrite */
+static DECLCALLBACK(int) vdWrite(PPDMIMEDIA pInterface,
+                                 uint64_t off, const void *pvBuf,
+                                 size_t cbWrite)
+{
+    LogFlow(("%s: off=%#llx pvBuf=%p cbWrite=%d\n", __FUNCTION__,
+             off, pvBuf, cbWrite));
+    PVBOXDISK pData = PDMIMEDIA_2_VBOXDISK(pInterface);
+    Log2(("%s: off=%#llx pvBuf=%p cbWrite=%d %.*Vhxd\n", __FUNCTION__,
+          off, pvBuf, cbWrite, cbWrite, pvBuf));
+    int rc = VDWrite(pData->pDisk, off, pvBuf, cbWrite);
+    LogFlow(("%s: returns %Vrc\n", __FUNCTION__, rc));
+    return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnFlush */
+static DECLCALLBACK(int) vdFlush(PPDMIMEDIA pInterface)
+{
+    LogFlow(("%s:\n", __FUNCTION__));
+    PVBOXDISK pData = PDMIMEDIA_2_VBOXDISK(pInterface);
+    int rc = VDFlush(pData->pDisk);
+    LogFlow(("%s: returns %Vrc\n", __FUNCTION__, rc));
+    return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnGetSize */
+static DECLCALLBACK(uint64_t) vdGetSize(PPDMIMEDIA pInterface)
+{
+    LogFlow(("%s:\n", __FUNCTION__));
+    PVBOXDISK pData = PDMIMEDIA_2_VBOXDISK(pInterface);
+    uint64_t cb = VDGetSize(pData->pDisk);
+    LogFlow(("%s: returns %#llx (%llu)\n", __FUNCTION__, cb, cb));
+    return cb;
+}
+
+/** @copydoc PDMIMEDIA::pfnIsReadOnly */
+static DECLCALLBACK(bool) vdIsReadOnly(PPDMIMEDIA pInterface)
+{
+    LogFlow(("%s:\n", __FUNCTION__));
+    PVBOXDISK pData = PDMIMEDIA_2_VBOXDISK(pInterface);
+    bool f = VDIsReadOnly(pData->pDisk);
+    LogFlow(("%s: returns %d\n", __FUNCTION__, f));
+    return f;
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosGetGeometry */
+static DECLCALLBACK(int) vdBiosGetGeometry(PPDMIMEDIA pInterface,
+                                           uint32_t *pcCylinders,
+                                           uint32_t *pcHeads,
+                                           uint32_t *pcSectors)
+{
+    LogFlow(("%s:\n", __FUNCTION__));
+    PVBOXDISK pData = PDMIMEDIA_2_VBOXDISK(pInterface);
+    int rc = VDGetGeometry(pData->pDisk, pcCylinders, pcHeads, pcSectors);
+    if (VBOX_FAILURE(rc))
+    {
+        Log(("%s: geometry not available.\n", __FUNCTION__));
+        rc = VERR_PDM_GEOMETRY_NOT_SET;
+    }
+    LogFlow(("%s: returns %Vrc (CHS=%d/%d/%d)\n", __FUNCTION__,
+             rc, *pcCylinders, *pcHeads, *pcSectors));
+    return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosSetGeometry */
+static DECLCALLBACK(int) vdBiosSetGeometry(PPDMIMEDIA pInterface,
+                                           uint32_t cCylinders,
+                                           uint32_t cHeads,
+                                           uint32_t cSectors)
+{
+    LogFlow(("%s: CHS=%d/%d/%d\n", __FUNCTION__,
+             cCylinders, cHeads, cSectors));
+    PVBOXDISK pData = PDMIMEDIA_2_VBOXDISK(pInterface);
+    int rc = VDSetGeometry(pData->pDisk, cCylinders, cHeads, cSectors);
+    LogFlow(("%s: returns %Vrc\n", __FUNCTION__, rc));
+    return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosGetTranslation */
+static DECLCALLBACK(int) vdBiosGetTranslation(PPDMIMEDIA pInterface,
+                                              PPDMBIOSTRANSLATION penmTranslation)
+{
+    LogFlow(("%s:\n", __FUNCTION__));
+    PVBOXDISK pData = PDMIMEDIA_2_VBOXDISK(pInterface);
+    int rc = VDGetTranslation(pData->pDisk, penmTranslation);
+    LogFlow(("%s: returns %Vrc (%d)\n", __FUNCTION__, rc, *penmTranslation));
+    return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosSetTranslation */
+static DECLCALLBACK(int) vdBiosSetTranslation(PPDMIMEDIA pInterface,
+                                              PDMBIOSTRANSLATION enmTranslation)
+{
+    LogFlow(("%s:\n", __FUNCTION__));
+    PVBOXDISK pData = PDMIMEDIA_2_VBOXDISK(pInterface);
+    int rc = VDSetTranslation(pData->pDisk, enmTranslation);
+    LogFlow(("%s: returns %Vrc (%d)\n", __FUNCTION__, rc, enmTranslation));
+    return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnGetUuid */
+static DECLCALLBACK(int) vdGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid)
+{
+    LogFlow(("%s:\n", __FUNCTION__));
+    PVBOXDISK pData = PDMIMEDIA_2_VBOXDISK(pInterface);
+    int rc = VDGetUuid(pData->pDisk, 0, pUuid);
+    LogFlow(("%s: returns %Vrc ({%Vuuid})\n", __FUNCTION__, rc, pUuid));
+    return rc;
+}
+
+
+/*******************************************************************************
+*   Base interface methods                                                     *
+*******************************************************************************/
+
+/** @copydoc PDMIBASE::pfnQueryInterface */
+static DECLCALLBACK(void *) vdQueryInterface(PPDMIBASE pInterface,
+                                             PDMINTERFACE enmInterface)
+{
+    PPDMDRVINS pDrvIns = PDMIBASE_2_DRVINS(pInterface);
+    PVBOXDISK pData = PDMINS2DATA(pDrvIns, PVBOXDISK);
+    switch (enmInterface)
+    {
+        case PDMINTERFACE_BASE:
+            return &pDrvIns->IBase;
+        case PDMINTERFACE_MEDIA:
+            return &pData->IMedia;
+        default:
+            return NULL;
+    }
+}
+
+
+/*******************************************************************************
+*   Driver methods                                                             *
+*******************************************************************************/
+
+
+/**
+ * Construct a VBox disk media driver instance.
+ *
+ * @returns VBox status.
+ * @param   pDrvIns     The driver instance data.
+ *                      If the registration structure is needed, pDrvIns->pDrvReg points to it.
+ * @param   pCfgHandle  Configuration node handle for the driver. Use this to obtain the configuration
+ *                      of the driver instance. It's also found in pDrvIns->pCfgHandle as it's expected
+ *                      to be used frequently in this function.
+ */
+static DECLCALLBACK(int) vdConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
+{
+    LogFlow(("%s:\n", __FUNCTION__));
+    PVBOXDISK pData = PDMINS2DATA(pDrvIns, PVBOXDISK);
+    int rc = VINF_SUCCESS;
+    char *pszName;          /**< The path of the disk image file. */
+    bool fReadOnly;         /**< True if the media is readonly. */
+    bool fHonorZeroWrites;  /**< True if zero blocks should be written. */
+
+    /*
+     * Init the static parts.
+     */
+    pDrvIns->IBase.pfnQueryInterface    = vdQueryInterface;
+    pData->pDrvIns = pDrvIns;
+    pData->fTempReadOnly = false;
+
+    /* IMedia */
+    pData->IMedia.pfnRead               = vdRead;
+    pData->IMedia.pfnWrite              = vdWrite;
+    pData->IMedia.pfnFlush              = vdFlush;
+    pData->IMedia.pfnGetSize            = vdGetSize;
+    pData->IMedia.pfnIsReadOnly         = vdIsReadOnly;
+    pData->IMedia.pfnBiosGetGeometry    = vdBiosGetGeometry;
+    pData->IMedia.pfnBiosSetGeometry    = vdBiosSetGeometry;
+    pData->IMedia.pfnBiosGetTranslation = vdBiosGetTranslation;
+    pData->IMedia.pfnBiosSetTranslation = vdBiosSetTranslation;
+    pData->IMedia.pfnGetUuid            = vdGetUuid;
+
+    /*
+     * Validate configuration and find all parent images.
+     * It's sort of up side down from the image dependency tree.
+     */
+    unsigned    iLevel = 0;
+    PCFGMNODE   pCurNode = pCfgHandle;
+    for (;;)
+    {
+        bool fValid;
+
+        if (pCurNode == pCfgHandle)
+        {
+            /* Toplevel configuration contains the format backend name and
+             * full image open information. */
+            fValid = CFGMR3AreValuesValid(pCurNode,
+                                          "Format\0"
+                                          "Path\0ReadOnly\0HonorZeroWrites\0");
+        }
+        else
+        {
+            /* All other image configurations only contain image name. */
+            fValid = CFGMR3AreValuesValid(pCurNode, "Path\0");
+        }
+        if (!fValid)
+        {
+            rc = PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES,
+                                     RT_SRC_POS, N_("DrvVD: Configuration error: keys incorrect at level %d"), iLevel);
+            break;
+        }
+
+        PCFGMNODE pParent = CFGMR3GetChild(pCurNode, "Parent");
+        if (!pParent)
+            break;
+        pCurNode = pParent;
+        iLevel++;
+    }
+
+    /*
+     * Open the images.
+     */
+    if (VBOX_SUCCESS(rc))
+    {
+        rc = CFGMR3QueryString(pCfgHandle, "Format", &pData->szFormat[0],
+                               sizeof(pData->szFormat));
+        if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+        {
+            /* Default disk image format is VMDK. */
+            rc = VINF_SUCCESS;
+            strncpy(&pData->szFormat[0], "VMDK", sizeof(pData->szFormat));
+            pData->szFormat[sizeof(pData->szFormat) - 1] = '\0';
+        }
+        if (VBOX_SUCCESS(rc))
+        {
+            rc = VDCreate(pData->szFormat, vdErrorCallback, pDrvIns, &pData->pDisk);
+            /* Error message is already set correctly. */
+        }
+        else
+            rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+                                  N_("DrvVD: Configuration error: Querying \"Format\" as string failed"));
+    }
+
+    while (pCurNode && VBOX_SUCCESS(rc))
+    {
+        /*
+         * Read the image configuration.
+         */
+        rc = CFGMR3QueryStringAlloc(pCurNode, "Path", &pszName);
+        if (VBOX_FAILURE(rc))
+        {
+            VDDestroy(pData->pDisk);
+            rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+                                  N_("DrvVD: Configuration error: Querying \"Path\" as string failed"));
+            break;
+        }
+
+        if (iLevel == 0)
+        {
+            rc = CFGMR3QueryBool(pCurNode, "ReadOnly", &fReadOnly);
+            if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+                fReadOnly = false;
+            else if (VBOX_FAILURE(rc))
+            {
+                MMR3HeapFree(pszName);
+                VDDestroy(pData->pDisk);
+                rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+                                      N_("DrvVD: Configuration error: Querying \"ReadOnly\" as boolean failed"));
+                break;
+            }
+
+            rc = CFGMR3QueryBool(pCfgHandle, "HonorZeroWrites", &fHonorZeroWrites);
+            if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+                fHonorZeroWrites = false;
+            else if (VBOX_FAILURE(rc))
+            {
+                MMR3HeapFree(pszName);
+                VDDestroy(pData->pDisk);
+                rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+                                      N_("DrvVD: Configuration error: Querying \"HonorZeroWrites\" as boolean failed"));
+                break;
+            }
+        }
+        else
+        {
+            fReadOnly = true;
+            fHonorZeroWrites = false;
+        }
+
+        /*
+         * Open the image.
+         */
+        unsigned uOpenFlags;
+        if (fReadOnly)
+            uOpenFlags = VD_OPEN_FLAGS_READONLY;
+        else
+            uOpenFlags = VD_OPEN_FLAGS_NORMAL;
+        if (fHonorZeroWrites)
+            uOpenFlags |= VD_OPEN_FLAGS_HONOR_ZEROES;
+        rc = VDOpen(pData->pDisk, pszName, uOpenFlags);
+        if (VBOX_SUCCESS(rc))
+            Log(("%s: %d - Opened '%s' in %s mode\n", __FUNCTION__,
+                 iLevel, pszName,
+                 VDIsReadOnly(pData->pDisk) ? "read-only" : "read-write"));
+        else
+        {
+            AssertMsgFailed(("Failed to open image '%s' rc=%Vrc\n", pszName, rc));
+            VDDestroy(pData->pDisk);
+            break;
+        }
+        MMR3HeapFree(pszName);
+
+        /* next */
+        iLevel--;
+        pCurNode = CFGMR3GetParent(pCurNode);
+    }
+
+    LogFlow(("%s: returns %Vrc\n", __FUNCTION__, rc));
+    return rc;
+}
+
+/**
+ * Destruct a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param   pDrvIns     The driver instance data.
+ */
+static DECLCALLBACK(void) vdDestruct(PPDMDRVINS pDrvIns)
+{
+    LogFlow(("%s:\n", __FUNCTION__));
+    PVBOXDISK pData = PDMINS2DATA(pDrvIns, PVBOXDISK);
+    int rc = VDCloseAll(pData->pDisk);
+    AssertRC(rc);
+}
+
+
+/**
+ * When the VM has been suspended we'll change the image mode to read-only
+ * so that main and others can read the VDIs. This is important when
+ * saving state and so forth.
+ *
+ * @param   pDrvIns     The driver instance data.
+ */
+static DECLCALLBACK(void) vdSuspend(PPDMDRVINS pDrvIns)
+{
+    LogFlow(("%s:\n", __FUNCTION__));
+    PVBOXDISK pData = PDMINS2DATA(pDrvIns, PVBOXDISK);
+    if (!VDIsReadOnly(pData->pDisk))
+    {
+        unsigned uOpenFlags;
+        int rc = VDGetOpenFlags(pData->pDisk, &uOpenFlags);
+        AssertRC(rc);
+        uOpenFlags |= VD_OPEN_FLAGS_READONLY;
+        rc = VDSetOpenFlags(pData->pDisk, uOpenFlags);
+        AssertRC(rc);
+        pData->fTempReadOnly = true;
+    }
+}
+
+/**
+ * Before the VM resumes we'll have to undo the read-only mode change
+ * done in vdSuspend.
+ *
+ * @param   pDrvIns     The driver instance data.
+ */
+static DECLCALLBACK(void) vdResume(PPDMDRVINS pDrvIns)
+{
+    LogFlow(("%s:\n", __FUNCTION__));
+    PVBOXDISK pData = PDMINS2DATA(pDrvIns, PVBOXDISK);
+    if (pData->fTempReadOnly)
+    {
+        unsigned uOpenFlags;
+        int rc = VDGetOpenFlags(pData->pDisk, &uOpenFlags);
+        AssertRC(rc);
+        uOpenFlags &= ~VD_OPEN_FLAGS_READONLY;
+        rc = VDSetOpenFlags(pData->pDisk, uOpenFlags);
+        AssertRC(rc);
+        pData->fTempReadOnly = false;
+    }
+}
+
+
+
+/**
+ * VBox disk container media driver registration record.
+ */
+const PDMDRVREG g_DrvVD =
+{
+    /* u32Version */
+    PDM_DRVREG_VERSION,
+    /* szDriverName */
+    "VD",
+    /* pszDescription */
+    "Generic VBox disk media driver.",
+    /* fFlags */
+    PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+    /* fClass. */
+    PDM_DRVREG_CLASS_MEDIA,
+    /* cMaxInstances */
+    ~0,
+    /* cbInstance */
+    sizeof(VBOXDISK),
+    /* pfnConstruct */
+    vdConstruct,
+    /* pfnDestruct */
+    vdDestruct,
+    /* pfnIOCtl */
+    NULL,
+    /* pfnPowerOn */
+    NULL,
+    /* pfnReset */
+    NULL,
+    /* pfnSuspend */
+    vdSuspend,
+    /* pfnResume */
+    vdResume,
+    /* pfnDetach */
+    NULL
+};
Index: trunk/src/VBox/Devices/Storage/VBoxHDD.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/VBoxHDD.cpp	(revision 2335)
+++ trunk/src/VBox/Devices/Storage/VBoxHDD.cpp	(revision 2358)
@@ -1,2 +1,3 @@
+/** $Id$ */
 /** @file
  *
@@ -36,5 +37,4 @@
 #include <iprt/file.h>
 #include <iprt/string.h>
-#include <iprt/asm.h>
 
 #include "VDICore.h"
Index: trunk/src/VBox/Devices/Storage/VDICore.h
===================================================================
--- trunk/src/VBox/Devices/Storage/VDICore.h	(revision 2335)
+++ trunk/src/VBox/Devices/Storage/VDICore.h	(revision 2358)
@@ -43,4 +43,10 @@
 *   Constants And Macros, Structures and Typedefs                              *
 *******************************************************************************/
+
+/** Image info, not handled anyhow.
+ *  Must be less than 64 bytes in length, including the trailing 0.
+ */
+#define VDI_IMAGE_FILE_INFO   "<<< InnoTek VirtualBox Disk Image >>>\n"
+
 /** The Sector size.
  * Currently we support only 512 bytes sectors.
@@ -527,3 +533,3 @@
 __END_DECLS
 
-#endif 
+#endif
Index: trunk/src/VBox/Devices/Storage/VmdkHDD.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/VmdkHDD.cpp	(revision 2335)
+++ 	(revision )
@@ -1,1024 +1,0 @@
-/** @file
- *
- * VBox storage devices:
- * VBox VMDK container implementation
- */
-
-/*
- * Copyright (C) 2006 InnoTek Systemberatung GmbH
- *
- * This file is part of VirtualBox Open Source Edition (OSE), as
- * available from http://www.virtualbox.org. This file 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 2 as it comes in the "COPYING" file of the VirtualBox OSE
- * distribution. VirtualBox OSE is distributed in the hope that it will
- * be useful, but WITHOUT ANY WARRANTY of any kind.
- *
- * If you received this file as part of a commercial VirtualBox
- * distribution, then only the terms of your commercial VirtualBox
- * license agreement apply instead of the previous paragraph.
- *
- * --------------------------------------------------------------------
- *
- * This code is based on:
- *
- * Block driver for the VMDK format
- *
- * Copyright (c) 2004 Fabrice Bellard
- * Copyright (c) 2005 Filip Navara
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-/*******************************************************************************
-*   Header Files                                                               *
-*******************************************************************************/
-#define LOG_GROUP LOG_GROUP_DRV_VBOXHDD
-#include <VBox/pdm.h>
-#include <VBox/mm.h>
-#include <VBox/err.h>
-
-#include <VBox/log.h>
-#include <iprt/alloc.h>
-#include <iprt/assert.h>
-#include <iprt/uuid.h>
-#include <iprt/file.h>
-#include <iprt/string.h>
-
-#include "vl_vbox.h"
-#include "Builtins.h"
-
-/*******************************************************************************
-*   Constants And Macros, Structures and Typedefs                              *
-*******************************************************************************/
-
-/** The Sector size.
- * Currently we support only 512 bytes sectors.
- */
-#define VMDK_GEOMETRY_SECTOR_SIZE    (512)
-/**  512 = 2^^9 */
-#define VMDK_GEOMETRY_SECTOR_SHIFT   (9)
-
-#define VMDK3_MAGIC (('C' << 24) | ('O' << 16) | ('W' << 8) | 'D')
-#define VMDK4_MAGIC (('K' << 24) | ('D' << 16) | ('M' << 8) | 'V')
-
-#pragma pack(1)
-typedef struct {
-    uint32_t version;
-    uint32_t flags;
-    uint32_t disk_sectors;
-    uint32_t granularity;
-    uint32_t l1dir_offset;
-    uint32_t l1dir_size;
-    uint32_t file_sectors;
-    uint32_t cylinders;
-    uint32_t heads;
-    uint32_t sectors_per_track;
-} VMDK3Header;
-#pragma pack()
-
-#pragma pack(1)
-typedef struct {
-    uint32_t version;
-    uint32_t flags;
-    int64_t capacity;
-    int64_t granularity;
-    int64_t desc_offset;
-    int64_t desc_size;
-    int32_t num_gtes_per_gte;
-    int64_t rgd_offset;
-    int64_t gd_offset;
-    int64_t grain_offset;
-    char filler[1];
-    char check_bytes[4];
-} VMDK4Header;
-#pragma pack()
-
-#define L2_CACHE_SIZE 16
-
-typedef struct BDRVVmdkState {
-    /** File handle. */
-    RTFILE                  File;
-
-    bool                    fReadOnly;
-
-    uint64_t                total_sectors;
-
-    int64_t l1_table_offset;
-    int64_t l1_backup_table_offset;
-    uint32_t *l1_table;
-    uint32_t *l1_backup_table;
-    unsigned int l1_size;
-    uint32_t l1_entry_sectors;
-
-    unsigned int l2_size;
-    uint32_t *l2_cache;
-    uint32_t l2_cache_offsets[L2_CACHE_SIZE];
-    uint32_t l2_cache_counts[L2_CACHE_SIZE];
-
-    unsigned int cluster_sectors;
-} BDRVVmdkState;
-
-
-#define VMDKDISK_SIGNATURE          0x8013ABCD
-
-
-/**
- * Harddisk geometry.
- */
-#pragma pack(1)
-typedef struct VMDKDISKGEOMETRY
-{
-    /** Cylinders. */
-    uint32_t    cCylinders;
-    /** Heads. */
-    uint32_t    cHeads;
-    /** Sectors per track. */
-    uint32_t    cSectors;
-    /** Sector size. (bytes per sector) */
-    uint32_t    cbSector;
-} VMDKDISKGEOMETRY, *PVMDKDISKGEOMETRY;
-#pragma pack()
-
-/**
- * VMDK HDD Container main structure, private part.
- */
-typedef struct VMDKDISK
-{
-    uint32_t        u32Signature;
-
-    BDRVVmdkState   VmdkState;
-
-    /** Hard disk geometry. */
-    VMDKDISKGEOMETRY Geometry;
-
-    /** The media interface. */
-    PDMIMEDIA       IMedia;
-    /** Pointer to the driver instance. */
-    PPDMDRVINS      pDrvIns;
-} VMDKDISK, *PVMDKDISK;
-
-
-/** Converts a pointer to VDIDISK::IMedia to a PVMDKDISK. */
-#define PDMIMEDIA_2_VMDKDISK(pInterface) ( (PVMDKDISK)((uintptr_t)pInterface - RT_OFFSETOF(VMDKDISK, IMedia)) )
-
-/** Converts a pointer to PDMDRVINS::IBase to a PPDMDRVINS. */
-#define PDMIBASE_2_DRVINS(pInterface)   ( (PPDMDRVINS)((uintptr_t)pInterface - RT_OFFSETOF(PDMDRVINS, IBase)) )
-
-/** Converts a pointer to PDMDRVINS::IBase to a PVMDKDISK. */
-#define PDMIBASE_2_VMDKDISK(pInterface)  ( PDMINS2DATA(PDMIBASE_2_DRVINS(pInterface), PVMDKDISK) )
-
-
-/*******************************************************************************
-*   Internal Functions                                                         *
-*******************************************************************************/
-static DECLCALLBACK(int)  vmdkConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle);
-static DECLCALLBACK(void) vmdkDestruct(PPDMDRVINS pDrvIns);
-static DECLCALLBACK(int)  vmdkRead(PPDMIMEDIA pInterface,
-                                  uint64_t off, void *pvBuf, size_t cbRead);
-static DECLCALLBACK(int)  vmdkWrite(PPDMIMEDIA pInterface,
-                                   uint64_t off, const void *pvBuf, size_t cbWrite);
-static DECLCALLBACK(int)  vmdkFlush(PPDMIMEDIA pInterface);
-static DECLCALLBACK(uint64_t) vmdkGetSize(PPDMIMEDIA pInterface);
-static DECLCALLBACK(int)  vmdkBiosGetGeometry(PPDMIMEDIA pInterface, uint32_t *pcCylinders,
-                                             uint32_t *pcHeads, uint32_t *pcSectors);
-static DECLCALLBACK(int)  vmdkBiosSetGeometry(PPDMIMEDIA pInterface, uint32_t cCylinders,
-                                             uint32_t cHeads, uint32_t cSectors);
-static DECLCALLBACK(int)  vmdkGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid);
-static DECLCALLBACK(bool) vmdkIsReadOnly(PPDMIMEDIA pInterface);
-static DECLCALLBACK(int)  vmdkBiosGetTranslation(PPDMIMEDIA pInterface,
-                                                PPDMBIOSTRANSLATION penmTranslation);
-static DECLCALLBACK(int)  vmdkBiosSetTranslation(PPDMIMEDIA pInterface,
-                                                PDMBIOSTRANSLATION enmTranslation);
-static DECLCALLBACK(void *) vmdkQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface);
-
-#if 0
-static int vmdk_probe(const uint8_t *buf, int buf_size, const char *filename)
-{
-    uint32_t magic;
-
-    if (buf_size < 4)
-        return 0;
-    magic = be32_to_cpu(*(uint32_t *)buf);
-    if (magic == VMDK3_MAGIC ||
-        magic == VMDK4_MAGIC)
-        return 100;
-    else
-        return 0;
-}
-#endif
-
-static int vmdk_open(PVMDKDISK pDisk, const char *filename, bool fReadOnly)
-{
-    uint32_t magic, i;
-    int l1_size;
-
-    BDRVVmdkState *s = &pDisk->VmdkState;
-
-    /*
-     * Open the image.
-     */
-    s->fReadOnly = fReadOnly;
-    int rc = RTFileOpen(&s->File,
-                        filename,
-                        fReadOnly
-                        ? RTFILE_O_READ      | RTFILE_O_OPEN | RTFILE_O_DENY_NONE
-                        : RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
-    if (VBOX_FAILURE(rc))
-    {
-        if (!fReadOnly)
-        {
-            /* Try to open image for reading only. */
-            rc = RTFileOpen(&s->File,
-                            filename,
-                            RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
-            if (VBOX_SUCCESS(rc))
-                s->fReadOnly = true;
-        }
-        if (VBOX_FAILURE(rc))
-            return rc;
-    }
-    rc = RTFileRead(s->File, &magic, sizeof(magic), NULL);
-    AssertRC(rc);
-    if (VBOX_FAILURE(rc))
-        goto fail;
-
-    magic = be32_to_cpu(magic);
-    if (magic == VMDK3_MAGIC)
-    {
-        VMDK3Header header;
-        rc = RTFileRead(s->File, &header, sizeof(header), NULL);
-        AssertRC(rc);
-        if (VBOX_FAILURE(rc))
-            goto fail;
-        s->cluster_sectors = le32_to_cpu(header.granularity);
-        s->l2_size = 1 << 9;
-        s->l1_size = 1 << 6;
-        s->total_sectors = le32_to_cpu(header.disk_sectors);
-        s->l1_table_offset = le32_to_cpu(header.l1dir_offset) << 9;
-        s->l1_backup_table_offset = 0;
-        s->l1_entry_sectors = s->l2_size * s->cluster_sectors;
-
-        /* fill in the geometry structure */
-        pDisk->Geometry.cCylinders = le32_to_cpu(header.cylinders);
-        pDisk->Geometry.cHeads = le32_to_cpu(header.heads);
-        pDisk->Geometry.cSectors = le32_to_cpu(header.sectors_per_track);
-        pDisk->Geometry.cbSector = VMDK_GEOMETRY_SECTOR_SIZE;
-    }
-    else if (magic == VMDK4_MAGIC)
-    {
-        VMDK4Header header;
-
-        rc = RTFileRead(s->File, &header, sizeof(header), NULL);
-        AssertRC(rc);
-        if (VBOX_FAILURE(rc))
-            goto fail;
-        s->total_sectors = le64_to_cpu(header.capacity);
-        s->cluster_sectors = le64_to_cpu(header.granularity);
-        s->l2_size = le32_to_cpu(header.num_gtes_per_gte);
-        s->l1_entry_sectors = s->l2_size * s->cluster_sectors;
-        if (s->l1_entry_sectors <= 0)
-        {
-            rc = VERR_VDI_INVALID_HEADER;
-            goto fail;
-        }
-        s->l1_size = (s->total_sectors + s->l1_entry_sectors - 1)
-            / s->l1_entry_sectors;
-        s->l1_table_offset = le64_to_cpu(header.rgd_offset) << 9;
-        s->l1_backup_table_offset = le64_to_cpu(header.gd_offset) << 9;
-
-        /* fill in the geometry structure */
-        /// @todo we should read these properties from the DDB section
-        //  of the Disk DescriptorFile. So, the below values are just a
-        //  quick hack.
-        pDisk->Geometry.cCylinders = (le64_to_cpu(header.capacity) /
-                                      (16 * 63));
-        pDisk->Geometry.cHeads = 16;
-        pDisk->Geometry.cSectors = 63;
-        pDisk->Geometry.cbSector = VMDK_GEOMETRY_SECTOR_SIZE;
-    }
-    else
-    {
-        rc = VERR_VDI_INVALID_HEADER;
-        goto fail;
-    }
-    /* read the L1 table */
-    l1_size = s->l1_size * sizeof(uint32_t);
-    s->l1_table = (uint32_t *)RTMemAllocZ(l1_size);
-    if (!s->l1_table)
-    {
-        rc = VERR_NO_MEMORY;
-        goto fail;
-    }
-    rc = RTFileSeek(s->File, s->l1_table_offset, RTFILE_SEEK_BEGIN, NULL);
-    AssertRC(rc);
-    if (VBOX_FAILURE(rc))
-        goto fail;
-    rc = RTFileRead(s->File, s->l1_table, l1_size, NULL);
-    AssertRC(rc);
-    if (VBOX_FAILURE(rc))
-        goto fail;
-    for(i = 0; i < s->l1_size; i++) {
-        le32_to_cpus(&s->l1_table[i]);
-    }
-
-    if (s->l1_backup_table_offset) {
-        s->l1_backup_table = (uint32_t *)RTMemAllocZ(l1_size);
-        if (!s->l1_backup_table)
-        {
-            rc = VERR_NO_MEMORY;
-            goto fail;
-        }
-        rc = RTFileSeek(s->File, s->l1_backup_table_offset, RTFILE_SEEK_BEGIN, NULL);
-        AssertRC(rc);
-        if (VBOX_FAILURE(rc))
-            goto fail;
-        rc = RTFileRead(s->File, s->l1_backup_table, l1_size, NULL);
-        AssertRC(rc);
-        if (VBOX_FAILURE(rc))
-            goto fail;
-        for(i = 0; i < s->l1_size; i++) {
-            le32_to_cpus(&s->l1_backup_table[i]);
-        }
-    }
-
-    s->l2_cache = (uint32_t *)RTMemAllocZ(s->l2_size * L2_CACHE_SIZE * sizeof(uint32_t));
-    if (!s->l2_cache)
-    {
-        rc = VERR_NO_MEMORY;
-        goto fail;
-    }
-
-    return VINF_SUCCESS;
-
- fail:
-    Log(("vmdk_open failed with %Vrc\n", rc));
-    if (s->l1_backup_table)
-        RTMemFree(s->l1_backup_table);
-    if (s->l1_table)
-        RTMemFree(s->l1_table);
-    if (s->l2_cache)
-        RTMemFree(s->l2_cache);
-    RTFileClose(s->File);
-    return rc;
-}
-
-static uint64_t get_cluster_offset(BDRVVmdkState *s,
-                                   uint64_t offset, int allocate)
-{
-    unsigned int l1_index, l2_offset, l2_index;
-    int min_index, i, j;
-    uint32_t min_count, *l2_table, tmp;
-    uint64_t cluster_offset;
-    int rc;
-
-    l1_index = (offset >> 9) / s->l1_entry_sectors;
-    if (l1_index >= s->l1_size)
-        return 0;
-    l2_offset = s->l1_table[l1_index];
-    if (!l2_offset)
-        return 0;
-    for(i = 0; i < L2_CACHE_SIZE; i++) {
-        if (l2_offset == s->l2_cache_offsets[i]) {
-            /* increment the hit count */
-            if (++s->l2_cache_counts[i] == 0xffffffff) {
-                for(j = 0; j < L2_CACHE_SIZE; j++) {
-                    s->l2_cache_counts[j] >>= 1;
-                }
-            }
-            l2_table = s->l2_cache + (i * s->l2_size);
-            goto found;
-        }
-    }
-    /* not found: load a new entry in the least used one */
-    min_index = 0;
-    min_count = 0xffffffff;
-    for(i = 0; i < L2_CACHE_SIZE; i++) {
-        if (s->l2_cache_counts[i] < min_count) {
-            min_count = s->l2_cache_counts[i];
-            min_index = i;
-        }
-    }
-    l2_table = s->l2_cache + (min_index * s->l2_size);
-    rc = RTFileSeek(s->File, (int64_t)l2_offset * VMDK_GEOMETRY_SECTOR_SIZE, RTFILE_SEEK_BEGIN, NULL);
-    AssertRC(rc);
-    if (VBOX_FAILURE(rc))
-        return 0;
-    rc = RTFileRead(s->File, l2_table, s->l2_size * sizeof(uint32_t), NULL);
-    AssertRC(rc);
-    if (VBOX_FAILURE(rc))
-        return 0;
-    s->l2_cache_offsets[min_index] = l2_offset;
-    s->l2_cache_counts[min_index] = 1;
- found:
-    l2_index = ((offset >> 9) / s->cluster_sectors) % s->l2_size;
-    cluster_offset = le32_to_cpu(l2_table[l2_index]);
-    if (!cluster_offset) {
-        if (!allocate)
-            return 0;
-        rc = RTFileSeek(s->File, 0, RTFILE_SEEK_END, &cluster_offset);
-        AssertRC(rc);
-        if (VBOX_FAILURE(rc))
-            return 0;
-        rc = RTFileSetSize(s->File, cluster_offset + (s->cluster_sectors << 9));
-        AssertRC(rc);
-        if (VBOX_FAILURE(rc))
-            return 0;
-        cluster_offset >>= 9;
-        /* update L2 table */
-        tmp = cpu_to_le32(cluster_offset);
-        l2_table[l2_index] = tmp;
-        rc = RTFileSeek(s->File, ((int64_t)l2_offset * VMDK_GEOMETRY_SECTOR_SIZE) + (l2_index * sizeof(tmp)), RTFILE_SEEK_BEGIN, NULL);
-        AssertRC(rc);
-        if (VBOX_FAILURE(rc))
-            return 0;
-        rc = RTFileWrite(s->File, &tmp, sizeof(tmp), NULL);
-        AssertRC(rc);
-        if (VBOX_FAILURE(rc))
-            return 0;
-        /* update backup L2 table */
-        if (s->l1_backup_table_offset != 0) {
-            l2_offset = s->l1_backup_table[l1_index];
-
-            rc = RTFileSeek(s->File, ((int64_t)l2_offset * VMDK_GEOMETRY_SECTOR_SIZE) + (l2_index * sizeof(tmp)), RTFILE_SEEK_BEGIN, NULL);
-            AssertRC(rc);
-            if (VBOX_FAILURE(rc))
-                return 0;
-            rc = RTFileWrite(s->File, &tmp, sizeof(tmp), NULL);
-            AssertRC(rc);
-            if (VBOX_FAILURE(rc))
-                return 0;
-        }
-    }
-    cluster_offset <<= 9;
-    return cluster_offset;
-}
-
-#if 0
-static int vmdk_is_allocated(BDRVVmdkState *s, int64_t sector_num,
-                             int nb_sectors, int *pnum)
-{
-    int index_in_cluster, n;
-    uint64_t cluster_offset;
-
-    cluster_offset = get_cluster_offset(s, sector_num << 9, 0);
-    index_in_cluster = sector_num % s->cluster_sectors;
-    n = s->cluster_sectors - index_in_cluster;
-    if (n > nb_sectors)
-        n = nb_sectors;
-    *pnum = n;
-    return (cluster_offset != 0);
-}
-#endif
-
-static int vmdk_read(BDRVVmdkState *s, int64_t sector_num,
-                    uint8_t *buf, int nb_sectors)
-{
-    int index_in_cluster, n;
-    uint64_t cluster_offset;
-
-    while (nb_sectors > 0) {
-        cluster_offset = get_cluster_offset(s, sector_num << 9, 0);
-        index_in_cluster = sector_num % s->cluster_sectors;
-        n = s->cluster_sectors - index_in_cluster;
-        if (n > nb_sectors)
-            n = nb_sectors;
-        if (!cluster_offset) {
-            memset(buf, 0, VMDK_GEOMETRY_SECTOR_SIZE * n);
-        } else {
-            int rc = RTFileSeek(s->File, cluster_offset + index_in_cluster * VMDK_GEOMETRY_SECTOR_SIZE, RTFILE_SEEK_BEGIN, NULL);
-            AssertRC(rc);
-            if (VBOX_FAILURE(rc))
-                return rc;
-
-            rc = RTFileRead(s->File, buf, n * VMDK_GEOMETRY_SECTOR_SIZE, NULL);
-            AssertRC(rc);
-            if (VBOX_FAILURE(rc))
-                return rc;
-        }
-        nb_sectors -= n;
-        sector_num += n;
-        buf += n * VMDK_GEOMETRY_SECTOR_SIZE;
-    }
-    return VINF_SUCCESS;
-}
-
-static int vmdk_write(BDRVVmdkState *s, int64_t sector_num,
-                     const uint8_t *buf, int nb_sectors)
-{
-    int index_in_cluster, n;
-    uint64_t cluster_offset;
-
-    while (nb_sectors > 0) {
-        index_in_cluster = sector_num & (s->cluster_sectors - 1);
-        n = s->cluster_sectors - index_in_cluster;
-        if (n > nb_sectors)
-            n = nb_sectors;
-        cluster_offset = get_cluster_offset(s, sector_num << 9, 1);
-        if (!cluster_offset)
-            return VERR_IO_SECTOR_NOT_FOUND;
-
-        int rc = RTFileSeek(s->File, cluster_offset + index_in_cluster * VMDK_GEOMETRY_SECTOR_SIZE, RTFILE_SEEK_BEGIN, NULL);
-        AssertRC(rc);
-        if (VBOX_FAILURE(rc))
-            return rc;
-
-        rc = RTFileWrite(s->File, buf, n * VMDK_GEOMETRY_SECTOR_SIZE, NULL);
-        AssertRC(rc);
-        if (VBOX_FAILURE(rc))
-            return rc;
-
-        nb_sectors -= n;
-        sector_num += n;
-        buf += n * VMDK_GEOMETRY_SECTOR_SIZE;
-    }
-    return VINF_SUCCESS;
-}
-
-static void vmdk_close(BDRVVmdkState *s)
-{
-    RTMemFree(s->l1_table);
-    RTMemFree(s->l2_cache);
-    RTFileClose(s->File);
-}
-
-static void vmdk_flush(BDRVVmdkState *s)
-{
-    RTFileFlush(s->File);
-}
-
-
-/**
- * Get read/write mode of VMDK HDD.
- *
- * @returns Disk ReadOnly status.
- * @returns true if no one VMDK image is opened in HDD container.
- */
-IDER3DECL(bool) VMDKDiskIsReadOnly(PVMDKDISK pDisk)
-{
-    /* sanity check */
-    Assert(pDisk);
-    AssertMsg(pDisk->u32Signature == VMDKDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
-
-    LogFlow(("VmdkDiskIsReadOnly: returns %u\n", pDisk->VmdkState.fReadOnly));
-    return pDisk->VmdkState.fReadOnly;
-}
-
-
-/**
- * Get disk size of VMDK HDD container.
- *
- * @returns Virtual disk size in bytes.
- * @returns 0 if no one VMDK image is opened in HDD container.
- */
-IDER3DECL(uint64_t) VMDKDiskGetSize(PVMDKDISK pDisk)
-{
-    /* sanity check */
-    Assert(pDisk);
-    AssertMsg(pDisk->u32Signature == VMDKDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
-
-    return pDisk->VmdkState.total_sectors * VMDK_GEOMETRY_SECTOR_SIZE;
-}
-
-/**
- * Get block size of VMDK HDD container.
- *
- * @returns VDI image block size in bytes.
- * @returns 0 if no one VMDK image is opened in HDD container.
- */
-IDER3DECL(unsigned) VMDKDiskGetBlockSize(PVMDKDISK pDisk)
-{
-    /* sanity check */
-    Assert(pDisk);
-    AssertMsg(pDisk->u32Signature == VMDKDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
-
-    return VMDK_GEOMETRY_SECTOR_SIZE;
-}
-
-/**
- * Get virtual disk geometry stored in image file.
- *
- * @returns VBox status code.
- * @param   pDisk           Pointer to VMDK HDD container.
- * @param   pcCylinders     Where to store the number of cylinders. NULL is ok.
- * @param   pcHeads         Where to store the number of heads. NULL is ok.
- * @param   pcSectors       Where to store the number of sectors. NULL is ok.
- */
-IDER3DECL(int) VMDKDiskGetGeometry(PVMDKDISK pDisk, unsigned *pcCylinders, unsigned *pcHeads, unsigned *pcSectors)
-{
-    /* sanity check */
-    Assert(pDisk);
-    AssertMsg(pDisk->u32Signature == VMDKDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
-
-    PVMDKDISKGEOMETRY pGeometry = &pDisk->Geometry;
-
-    LogFlow(("VDIDiskGetGeometry: C/H/S = %u/%u/%u\n",
-             pGeometry->cCylinders, pGeometry->cHeads, pGeometry->cSectors));
-
-    int rc = VINF_SUCCESS;
-
-    if (    pGeometry->cCylinders > 0
-        &&  pGeometry->cHeads > 0
-        &&  pGeometry->cSectors > 0)
-    {
-        if (pcCylinders)
-            *pcCylinders = pDisk->Geometry.cCylinders;
-        if (pcHeads)
-            *pcHeads = pDisk->Geometry.cHeads;
-        if (pcSectors)
-            *pcSectors = pDisk->Geometry.cSectors;
-    }
-    else
-        rc = VERR_VDI_GEOMETRY_NOT_SET;
-
-    LogFlow(("VDIDiskGetGeometry: returns %Vrc\n", rc));
-    return rc;
-}
-
-/**
- * Store virtual disk geometry into base image file of HDD container.
- *
- * Note that in case of unrecoverable error all images of HDD container will be closed.
- *
- * @returns VBox status code.
- * @param   pDisk           Pointer to VMDK HDD container.
- * @param   cCylinders      Number of cylinders.
- * @param   cHeads          Number of heads.
- * @param   cSectors        Number of sectors.
- */
-IDER3DECL(int) VMDKDiskSetGeometry(PVMDKDISK pDisk, unsigned cCylinders, unsigned cHeads, unsigned cSectors)
-{
-    LogFlow(("VMDKDiskSetGeometry: C/H/S = %u/%u/%u\n", cCylinders, cHeads, cSectors));
-    /* sanity check */
-    Assert(pDisk);
-    AssertMsg(pDisk->u32Signature == VMDKDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
-
-    pDisk->Geometry.cCylinders = cCylinders;
-    pDisk->Geometry.cHeads     = cHeads;
-    pDisk->Geometry.cSectors   = cSectors;
-    pDisk->Geometry.cbSector   = VMDK_GEOMETRY_SECTOR_SIZE;
-
-    /** @todo Update header information in base image file. */
-    return VINF_SUCCESS;
-}
-
-/**
- * Get number of opened images in HDD container.
- *
- * @returns Number of opened images for HDD container. 0 if no images is opened.
- * @param   pDisk           Pointer to VMDK HDD container.
- */
-IDER3DECL(int) VMDKDiskGetImagesCount(PVMDKDISK pDisk)
-{
-    /* sanity check */
-    Assert(pDisk);
-    AssertMsg(pDisk->u32Signature == VMDKDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
-
-    return 1;
-}
-
-/*******************************************************************************
-*   PDM interface                                                              *
-*******************************************************************************/
-
-/**
- * Construct a VBox HDD media driver instance.
- *
- * @returns VBox status.
- * @param   pDrvIns     The driver instance data.
- *                      If the registration structure is needed, pDrvIns->pDrvReg points to it.
- * @param   pCfgHandle  Configuration node handle for the driver. Use this to obtain the configuration
- *                      of the driver instance. It's also found in pDrvIns->pCfgHandle, but like
- *                      iInstance it's expected to be used a bit in this function.
- */
-static DECLCALLBACK(int) vmdkConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
-{
-    LogFlow(("vmdkConstruct:\n"));
-    PVMDKDISK pData = PDMINS2DATA(pDrvIns, PVMDKDISK);
-
-    /*
-     * Init the static parts.
-     */
-    pDrvIns->IBase.pfnQueryInterface    = vmdkQueryInterface;
-    pData->pDrvIns = pDrvIns;
-
-    pData->u32Signature = VMDKDISK_SIGNATURE;
-
-    pData->Geometry.cCylinders = 0;
-    pData->Geometry.cHeads = 0;
-    pData->Geometry.cSectors = 0;
-    pData->Geometry.cbSector = 0;
-
-    /* IMedia */
-    pData->IMedia.pfnRead               = vmdkRead;
-    pData->IMedia.pfnWrite              = vmdkWrite;
-    pData->IMedia.pfnFlush              = vmdkFlush;
-    pData->IMedia.pfnGetSize            = vmdkGetSize;
-    pData->IMedia.pfnGetUuid            = vmdkGetUuid;
-    pData->IMedia.pfnIsReadOnly         = vmdkIsReadOnly;
-    pData->IMedia.pfnBiosGetGeometry    = vmdkBiosGetGeometry;
-    pData->IMedia.pfnBiosSetGeometry    = vmdkBiosSetGeometry;
-    pData->IMedia.pfnBiosGetTranslation = vmdkBiosGetTranslation;
-    pData->IMedia.pfnBiosSetTranslation = vmdkBiosSetTranslation;
-
-    /*
-     * Validate and read top level configuration.
-     */
-    char *pszName;
-    int rc = CFGMR3QueryStringAlloc(pCfgHandle, "Path", &pszName);
-    if (VBOX_FAILURE(rc))
-        return PDMDRV_SET_ERROR(pDrvIns, rc,
-                                N_("VHDD: Configuration error: Querying \"Path\" as string failed"));
-
-    /** True if the media is readonly. */
-    bool fReadOnly;
-    rc = CFGMR3QueryBool(pCfgHandle, "ReadOnly", &fReadOnly);
-    if (rc == VERR_CFGM_VALUE_NOT_FOUND)
-        fReadOnly = false;
-    else if (VBOX_FAILURE(rc))
-    {
-        MMR3HeapFree(pszName);
-        return PDMDRV_SET_ERROR(pDrvIns, rc,
-                                N_("VHDD: Configuration error: Querying \"ReadOnly\" as boolean failed"));
-    }
-
-    /*
-     * Open the image.
-     */
-    rc = vmdk_open(pData, pszName, fReadOnly);
-    if (VBOX_SUCCESS(rc))
-        Log(("vmdkConstruct: Opened '%s' in %s mode\n", pszName, VMDKDiskIsReadOnly(pData) ? "read-only" : "read-write"));
-    else
-        AssertMsgFailed(("Failed to open image '%s' rc=%Vrc\n", pszName, rc));
-
-    MMR3HeapFree(pszName);
-    pszName = NULL;
-
-    return rc;
-}
-
-/**
- * Destruct a driver instance.
- *
- * Most VM resources are freed by the VM. This callback is provided so that any non-VM
- * resources can be freed correctly.
- *
- * @param   pDrvIns     The driver instance data.
- */
-static DECLCALLBACK(void) vmdkDestruct(PPDMDRVINS pDrvIns)
-{
-    LogFlow(("vmdkDestruct:\n"));
-    PVMDKDISK pData = PDMINS2DATA(pDrvIns, PVMDKDISK);
-    vmdk_close(&pData->VmdkState);
-}
-
-/**
- * When the VM has been suspended we'll change the image mode to read-only
- * so that main and others can read the VDIs. This is important when
- * saving state and so forth.
- *
- * @param   pDrvIns     The driver instance data.
- */
-static DECLCALLBACK(void) vmdkSuspend(PPDMDRVINS pDrvIns)
-{
-    LogFlow(("vmdkSuspend:\n"));
-    PVMDKDISK pData = PDMINS2DATA(pDrvIns, PVMDKDISK);
-    if (!VMDKDiskIsReadOnly(pData))
-    {
-        /** @todo does this even make sense? the vdi method locks the whole file, but don't we close it afterwards?? */
-        //int rc = vmdkChangeImageMode(pData, true);
-        //AssertRC(rc);
-    }
-}
-
-/**
- * Before the VM resumes we'll have to undo the read-only mode change
- * done in vmdkSuspend.
- *
- * @param   pDrvIns     The driver instance data.
- */
-static DECLCALLBACK(void) vmdkResume(PPDMDRVINS pDrvIns)
-{
-    LogFlow(("vmdkSuspend:\n"));
-    PVMDKDISK pData = PDMINS2DATA(pDrvIns, PVMDKDISK);
-    if (!VMDKDiskIsReadOnly(pData))
-    {
-        /** @todo does this even make sense? the vdi method locks the whole file, but don't we close it afterwards?? */
-        //int rc = vmdkChangeImageMode(pData, false);
-        //AssertRC(rc);
-    }
-}
-
-
-/** @copydoc PDMIMEDIA::pfnGetSize */
-static DECLCALLBACK(uint64_t) vmdkGetSize(PPDMIMEDIA pInterface)
-{
-    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
-    uint64_t cb = VMDKDiskGetSize(pData);
-    LogFlow(("vmdkGetSize: returns %#llx (%llu)\n", cb, cb));
-    return cb;
-}
-
-/**
- * Get stored media geometry - BIOS property.
- *
- * @see PDMIMEDIA::pfnBiosGetGeometry for details.
- */
-static DECLCALLBACK(int) vmdkBiosGetGeometry(PPDMIMEDIA pInterface, uint32_t *pcCylinders, uint32_t *pcHeads, uint32_t *pcSectors)
-{
-    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
-    int rc = VMDKDiskGetGeometry(pData, pcCylinders, pcHeads, pcSectors);
-    if (VBOX_SUCCESS(rc))
-    {
-        LogFlow(("vmdkBiosGetGeometry: returns VINF_SUCCESS\n"));
-        return VINF_SUCCESS;
-    }
-    Log(("vmdkBiosGetGeometry: The Bios geometry data was not available.\n"));
-    return VERR_PDM_GEOMETRY_NOT_SET;
-}
-
-/**
- * Set stored media geometry - BIOS property.
- *
- * @see PDMIMEDIA::pfnBiosSetGeometry for details.
- */
-static DECLCALLBACK(int) vmdkBiosSetGeometry(PPDMIMEDIA pInterface, uint32_t cCylinders, uint32_t cHeads, uint32_t cSectors)
-{
-    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
-    int rc = VMDKDiskSetGeometry(pData, cCylinders, cHeads, cSectors);
-    LogFlow(("vmdkBiosSetGeometry: returns %Vrc (%d,%d,%d)\n", rc, cCylinders, cHeads, cSectors));
-    return rc;
-}
-
-/**
- * Read bits.
- *
- * @see PDMIMEDIA::pfnRead for details.
- */
-static DECLCALLBACK(int) vmdkRead(PPDMIMEDIA pInterface, uint64_t off, void *pvBuf, size_t cbRead)
-{
-    LogFlow(("vmdkRead: off=%#llx pvBuf=%p cbRead=%d\n", off, pvBuf, cbRead));
-    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
-    int rc = vmdk_read(&pData->VmdkState, off >> VMDK_GEOMETRY_SECTOR_SHIFT, (uint8_t *)pvBuf, cbRead >> VMDK_GEOMETRY_SECTOR_SHIFT);
-    if (VBOX_SUCCESS(rc))
-        Log2(("vmdkRead: off=%#llx pvBuf=%p cbRead=%d\n"
-              "%.*Vhxd\n",
-              off, pvBuf, cbRead, cbRead, pvBuf));
-    LogFlow(("vmdkRead: returns %Vrc\n", rc));
-    return rc;
-}
-
-/**
- * Write bits.
- *
- * @see PDMIMEDIA::pfnWrite for details.
- */
-static DECLCALLBACK(int) vmdkWrite(PPDMIMEDIA pInterface, uint64_t off, const void *pvBuf, size_t cbWrite)
-{
-    LogFlow(("vmdkWrite: off=%#llx pvBuf=%p cbWrite=%d\n", off, pvBuf, cbWrite));
-    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
-    Log2(("vmdkWrite: off=%#llx pvBuf=%p cbWrite=%d\n"
-          "%.*Vhxd\n",
-          off, pvBuf, cbWrite, cbWrite, pvBuf));
-    int rc = vmdk_write(&pData->VmdkState, off >> VMDK_GEOMETRY_SECTOR_SHIFT, (const uint8_t *)pvBuf, cbWrite >> VMDK_GEOMETRY_SECTOR_SHIFT);
-    LogFlow(("vmdkWrite: returns %Vrc\n", rc));
-    return rc;
-}
-
-/**
- * Flush bits to media.
- *
- * @see PDMIMEDIA::pfnFlush for details.
- */
-static DECLCALLBACK(int) vmdkFlush(PPDMIMEDIA pInterface)
-{
-    LogFlow(("vmdkFlush:\n"));
-    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
-    vmdk_flush(&pData->VmdkState);
-    int rc = VINF_SUCCESS;
-    LogFlow(("vmdkFlush: returns %Vrc\n", rc));
-    return rc;
-}
-
-/** @copydoc PDMIMEDIA::pfnGetUuid */
-static DECLCALLBACK(int) vmdkGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid)
-{
-    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
-    /** @todo */
-    int rc = VINF_SUCCESS;
-    NOREF(pData);
-    LogFlow(("vmdkGetUuid: returns %Vrc ({%Vuuid})\n", rc, pUuid));
-    return rc;
-}
-
-/** @copydoc PDMIMEDIA::pfnIsReadOnly */
-static DECLCALLBACK(bool) vmdkIsReadOnly(PPDMIMEDIA pInterface)
-{
-    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
-    LogFlow(("vmdkIsReadOnly: returns %d\n", VMDKDiskIsReadOnly(pData)));
-    return VMDKDiskIsReadOnly(pData);
-}
-
-/** @copydoc PDMIMEDIA::pfnBiosGetTranslation */
-static DECLCALLBACK(int) vmdkBiosGetTranslation(PPDMIMEDIA pInterface,
-                                               PPDMBIOSTRANSLATION penmTranslation)
-{
-    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
-    int rc = VINF_SUCCESS;
-    NOREF(pData);
-    *penmTranslation = PDMBIOSTRANSLATION_AUTO; /** @todo */
-    LogFlow(("vmdkBiosGetTranslation: returns %Vrc (%d)\n", rc, *penmTranslation));
-    return rc;
-}
-
-/** @copydoc PDMIMEDIA::pfnBiosSetTranslation */
-static DECLCALLBACK(int) vmdkBiosSetTranslation(PPDMIMEDIA pInterface,
-                                               PDMBIOSTRANSLATION enmTranslation)
-{
-    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
-    /** @todo */
-    int rc = VINF_SUCCESS;
-    NOREF(pData);
-    LogFlow(("vmdkBiosSetTranslation: returns %Vrc (%d)\n", rc, enmTranslation));
-    return rc;
-}
-
-
-/**
- * Queries an interface to the driver.
- *
- * @returns Pointer to interface.
- * @returns NULL if the interface was not supported by the driver.
- * @param   pInterface          Pointer to this interface structure.
- * @param   enmInterface        The requested interface identification.
- * @thread  Any thread.
- */
-static DECLCALLBACK(void *) vmdkQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
-{
-    PPDMDRVINS pDrvIns = PDMIBASE_2_DRVINS(pInterface);
-    PVMDKDISK pData = PDMINS2DATA(pDrvIns, PVMDKDISK);
-    switch (enmInterface)
-    {
-        case PDMINTERFACE_BASE:
-            return &pDrvIns->IBase;
-        case PDMINTERFACE_MEDIA:
-            return &pData->IMedia;
-        default:
-            return NULL;
-    }
-}
-
-
-/**
- * VMDK driver registration record.
- */
-const PDMDRVREG g_DrvVmdkHDD =
-{
-    /* u32Version */
-    PDM_DRVREG_VERSION,
-    /* szDriverName */
-    "VmdkHDD",
-    /* pszDescription */
-    "VMDK media driver.",
-    /* fFlags */
-    PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
-    /* fClass. */
-    PDM_DRVREG_CLASS_MEDIA,
-    /* cMaxInstances */
-    ~0,
-    /* cbInstance */
-    sizeof(VMDKDISK),
-    /* pfnConstruct */
-    vmdkConstruct,
-    /* pfnDestruct */
-    vmdkDestruct,
-    /* pfnIOCtl */
-    NULL,
-    /* pfnPowerOn */
-    NULL,
-    /* pfnReset */
-    NULL,
-    /* pfnSuspend */
-    vmdkSuspend,
-    /* pfnResume */
-    vmdkResume,
-    /* pfnDetach */
-    NULL
-};
Index: trunk/src/VBox/Devices/Storage/VmdkHDDCore.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/VmdkHDDCore.cpp	(revision 2358)
+++ trunk/src/VBox/Devices/Storage/VmdkHDDCore.cpp	(revision 2358)
@@ -0,0 +1,2486 @@
+/** $Id$ */
+/** @file
+ * VMDK Disk image, Core Code.
+ */
+
+/*
+ * Copyright (C) 2006 InnoTek Systemberatung GmbH
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file 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 2 as it comes in the "COPYING" file of the VirtualBox OSE
+ * distribution. VirtualBox OSE is distributed in the hope that it will
+ * be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * If you received this file as part of a commercial VirtualBox
+ * distribution, then only the terms of your commercial VirtualBox
+ * license agreement apply instead of the previous paragraph.
+ */
+
+/*******************************************************************************
+*   Header Files                                                               *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_VMDK
+#include "VBoxHDD-newInternal.h"
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/alloc.h>
+#include <iprt/uuid.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+
+/*******************************************************************************
+*   Constants And Macros, Structures and Typedefs                              *
+*******************************************************************************/
+
+/**
+ * Magic number for hosted images created by VMware Workstation 4, VMware
+ * Workstation 5, VMware Server or VMware Player.
+ */
+#define VMDK_SPARSE_MAGICNUMBER 0x564d444b /* 'V' 'M' 'D' 'K' */
+
+/** VMDK hosted sparse extent header. */
+#pragma pack(1)
+typedef struct SparseExtentHeader
+{
+    uint32_t    magicNumber;
+    uint32_t    version;
+    uint32_t    flags;
+    uint64_t    capacity;
+    uint64_t    grainSize;
+    uint64_t    descriptorOffset;
+    uint64_t    descriptorSize;
+    uint32_t    numGTEsPerGT;
+    uint64_t    rgdOffset;
+    uint64_t    gdOffset;
+    uint64_t    overHead;
+    bool        uncleanShutdown;
+    char        singleEndLineChar;
+    char        nonEndLineChar;
+    char        doubleEndLineChar1;
+    char        doubleEndLineChar2;
+    uint8_t     pad[435];
+} SparseExtentHeader;
+#pragma pack()
+
+
+#ifdef VBOX_WITH_VMDK_ESX
+
+/** @todo the ESX code is not tested, not used, and lacks error messages. */
+
+/**
+ * Magic number for images created by VMware GSX Server 3 or ESX Server 3.
+ */
+#define VMDK_ESX_SPARSE_MAGICNUMBER 0x44574f43 /* 'C' 'O' 'W' 'D' */
+
+#pragma pack(1)
+typedef struct COWDisk_Header
+{
+    uint32_t    magicNumber;
+    uint32_t    version;
+    uint32_t    flags;
+    uint32_t    numSectors;
+    uint32_t    grainSize;
+    uint32_t    gdOffset;
+    uint32_t    numGDEntries;
+    uint32_t    freeSector;
+    /* The spec incompletely documents quite a few further fields, but states
+     * that they are not used by the current format. Replace them by padding. */
+    char        reserved1[1604];
+    uint32_t    savedGeneration;
+    char        reserved2[8];
+    uint32_t    uncleanShutdown;
+    char        padding[396];
+} COWDisk_Header;
+#pragma pack()
+#endif /* VBOX_WITH_VMDK_ESX */
+
+
+/** Convert sector number/size to byte offset/size. */
+#define VMDK_SECTOR2BYTE(u) ((u) << 9)
+
+/** Convert byte offset/size to sector number/size. */
+#define VMDK_BYTE2SECTOR(u) ((u) >> 9)
+
+/**
+ * VMDK extent type.
+ */
+typedef enum VMDKETYPE
+{
+    /** Hosted sparse extent. */
+    VMDKETYPE_HOSTED_SPARSE = 1,
+    /** Flat extent. */
+    VMDKETYPE_FLAT,
+    /** Zero extent. */
+    VMDKETYPE_ZERO
+#ifdef VBOX_WITH_VMDK_ESX
+    ,
+    /** ESX sparse extent. */
+    VMDKETYPE_ESX_SPARSE
+#endif /* VBOX_WITH_VMDK_ESX */
+} VMDKETYPE, *PVMDKETYPE;
+
+/**
+ * VMDK access type for a extent.
+ */
+typedef enum VMDKACCESS
+{
+    /** No access allowed. */
+    VMDKACCESS_NOACCESS = 0,
+    /** Read-only access. */
+    VMDKACCESS_READONLY,
+    /** Read-write access. */
+    VMDKACCESS_READWRITE
+} VMDKACCESS, *PVMDKACCESS;
+
+/**
+ * VMDK extent data structure.
+ */
+typedef struct VMDKEXTENT
+{
+    /** File handle. */
+    RTFILE      File;
+    /** Base name of the image extent. */
+    char        *pszBasename;
+    /** Full name of the image extent. */
+    char        *pszFullname;
+    /** Number of sectors in this extent. */
+    uint64_t    cSectors;
+    /** Number of sectors per block (grain in VMDK speak). */
+    uint64_t    cSectorsPerGrain;
+    /** Starting sector number of descriptor. */
+    uint64_t    uDescriptorSector;
+    /** Size of descriptor in sectors. */
+    uint64_t    cDescriptorSectors;
+    /** Starting sector number of grain directory. */
+    uint64_t    uSectorGD;
+    /** Starting sector number of redundant grain directory. */
+    uint64_t    uSectorRGD;
+    /** Total number of metadata sectors. */
+    uint64_t    uOverheadSectors;
+    /** Nominal size (i.e. as described by the descriptor) of this extent. */
+    uint64_t    cNominalSectors;
+    /** Sector offset (i.e. as described by the descriptor) of this extent. */
+    uint64_t    uSectorOffset;
+    /** Number of entries in a grain table. */
+    uint32_t    cGTEntries;
+    /** Number of sectors reachable via a grain directory entry. */
+    uint32_t    cSectorsPerGDE;
+    /** Number of entries in the grain directory. */
+    uint32_t    cGDEntries;
+    /** Pointer to the next free sector. Legacy information. Do not use. */
+    uint32_t    uFreeSector;
+    /** Number of this extent in the list of images. */
+    uint32_t    uExtent;
+    /** Pointer to the descriptor (NULL if no descriptor in this extent). */
+    char        *pDescData;
+    /** Pointer to the grain directory. */
+    uint32_t    *pGD;
+    /** Pointer to the redundant grain directory. */
+    uint32_t     *pRGD;
+    /** Type of this extent. */
+    VMDKETYPE   enmType;
+    /** Access to this extent. */
+    VMDKACCESS  enmAccess;
+    /** Flag whether this extent is marked as unclean. */
+    bool        fUncleanShutdown;
+    /** Flag whether the metadata in the extent header needs to be updated. */
+    bool        fMetaDirty;
+    /** Reference to the image in which this extent is used. Do not use this
+     * on a regular basis to avoid passing pImage references to functions
+     * explicitly. */
+    struct VMDKIMAGE *pImage;
+} VMDKEXTENT, *PVMDKEXTENT;
+
+/**
+ * Grain table cache size. Allocated per image.
+ */
+#define VMDK_GT_CACHE_SIZE 256
+
+/**
+ * Grain table block size. Smaller than an actual grain table block to allow
+ * more grain table blocks to be cached without having to allocate excessive
+ * amounts of memory for the cache.
+ */
+#define VMDK_GT_CACHELINE_SIZE 128
+
+
+/**
+ * Maximum number of lines in a descriptor file. Not worth the effort of
+ * making it variable. Descriptor files are generally very short (~20 lines).
+ */
+#define VMDK_DESCRIPTOR_LINES_MAX   100
+
+/**
+ * Parsed descriptor information. Allows easy access and update of the
+ * descriptor (whether separate file or not). Free form text files suck.
+ */
+typedef struct VMDKDESCRIPTOR
+{
+    /** Line number of first entry of the disk descriptor. */
+    unsigned    uFirstDesc;
+    /** Line number of first entry in the extent description. */
+    unsigned    uFirstExtent;
+    /** Line number of first disk database entry. */
+    unsigned    uFirstDDB;
+    /** Total number of lines. */
+    unsigned    cLines;
+    /** Total amount of memory available for the descriptor. */
+    size_t      cbDescAlloc;
+    /** Set if descriptor has been changed and not yer written to disk. */
+    bool        fDirty;
+    /** Array of pointers to the data in the descriptor. */
+    char        *aLines[VMDK_DESCRIPTOR_LINES_MAX];
+    /** Array of line indices pointing to the next non-comment line. */
+    unsigned    aNextLines[VMDK_DESCRIPTOR_LINES_MAX];
+} VMDKDESCRIPTOR, *PVMDKDESCRIPTOR;
+
+
+/**
+ * Cache entry for translating extent/sector to a sector number in that
+ * extent.
+ */
+typedef struct VMDKGTCACHEENTRY
+{
+    /** Extent number for which this entry is valid. */
+    uint32_t    uExtent;
+    /** GT data block number. */
+    uint64_t    uGTBlock;
+    /** Data part of the cache entry. */
+    uint32_t    aGTData[VMDK_GT_CACHELINE_SIZE];
+} VMDKGTCACHEENTRY, *PVMDKGTCACHEENTRY;
+
+/**
+ * Cache data structure for blocks of grain table entries. For now this is a
+ * fixed size direct mapping cache, but this should be adapted to the size of
+ * the sparse image and maybe converted to a set-associative cache. The
+ * implementation below implements a write-through cache with write allocate.
+ */
+typedef struct VMDKGTCACHE
+{
+    /** Cache entries. */
+    VMDKGTCACHEENTRY    aGTCache[VMDK_GT_CACHE_SIZE];
+    /** Number of cache entries (currently unused). */
+    unsigned            cEntries;
+} VMDKGTCACHE, *PVMDKGTCACHE;
+
+/**
+ * Complete VMDK image data structure. Mainly a collection of extents and a few
+ * extra global data fields.
+ */
+typedef struct VMDKIMAGE
+{
+    PVMDKEXTENT     pExtents;
+    unsigned        cExtents;
+
+    /** Base image name. */
+    const char      *pszFilename;
+    /** Descriptor file if applicable. */
+    RTFILE          File;
+
+    /** Error callback. */
+    PFNVDERROR      pfnError;
+    /** Opaque data for error callback. */
+    void            *pvErrorUser;
+
+    /** Open flags passed by VBoxHD layer. */
+    unsigned        uOpenFlags;
+    /** Image type. */
+    VDIMAGETYPE     enmImageType;
+    /** Total size of the image. */
+    uint64_t        cbSize;
+    /** BIOS translation mode. */
+    PDMBIOSTRANSLATION enmTranslation;
+    /** Physical geometry of this image, cylinders. */
+    uint32_t        cCylinders;
+    /** Physical geometry of this image, heads. */
+    uint32_t        cHeads;
+    /** Physical geometry of this image, sectors. */
+    uint32_t        cSectors;
+    /** Image UUID. */
+    RTUUID          ImageUuid;
+    /** Image modification UUID. */
+    RTUUID          ModificationUuid;
+    /** Parent image UUID. */
+    RTUUID          ParentUuid;
+
+    /** Pointer to the grain table cache, if this image contains sparse extents. */
+    PVMDKGTCACHE    pGTCache;
+    /** Pointer to the descriptor (NULL if no separate descriptor file). */
+    char            *pDescData;
+    /** Allocation size of the descriptor file. */
+    size_t          cbDescAlloc;
+    /** Parsed descriptor file content. */
+    VMDKDESCRIPTOR  Descriptor;
+} VMDKIMAGE, *PVMDKIMAGE;
+
+
+/*******************************************************************************
+*   Internal Functions                                                         *
+*******************************************************************************/
+
+static int vmdkReadGrainDirectory(PVMDKEXTENT pExtent);
+static void vmdkFreeGrainDirectory(PVMDKEXTENT pExtent);
+
+static int vmdkPreprocessDescriptor(PVMDKIMAGE pImage, char *pDescData, size_t cbDescData, PVMDKDESCRIPTOR pDescriptor);
+static int vmdkReadMetaSparseExtent(PVMDKEXTENT pExtent);
+static int vmdkWriteMetaSparseExtent(PVMDKEXTENT pExtent);
+#ifdef VBOX_WITH_VMDK_ESX
+static int vmdkReadMetaESXSparseExtent(PVMDKEXTENT pExtent);
+#endif /* VBOX_WITH_VMDK_ESX */
+static void vmdkFreeExtentData(PVMDKEXTENT pExtent);
+
+static int vmdkCreateExtents(PVMDKIMAGE pImage, unsigned cExtents);
+static int vmdkOpenImage(PVMDKIMAGE pImage, const char *pszFilename, unsigned uOpenFlags);
+static int vmdkFlushImage(PVMDKIMAGE pImage);
+static void vmdkFreeImage(PVMDKIMAGE pImage);
+
+static int vmdkOpen(const char *pszFilename, unsigned uOpenFlags, PFNVDERROR pfnError, void *pvErrorUser, void **ppvBackendData);
+static int vmdkClose(void *pBackendData);
+
+
+DECLINLINE(int) vmdkError(PVMDKIMAGE pImage, int rc, RT_SRC_POS_DECL, const char *pszFormat, ...)
+{
+    va_list va;
+    va_start(va, pszFormat);
+    pImage->pfnError(pImage->pvErrorUser, rc, RT_SRC_POS_ARGS, pszFormat, va);
+    va_end(va);
+    return rc;
+}
+
+static int vmdkReadGrainDirectory(PVMDKEXTENT pExtent)
+{
+    int rc = VINF_SUCCESS;
+    unsigned i;
+    uint32_t *pGD = NULL, *pRGD = NULL, *pGDTmp, *pRGDTmp;
+    size_t cbGD = pExtent->cGDEntries * sizeof(uint32_t);
+
+    pGD = (uint32_t *)RTMemAllocZ(cbGD);
+    if (!pGD)
+    {
+        rc = VERR_NO_MEMORY;
+        goto out;
+    }
+    pExtent->pGD = pGD;
+    rc = RTFileReadAt(pExtent->File, VMDK_SECTOR2BYTE(pExtent->uSectorGD),
+                      pGD, cbGD, NULL);
+    AssertRC(rc);
+    if (VBOX_FAILURE(rc))
+    {
+        rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: could not read grain directory for file '%s'"), pExtent->pszFullname);
+        goto out;
+    }
+    for (i = 0, pGDTmp = pGD; i < pExtent->cGDEntries; i++, pGDTmp++)
+        *pGDTmp = RT_LE2H_U32(*pGDTmp);
+
+    if (pExtent->uSectorRGD)
+    {
+        pRGD = (uint32_t *)RTMemAllocZ(cbGD);
+        if (!pRGD)
+        {
+            rc = VERR_NO_MEMORY;
+            goto out;
+        }
+        pExtent->pRGD = pRGD;
+        rc = RTFileReadAt(pExtent->File, VMDK_SECTOR2BYTE(pExtent->uSectorRGD),
+                          pRGD, cbGD, NULL);
+        AssertRC(rc);
+        if (VBOX_FAILURE(rc))
+        {
+            rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: could not read redundant grain directory for file '%s'"), pExtent->pszFullname);
+            goto out;
+        }
+        for (i = 0, pRGDTmp = pRGD; i < pExtent->cGDEntries; i++, pRGDTmp++)
+            *pRGDTmp = RT_LE2H_U32(*pRGDTmp);
+
+        /* Check grain table and redundant grain table for consistency. */
+        size_t cbGT = pExtent->cGTEntries;
+        uint32_t *pTmpGT1 = (uint32_t *)RTMemTmpAlloc(cbGT);
+        if (!pTmpGT1)
+        {
+            rc = VERR_NO_MEMORY;
+            goto out;
+        }
+        uint32_t *pTmpGT2 = (uint32_t *)RTMemTmpAlloc(cbGT);
+        if (!pTmpGT2)
+        {
+            RTMemTmpFree(pTmpGT1);
+            rc = VERR_NO_MEMORY;
+            goto out;
+        }
+
+        for (i = 0, pGDTmp = pGD, pRGDTmp = pRGD; i < pExtent->cGDEntries; i++, pGDTmp++, pRGDTmp++)
+        {
+            /* If no grain table is allocated skip the entry. */
+            if (*pGDTmp == 0 && *pRGDTmp == 0)
+                continue;
+
+            if (*pGDTmp == 0 || *pRGDTmp == 0 || *pGDTmp == *pRGDTmp)
+            {
+                /* Just one grain directory entry refers to a not yet allocated
+                 * grain table or both grain directory copies refer to the same
+                 * grain table. Not allowed. */
+                RTMemTmpFree(pTmpGT1);
+                RTMemTmpFree(pTmpGT2);
+                rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: inconsistent references to grain directory in file '%s'"), pExtent->pszFullname);
+                goto out;
+            }
+            rc = RTFileReadAt(pExtent->File, VMDK_SECTOR2BYTE(*pGDTmp),
+                              pTmpGT1, cbGT, NULL);
+            if (VBOX_FAILURE(rc))
+            {
+                rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error reading grain table in file '%s'"), pExtent->pszFullname);
+                RTMemTmpFree(pTmpGT1);
+                RTMemTmpFree(pTmpGT2);
+                goto out;
+            }
+            rc = RTFileReadAt(pExtent->File, VMDK_SECTOR2BYTE(*pRGDTmp),
+                              pTmpGT2, cbGT, NULL);
+            if (VBOX_FAILURE(rc))
+            {
+                rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error reading backup grain table in file '%s'"), pExtent->pszFullname);
+                RTMemTmpFree(pTmpGT1);
+                RTMemTmpFree(pTmpGT2);
+                goto out;
+            }
+            if (memcmp(pTmpGT1, pTmpGT2, cbGT))
+            {
+                RTMemTmpFree(pTmpGT1);
+                RTMemTmpFree(pTmpGT2);
+                rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: inconsistency between grain table and backup grain table"));
+                goto out;
+            }
+        }
+
+        /** @todo figure out what to do for unclean VMDKs. */
+    }
+
+out:
+    if (VBOX_FAILURE(rc))
+        vmdkFreeGrainDirectory(pExtent);
+    return rc;
+}
+
+static void vmdkFreeGrainDirectory(PVMDKEXTENT pExtent)
+{
+    if (pExtent->pszBasename)
+    {
+        RTMemTmpFree(pExtent->pszBasename);
+        pExtent->pszBasename = NULL;
+    }
+    if (pExtent->pGD)
+    {
+        RTMemFree(pExtent->pGD);
+        pExtent->pGD = NULL;
+    }
+    if (pExtent->pRGD)
+    {
+        RTMemFree(pExtent->pRGD);
+        pExtent->pRGD = NULL;
+    }
+}
+
+static int vmdkStringUnquote(PVMDKIMAGE pImage, const char *pszStr, char **ppszUnquoted, char **ppszNext)
+{
+    char *pszQ;
+    char *pszUnquoted;
+
+    /* Skip over whitespace. */
+    while (*pszStr == ' ' || *pszStr == '\t')
+        pszStr++;
+    if (*pszStr++ != '"')
+        return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrectly quoted value in descriptor"));
+
+    pszQ = strchr(pszStr, '"');
+    if (pszQ == NULL)
+        return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrectly quoted value in descriptor"));
+    pszUnquoted = (char *)RTMemTmpAlloc(pszQ - pszStr + 1);
+    if (!pszUnquoted)
+        return VERR_NO_MEMORY;
+    memcpy(pszUnquoted, pszStr, pszQ - pszStr);
+    pszUnquoted[pszQ - pszStr] = '\0';
+    *ppszUnquoted = pszUnquoted;
+    if (ppszNext)
+        *ppszNext = pszQ + 1;
+    return VINF_SUCCESS;
+}
+
+static bool vmdkDescGetStr(PVMDKDESCRIPTOR pDescriptor, unsigned uStart,
+                           const char *pszKey, const char **ppszValue)
+{
+    size_t cbKey = strlen(pszKey);
+    const char *pszValue;
+
+    while (uStart != 0)
+    {
+        if (!strncmp(pDescriptor->aLines[uStart], pszKey, cbKey))
+        {
+            /* Key matches, check if there is a '=' (preceded by whitespace). */
+            pszValue = pDescriptor->aLines[uStart] + cbKey;
+            while (*pszValue == ' ' || *pszValue == '\t')
+                pszValue++;
+            if (*pszValue == '=')
+            {
+                *ppszValue = pszValue + 1;
+                break;
+            }
+        }
+        uStart = pDescriptor->aNextLines[uStart];
+    }
+    return !!uStart;
+}
+
+static int vmdkDescSetStr(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+                          unsigned uStart,
+                          const char *pszKey, const char *pszValue)
+{
+    char *pszTmp;
+    size_t cbKey = strlen(pszKey);
+    unsigned uLast;
+
+    while (uStart != 0)
+    {
+        if (!strncmp(pDescriptor->aLines[uStart], pszKey, cbKey))
+        {
+            /* Key matches, check if there is a '=' (preceded by whitespace). */
+            pszTmp = pDescriptor->aLines[uStart] + cbKey;
+            while (*pszTmp == ' ' || *pszTmp == '\t')
+                pszTmp++;
+            if (*pszTmp == '=')
+            {
+                while (*pszTmp == ' ' || *pszTmp == '\t')
+                    pszTmp++;
+                break;
+            }
+        }
+        if (!pDescriptor->aNextLines[uStart])
+            uLast = uStart;
+        uStart = pDescriptor->aNextLines[uStart];
+    }
+    if (uStart)
+    {
+        /* Key already exists, replace existing value. */
+        size_t cbOldVal = strlen(pszTmp);
+        size_t cbNewVal = strlen(pszValue);
+        ssize_t cbDiff = cbNewVal - cbOldVal;
+        /* Check for buffer overflow. */
+        if (    pDescriptor->aLines[pDescriptor->cLines]
+            -   pDescriptor->aLines[0] > (ptrdiff_t)pDescriptor->cbDescAlloc - cbDiff)
+            return vmdkError(pImage, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too big"));
+
+        memmove(pszTmp + cbNewVal, pszTmp + cbOldVal,
+                pDescriptor->aLines[pDescriptor->cLines] - pszTmp - cbOldVal);
+        memcpy(pszTmp, pszValue, cbNewVal + 1);
+        for (unsigned i = uStart + 1; i <= pDescriptor->cLines; i++)
+            pDescriptor->aLines[i] += cbDiff;
+    }
+    else
+    {
+        /* Key doesn't exist, append it after the last entry in this category. */
+        size_t cbKey = strlen(pszKey);
+        size_t cbValue = strlen(pszValue);
+        ssize_t cbDiff = cbKey + 1 + cbValue + 1;
+        /* Check for buffer overflow. */
+        if (   (pDescriptor->cLines >= VMDK_DESCRIPTOR_LINES_MAX - 1)
+            || (  pDescriptor->aLines[pDescriptor->cLines]
+                - pDescriptor->aLines[0] > (ptrdiff_t)pDescriptor->cbDescAlloc - cbDiff))
+            return vmdkError(pImage, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too big"));
+        for (unsigned i = pDescriptor->cLines + 1; i > uLast + 1; i--)
+        {
+            pDescriptor->aLines[i] = pDescriptor->aLines[i - 1];
+            if (pDescriptor->aNextLines[i])
+                pDescriptor->aNextLines[i]++;
+        }
+        uStart = uLast + 1;
+        pDescriptor->aNextLines[uLast] = uStart;
+        pDescriptor->aNextLines[uStart] = 0;
+        pDescriptor->cLines++;
+        pszTmp = pDescriptor->aLines[uStart];
+        memmove(pszTmp + cbDiff, pszTmp,
+                pDescriptor->aLines[pDescriptor->cLines] - pszTmp);
+        memcpy(pDescriptor->aLines[uStart], pszKey, cbKey);
+        pDescriptor->aLines[uStart][cbKey] = '=';
+        memcpy(pDescriptor->aLines[uStart] + cbKey + 1, pszValue, cbValue + 1);
+        for (unsigned i = uStart + 1; i <= pDescriptor->cLines; i++)
+            pDescriptor->aLines[i] += cbDiff;
+    }
+    pDescriptor->fDirty = true;
+    return VINF_SUCCESS;
+}
+
+static int vmdkDescBaseGetU32(PVMDKDESCRIPTOR pDescriptor, const char *pszKey,
+                              uint32_t *puValue)
+{
+    const char *pszValue;
+
+    if (!vmdkDescGetStr(pDescriptor, pDescriptor->uFirstDesc, pszKey, &pszValue))
+        return VERR_VDI_VALUE_NOT_FOUND;
+    return RTStrToUInt32Ex(pszValue, NULL, 10, puValue);
+}
+
+static int vmdkDescDDBGetU32(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+                             const char *pszKey, uint32_t *puValue)
+{
+    const char *pszValue;
+    char *pszValueUnquoted;
+
+    if (!vmdkDescGetStr(pDescriptor, pDescriptor->uFirstDDB, pszKey, &pszValue))
+        return VERR_VDI_VALUE_NOT_FOUND;
+    int rc = vmdkStringUnquote(pImage, pszValue, &pszValueUnquoted, NULL);
+    if (VBOX_FAILURE(rc))
+        return rc;
+    rc = RTStrToUInt32Ex(pszValueUnquoted, NULL, 10, puValue);
+    RTMemTmpFree(pszValueUnquoted);
+    return rc;
+}
+
+static int vmdkDescDDBGetUuid(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor,
+                              const char *pszKey, PRTUUID pUuid)
+{
+    const char *pszValue;
+    char *pszValueUnquoted;
+
+    if (!vmdkDescGetStr(pDescriptor, pDescriptor->uFirstDDB, pszKey, &pszValue))
+        return VERR_VDI_VALUE_NOT_FOUND;
+    int rc = vmdkStringUnquote(pImage, pszValue, &pszValueUnquoted, NULL);
+    if (VBOX_FAILURE(rc))
+        return rc;
+    rc = RTUuidFromStr(pUuid, pszValueUnquoted);
+    RTMemTmpFree(pszValueUnquoted);
+    return rc;
+}
+
+int vmdkDescDDBSetUuid(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor, const char *pszKey, PCRTUUID pUuid)
+{
+    char *pszUuid;
+
+    int rc = RTStrAPrintf(&pszUuid, "\"%Vuuid\"", pUuid);
+    if (VBOX_FAILURE(rc))
+        return rc;
+    rc = vmdkDescSetStr(pImage, pDescriptor, pDescriptor->uFirstDDB, pszKey, pszUuid);
+    RTStrFree(pszUuid);
+    return rc;
+}
+
+int vmdkDescDDBSetU32(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor, const char *pszKey, uint32_t uValue)
+{
+    char *pszValue;
+
+    int rc = RTStrAPrintf(&pszValue, "\"%d\"", uValue);
+    if (VBOX_FAILURE(rc))
+        return rc;
+    rc = vmdkDescSetStr(pImage, pDescriptor, pDescriptor->uFirstDDB, pszKey, pszValue);
+    RTStrFree(pszValue);
+    return rc;
+}
+
+static int vmdkPreprocessDescriptor(PVMDKIMAGE pImage, char *pDescData, size_t cbDescData, PVMDKDESCRIPTOR pDescriptor)
+{
+    int rc = VINF_SUCCESS;
+    unsigned cLine = 0, uLastNonEmptyLine = 0;
+    char *pTmp = pDescData;
+
+    pDescriptor->cbDescAlloc = cbDescData;
+    while (*pTmp != '\0')
+    {
+        pDescriptor->aLines[cLine++] = pTmp;
+        if (cLine >= VMDK_DESCRIPTOR_LINES_MAX)
+        {
+            rc = vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: descriptor too long"));
+            goto out;
+        }
+
+        while (*pTmp != '\0' && *pTmp != '\n')
+        {
+            if (*pTmp == '\r')
+            {
+                if (*(pTmp + 1) != '\n')
+                {
+                    rc = vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: unsupported end of line in descriptor"));
+                    goto out;
+                }
+                else
+                {
+                    /* Get rid of CR character. */
+                    *pTmp = '\0';
+                }
+            }
+            pTmp++;
+        }
+        /* Get rid of LF character. */
+        if (*pTmp == '\n')
+        {
+            *pTmp = '\0';
+            pTmp++;
+        }
+    }
+    pDescriptor->cLines = cLine;
+    /* Pointer right after the end of the used part of the buffer. */
+    pDescriptor->aLines[cLine] = pTmp;
+
+    if (strcmp(pDescriptor->aLines[0], "# Disk DescriptorFile"))
+    {
+        rc = vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: descriptor does not start as expected"));
+        goto out;
+    }
+
+    /* Initialize those, because we need to be able to reopen an image. */
+    pDescriptor->uFirstDesc = 0;
+    pDescriptor->uFirstExtent = 0;
+    pDescriptor->uFirstDDB = 0;
+    for (unsigned i = 0; i < cLine; i++)
+    {
+        if (*pDescriptor->aLines[i] != '#' && *pDescriptor->aLines[i] != '\0')
+        {
+            if (    !strncmp(pDescriptor->aLines[i], "RW", 2)
+                ||  !strncmp(pDescriptor->aLines[i], "RDONLY", 6)
+                ||  !strncmp(pDescriptor->aLines[i], "NOACCESS", 8) )
+            {
+                /* An extent descriptor. */
+                if (!pDescriptor->uFirstDesc || pDescriptor->uFirstDDB)
+                {
+                    /* Incorrect ordering of entries. */
+                    rc = vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect ordering of entries in descriptor"));
+                    goto out;
+                }
+                if (!pDescriptor->uFirstExtent)
+                {
+                    pDescriptor->uFirstExtent = i;
+                    uLastNonEmptyLine = 0;
+                }
+            }
+            else if (!strncmp(pDescriptor->aLines[i], "ddb.", 4))
+            {
+                /* A disk database entry. */
+                if (!pDescriptor->uFirstDesc || !pDescriptor->uFirstExtent)
+                {
+                    /* Incorrect ordering of entries. */
+                    rc = vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect ordering of entries in descriptor"));
+                    goto out;
+                }
+                if (!pDescriptor->uFirstDDB)
+                {
+                    pDescriptor->uFirstDDB = i;
+                    uLastNonEmptyLine = 0;
+                }
+            }
+            else
+            {
+                /* A normal entry. */
+                if (pDescriptor->uFirstExtent || pDescriptor->uFirstDDB)
+                {
+                    /* Incorrect ordering of entries. */
+                    rc = vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect ordering of entries in descriptor"));
+                    goto out;
+                }
+                if (!pDescriptor->uFirstDesc)
+                {
+                    pDescriptor->uFirstDesc = i;
+                    uLastNonEmptyLine = 0;
+                }
+            }
+            if (uLastNonEmptyLine)
+                pDescriptor->aNextLines[uLastNonEmptyLine] = i;
+            uLastNonEmptyLine = i;
+        }
+    }
+
+out:
+    return rc;
+}
+
+static int vmdkParseDescriptor(PVMDKIMAGE pImage, char *pDescData, size_t cbDescData)
+{
+    int rc;
+    unsigned cExtents;
+    unsigned uLine;
+
+    rc = vmdkPreprocessDescriptor(pImage, pDescData, cbDescData, &pImage->Descriptor);
+    if (VBOX_FAILURE(rc))
+        return rc;
+
+    /* Check version, must be 1. */
+    uint32_t uVersion;
+    rc = vmdkDescBaseGetU32(&pImage->Descriptor, "version", &uVersion);
+    if (VBOX_FAILURE(rc))
+        return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error finding key 'version' in descriptor"));
+    if (uVersion != 1)
+        return vmdkError(pImage, VERR_VDI_UNSUPPORTED_VERSION, RT_SRC_POS, N_("VMDK: unsupported format version in descriptor"));
+
+    /* Count the number of extent config entries. */
+    for (uLine = pImage->Descriptor.uFirstExtent, cExtents = 0;
+         uLine != 0;
+         uLine = pImage->Descriptor.aNextLines[uLine], cExtents++)
+        /* nothing */;
+
+    if (!pImage->pDescData && cExtents != 1)
+    {
+        /* Monolithic image, must have only one extent (already opened). */
+        return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: monolithic image may only have one extent"));
+    }
+
+    if (pImage->pDescData)
+    {
+        /* Non-monolithic image, extents need to be allocated. */
+        rc = vmdkCreateExtents(pImage, cExtents);
+        if (VBOX_FAILURE(rc))
+            return rc;
+    }
+
+    for (unsigned i = 0, uLine = pImage->Descriptor.uFirstExtent;
+         i < cExtents; i++, uLine = pImage->Descriptor.aNextLines[uLine])
+    {
+        char *pszLine = pImage->Descriptor.aLines[uLine];
+
+        /* Access type of the extent. */
+        if (!strncmp(pszLine, "RW", 2))
+        {
+            pImage->pExtents[i].enmAccess = VMDKACCESS_READWRITE;
+            pszLine += 2;
+        }
+        else if (!strncmp(pszLine, "RDONLY", 6))
+        {
+            pImage->pExtents[i].enmAccess = VMDKACCESS_READONLY;
+            pszLine += 6;
+        }
+        else if (!strncmp(pszLine, "NOACCESS", 8))
+        {
+            pImage->pExtents[i].enmAccess = VMDKACCESS_NOACCESS;
+            pszLine += 8;
+        }
+        else
+            return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
+        if (*pszLine++ != ' ')
+            return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
+
+        /* Nominal size of the extent. */
+        rc = RTStrToUInt64Ex(pszLine, &pszLine, 10,
+                             &pImage->pExtents[i].cNominalSectors);
+        if (VBOX_FAILURE(rc))
+            return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
+        if (*pszLine++ != ' ')
+            return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
+
+        /* Type of the extent. */
+#ifdef VBOX_WITH_VMDK_ESX
+        /** @todo Add the ESX extent types. Not necessary for now because
+         * the ESX extent types are only used inside an ESX server. They are
+         * automatically converted if the VMDK is exported. */
+#endif /* VBOX_WITH_VMDK_ESX */
+        if (!strncmp(pszLine, "SPARSE", 6))
+        {
+            pImage->pExtents[i].enmType = VMDKETYPE_HOSTED_SPARSE;
+            pszLine += 6;
+        }
+        else if (!strncmp(pszLine, "FLAT", 4))
+        {
+            pImage->pExtents[i].enmType = VMDKETYPE_FLAT;
+            pszLine += 4;
+        }
+        else if (!strncmp(pszLine, "ZERO", 4))
+        {
+            pImage->pExtents[i].enmType = VMDKETYPE_ZERO;
+            pszLine += 4;
+        }
+        else
+            return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
+        if (pImage->pExtents[i].enmType == VMDKETYPE_ZERO)
+        {
+            /* This one has no basename or offset. */
+            if (*pszLine == ' ')
+                pszLine++;
+            if (*pszLine != '\0')
+                return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
+            pImage->pExtents[i].pszBasename = NULL;
+        }
+        else
+        {
+            /* All other extent types have basename and optional offset. */
+            if (*pszLine++ != ' ')
+                return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
+
+            /* Basename of the image. Surrounded by quotes. */
+            rc = vmdkStringUnquote(pImage, pszLine,
+                                   &pImage->pExtents[i].pszBasename, &pszLine);
+            if (VBOX_FAILURE(rc))
+                return rc;
+            if (*pszLine == ' ')
+            {
+                pszLine++;
+                if (*pszLine != '\0')
+                {
+                    /* Optional offset in extent specified. */
+                    rc = RTStrToUInt64Ex(pszLine, &pszLine, 10,
+                                         &pImage->pExtents[i].uSectorOffset);
+                    if (VBOX_FAILURE(rc))
+                        return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
+                }
+            }
+
+            if (*pszLine != '\0')
+                return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
+        }
+    }
+
+    rc = vmdkDescDDBGetU32(pImage, &pImage->Descriptor,
+                           "ddb.geometry.cylinders", &pImage->cCylinders);
+    if (VBOX_FAILURE(rc))
+        return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error getting CHS geometry from extent description"));
+    rc = vmdkDescDDBGetU32(pImage, &pImage->Descriptor,
+                           "ddb.geometry.heads", &pImage->cHeads);
+    if (VBOX_FAILURE(rc))
+        return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error getting CHS geometry from extent description"));
+    rc = vmdkDescDDBGetU32(pImage, &pImage->Descriptor,
+                           "ddb.geometry.sectors", &pImage->cSectors);
+    if (VBOX_FAILURE(rc))
+        return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error getting CHS geometry from extent description"));
+    if (pImage->cCylinders >= 1024 || pImage->cHeads != 16)
+        pImage->enmTranslation = PDMBIOSTRANSLATION_LBA;
+    else
+        pImage->enmTranslation = PDMBIOSTRANSLATION_NONE;
+
+    /* Get image UUID. */
+    rc = vmdkDescDDBGetUuid(pImage, &pImage->Descriptor, "ddb.uuid.image",
+                            &pImage->ImageUuid);
+    if (rc == VERR_VDI_VALUE_NOT_FOUND)
+    {
+        /* Image without UUID. Probably created by VMware and not yet used
+         * by VirtualBox. Can only be added for images opened in read/write
+         * mode, so don't bother producing a sensible UUID otherwise. */
+        if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+            RTUuidClear(&pImage->ImageUuid);
+        else
+        {
+            rc = RTUuidCreate(&pImage->ImageUuid);
+            if (VBOX_FAILURE(rc))
+                return rc;
+            rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+                                    "ddb.uuid.image", &pImage->ImageUuid);
+            if (VBOX_FAILURE(rc))
+                return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing image UUID in descriptor"));
+        }
+    }
+    else if (VBOX_FAILURE(rc))
+        return rc;
+
+    /* Get image modification UUID. */
+    rc = vmdkDescDDBGetUuid(pImage, &pImage->Descriptor, "ddb.uuid.modification",
+                            &pImage->ModificationUuid);
+    if (rc == VERR_VDI_VALUE_NOT_FOUND)
+    {
+        /* Image without UUID. Probably created by VMware and not yet used
+         * by VirtualBox. Can only be added for images opened in read/write
+         * mode, so don't bother producing a sensible UUID otherwise. */
+        if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+            RTUuidClear(&pImage->ModificationUuid);
+        else
+        {
+            rc = RTUuidCreate(&pImage->ModificationUuid);
+            if (VBOX_FAILURE(rc))
+                return rc;
+            rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+                                    "ddb.uuid.modification", &pImage->ModificationUuid);
+            if (VBOX_FAILURE(rc))
+                return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing image modification UUID in descriptor"));
+        }
+    }
+    else if (VBOX_FAILURE(rc))
+        return rc;
+
+    /* Get UUID of parent image. */
+    rc = vmdkDescDDBGetUuid(pImage, &pImage->Descriptor, "ddb.uuid.parent",
+                            &pImage->ParentUuid);
+    if (rc == VERR_VDI_VALUE_NOT_FOUND)
+    {
+        /* Image without UUID. Probably created by VMware and not yet used
+         * by VirtualBox. Can only be added for images opened in read/write
+         * mode, so don't bother producing a sensible UUID otherwise. */
+        if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+            RTUuidClear(&pImage->ParentUuid);
+        else
+        {
+            rc = RTUuidClear(&pImage->ParentUuid);
+            if (VBOX_FAILURE(rc))
+                return rc;
+            rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+                                    "ddb.uuid.parent", &pImage->ParentUuid);
+            if (VBOX_FAILURE(rc))
+                return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing parent UUID in descriptor"));
+        }
+    }
+    else if (VBOX_FAILURE(rc))
+        return rc;
+
+    return VINF_SUCCESS;
+}
+
+static int vmdkWriteDescriptor(PVMDKIMAGE pImage)
+{
+    int rc = VINF_SUCCESS;
+    uint64_t cbLimit;
+    uint64_t uOffset;
+    RTFILE DescFile;
+
+    if (pImage->pDescData)
+    {
+        /* Separate descriptor file. */
+        uOffset = 0;
+        cbLimit = 0;
+        DescFile = pImage->File;
+    }
+    else
+    {
+        /* Embedded descriptor file. */
+        uOffset = VMDK_SECTOR2BYTE(pImage->pExtents[0].uDescriptorSector);
+        cbLimit = VMDK_SECTOR2BYTE(pImage->pExtents[0].cDescriptorSectors);
+        cbLimit += uOffset;
+        DescFile = pImage->pExtents[0].File;
+    }
+    for (unsigned i = 0; i < pImage->Descriptor.cLines; i++)
+    {
+        const char *psz = pImage->Descriptor.aLines[i];
+        size_t cb = strlen(psz);
+
+        if (cbLimit && uOffset + cb + 1 > cbLimit)
+            return vmdkError(pImage, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too long"));
+        rc = RTFileWriteAt(DescFile, uOffset, psz, cb, NULL);
+        if (VBOX_FAILURE(rc))
+            return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error writing descriptor for '%s'"), pImage->pszFilename);
+        uOffset += cb;
+        rc = RTFileWriteAt(DescFile, uOffset, "\n", 1, NULL);
+        if (VBOX_FAILURE(rc))
+            return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error writing descriptor for '%s'"), pImage->pszFilename);
+        uOffset++;
+    }
+    if (cbLimit)
+    {
+        /* Inefficient, but simple. */
+        while (uOffset < cbLimit)
+        {
+            rc = RTFileWriteAt(DescFile, uOffset, "", 1, NULL);
+            if (VBOX_FAILURE(rc))
+                return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error writing descriptor for '%s'"), pImage->pszFilename);
+            uOffset++;
+        }
+    }
+    else
+    {
+        rc = RTFileSetSize(DescFile, uOffset);
+        return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error truncating descriptor file '%s'"), pImage->pszFilename);
+    }
+    pImage->Descriptor.fDirty = false;
+    return rc;
+}
+
+static int vmdkReadMetaSparseExtent(PVMDKEXTENT pExtent)
+{
+    SparseExtentHeader Header;
+    uint64_t cbExtentSize, cSectorsPerGDE;
+
+    int rc = RTFileReadAt(pExtent->File, 0, &Header, sizeof(Header), NULL);
+    AssertRC(rc);
+    if (VBOX_FAILURE(rc))
+    {
+        rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error reading extent header of file '%s'"), pExtent->pszFullname);
+        goto out;
+    }
+    if (    RT_LE2H_U32(Header.magicNumber) != VMDK_SPARSE_MAGICNUMBER
+        ||  RT_LE2H_U32(Header.version) != 1)
+    {
+        rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect magic/version in extent header of file '%s'"), pExtent->pszFullname);
+        goto out;
+    }
+    /* The image must be a multiple of a sector in size. If not, it means the
+     * image is at least truncated, or even seriously garbled. */
+    rc = RTFileGetSize(pExtent->File, &cbExtentSize);
+    if (VBOX_FAILURE(rc))
+    {
+        rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error getting size of file '%s'"), pExtent->pszFullname);
+        goto out;
+    }
+    if (    (RT_LE2H_U32(Header.flags) & 1)
+        &&  (   Header.singleEndLineChar != '\n'
+             || Header.nonEndLineChar != ' '
+             || Header.doubleEndLineChar1 != '\r'
+             || Header.doubleEndLineChar2 != '\n') )
+    {
+        rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: file '%s' is corrupted by CR/LF translation"), pExtent->pszFullname);
+        goto out;
+    }
+    pExtent->enmType = VMDKETYPE_HOSTED_SPARSE;
+    pExtent->cSectors = RT_LE2H_U64(Header.capacity);
+    pExtent->cSectorsPerGrain = RT_LE2H_U64(Header.grainSize);
+    /* The spec says that this must be a power of two and greater than 8,
+     * but probably they meant not less than 8. */
+    if (    (pExtent->cSectorsPerGrain & (pExtent->cSectorsPerGrain - 1))
+        ||  pExtent->cSectorsPerGrain < 8)
+    {
+        rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: invalid extent grain size %u for file '%s'"), pExtent->cSectorsPerGrain, pExtent->pszFullname);
+        goto out;
+    }
+    pExtent->uDescriptorSector = RT_LE2H_U64(Header.descriptorOffset);
+    pExtent->cDescriptorSectors = RT_LE2H_U64(Header.descriptorSize);
+    if (pExtent->uDescriptorSector && !pExtent->cDescriptorSectors)
+    {
+        rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: inconsistent embedded descriptor config for file '%s'"), pExtent->pszFullname);
+        goto out;
+    }
+    pExtent->cGTEntries = RT_LE2H_U32(Header.numGTEsPerGT);
+    /* This code requires that a grain table must hold a power of two multiple
+     * of the number of entries per GT cache entry. */
+    if (    (pExtent->cGTEntries & (pExtent->cGTEntries - 1))
+        ||  pExtent->cGTEntries < VMDK_GT_CACHELINE_SIZE)
+    {
+        rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: grain table cache size problem for file '%s'"), pExtent->pszFullname);
+        goto out;
+    }
+    if (RT_LE2H_U32(Header.flags) & 2)
+    {
+        pExtent->uSectorRGD = RT_LE2H_U64(Header.rgdOffset);
+        pExtent->uSectorGD = RT_LE2H_U64(Header.gdOffset);
+    }
+    else
+    {
+        /** @todo this is just guesswork, the spec doesn't document this
+         * properly and I don't have a vmdk without RGD. */
+        pExtent->uSectorGD = RT_LE2H_U64(Header.rgdOffset);
+        pExtent->uSectorRGD = 0;
+    }
+    pExtent->uOverheadSectors = RT_LE2H_U64(Header.overHead);
+    pExtent->fUncleanShutdown = !!Header.uncleanShutdown;
+    cSectorsPerGDE = pExtent->cGTEntries * pExtent->cSectorsPerGrain;
+    if (!cSectorsPerGDE || cSectorsPerGDE > UINT32_MAX)
+    {
+        rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect grain directory size for file '%s'"), pExtent->pszFullname);
+        goto out;
+    }
+    pExtent->cSectorsPerGDE = cSectorsPerGDE;
+    pExtent->cGDEntries = (pExtent->cSectors + cSectorsPerGDE - 1) / cSectorsPerGDE;
+
+    rc = vmdkReadGrainDirectory(pExtent);
+
+out:
+    if (VBOX_FAILURE(rc))
+    {
+        vmdkFreeExtentData(pExtent);
+    }
+
+    return rc;
+}
+
+static int vmdkWriteMetaSparseExtent(PVMDKEXTENT pExtent)
+{
+    SparseExtentHeader Header;
+
+    memset(&Header, '\0', sizeof(Header));
+    Header.magicNumber = RT_H2LE_U32(VMDK_SPARSE_MAGICNUMBER);
+    Header.version = RT_H2LE_U32(1);
+    Header.flags = RT_H2LE_U32(1 | ((pExtent->pRGD) ? 2 : 0));
+    Header.capacity = RT_H2LE_U64(pExtent->cSectors);
+    Header.grainSize = RT_H2LE_U64(pExtent->cSectorsPerGrain);
+    Header.descriptorOffset = RT_H2LE_U64(pExtent->uDescriptorSector);
+    Header.descriptorSize = RT_H2LE_U64(pExtent->cDescriptorSectors);
+    Header.numGTEsPerGT = RT_H2LE_U32(pExtent->cGTEntries);
+    if (pExtent->pRGD)
+    {
+        Header.rgdOffset = RT_H2LE_U64(pExtent->uSectorRGD);
+        Header.gdOffset = RT_H2LE_U64(pExtent->uSectorGD);
+    }
+    else
+    {
+        /** @todo this is just guesswork, the spec doesn't document this
+         * properly and I don't have a vmdk without RGD. */
+        Header.rgdOffset = RT_H2LE_U64(pExtent->uSectorGD);
+    }
+    Header.overHead = RT_H2LE_U64(pExtent->uOverheadSectors);
+    Header.uncleanShutdown = pExtent->fUncleanShutdown;
+    Header.singleEndLineChar = '\n';
+    Header.nonEndLineChar = ' ';
+    Header.doubleEndLineChar1 = '\r';
+    Header.doubleEndLineChar2 = '\n';
+
+    int rc = RTFileWriteAt(pExtent->File, 0, &Header, sizeof(Header), NULL);
+    AssertRC(rc);
+    if (VBOX_FAILURE(rc))
+        rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error writing extent header to file '%s'"), pExtent->pszFullname);
+    return rc;
+}
+
+#ifdef VBOX_WITH_VMDK_ESX
+static int vmdkReadMetaESXSparseExtent(PVMDKEXTENT pExtent)
+{
+    COWDisk_Header Header;
+    uint64_t cSectorsPerGDE;
+
+    int rc = RTFileReadAt(pExtent->File, 0, &Header, sizeof(Header), NULL);
+    AssertRC(rc);
+    if (VBOX_FAILURE(rc))
+        goto out;
+    if (    RT_LE2H_U32(Header.magicNumber) != VMDK_ESX_SPARSE_MAGICNUMBER
+        ||  RT_LE2H_U32(Header.version) != 1
+        ||  RT_LE2H_U32(Header.flags) != 3)
+    {
+        rc = VERR_VDI_INVALID_HEADER;
+        goto out;
+    }
+    pExtent->enmType = VMDKETYPE_ESX_SPARSE;
+    pExtent->cSectors = RT_LE2H_U32(Header.numSectors);
+    pExtent->cSectorsPerGrain = RT_LE2H_U32(Header.grainSize);
+    /* The spec says that this must be between 1 sector and 1MB. This code
+     * assumes it's a power of two, so check that requirement, too. */
+    if (    (pExtent->cSectorsPerGrain & (pExtent->cSectorsPerGrain - 1))
+        ||  pExtent->cSectorsPerGrain == 0
+        ||  pExtent->cSectorsPerGrain > 2048)
+    {
+        rc = VERR_VDI_INVALID_HEADER;
+        goto out;
+    }
+    pExtent->uDescriptorSector = 0;
+    pExtent->cDescriptorSectors = 0;
+    pExtent->uSectorGD = RT_LE2H_U32(Header.gdOffset);
+    pExtent->uSectorRGD = 0;
+    pExtent->uOverheadSectors = 0;
+    pExtent->cGTEntries = 4096;
+    cSectorsPerGDE = pExtent->cGTEntries * pExtent->cSectorsPerGrain;
+    if (!cSectorsPerGDE || cSectorsPerGDE > UINT32_MAX)
+    {
+        rc = VERR_VDI_INVALID_HEADER;
+        goto out;
+    }
+    pExtent->cSectorsPerGDE = cSectorsPerGDE;
+    pExtent->cGDEntries = (pExtent->cSectors + cSectorsPerGDE - 1) / cSectorsPerGDE;
+    if (pExtent->cGDEntries != RT_LE2H_U32(Header.numGDEntries))
+    {
+        /* Inconsistency detected. Computed number of GD entries doesn't match
+         * stored value. Better be safe than sorry. */
+        rc = VERR_VDI_INVALID_HEADER;
+        goto out;
+    }
+    pExtent->uFreeSector = RT_LE2H_U32(Header.freeSector);
+    pExtent->fUncleanShutdown = !!Header.uncleanShutdown;
+
+    rc = vmdkReadGrainDirectory(pExtent);
+
+out:
+    if (VBOX_FAILURE(rc))
+    {
+        vmdkFreeExtentData(pExtent);
+    }
+
+    return rc;
+}
+#endif /* VBOX_WITH_VMDK_ESX */
+
+static void vmdkFreeExtentData(PVMDKEXTENT pExtent)
+{
+    vmdkFreeGrainDirectory(pExtent);
+    if (pExtent->pDescData)
+    {
+        RTMemFree(pExtent->pDescData);
+        pExtent->pDescData = NULL;
+    }
+    if (pExtent->pszFullname)
+    {
+        RTStrFree(pExtent->pszFullname);
+        pExtent->pszFullname = NULL;
+    }
+    if (pExtent->File != NIL_RTFILE)
+    {
+        RTFileClose(pExtent->File);
+        pExtent->File = NIL_RTFILE;
+    }
+}
+
+static int vmdkCreateExtents(PVMDKIMAGE pImage, unsigned cExtents)
+{
+    int rc = VINF_SUCCESS;
+    PVMDKEXTENT pExtents = (PVMDKEXTENT)RTMemAllocZ(cExtents * sizeof(VMDKEXTENT));
+    if (pImage)
+    {
+        for (unsigned i = 0; i < cExtents; i++)
+        {
+            pExtents[i].File = NIL_RTFILE;
+            pExtents[i].pszBasename = NULL;
+            pExtents[i].pszFullname = NULL;
+            pExtents[i].pGD = NULL;
+            pExtents[i].pRGD = NULL;
+            pExtents[i].pDescData = NULL;
+            pExtents[i].uExtent = i;
+            pExtents[i].pImage = pImage;
+        }
+        pImage->pExtents = pExtents;
+        pImage->cExtents = cExtents;
+    }
+    else
+        rc = VERR_NO_MEMORY;
+
+    return rc;
+}
+
+static int vmdkOpenImage(PVMDKIMAGE pImage, const char *pszFilename, unsigned uOpenFlags)
+{
+    int rc = VINF_SUCCESS;
+    uint32_t u32Magic;
+    RTFILE File;
+    PVMDKEXTENT pExtent;
+
+    pImage->uOpenFlags = uOpenFlags;
+
+    /** @todo check the image file name for invalid characters, especially double quotes. */
+
+    /** @todo check whether the same file is used somewhere else. don't open any file twice, leads to locking problems and can cause trouble with file caching. */
+
+    /*
+     * Open the image.
+     */
+    rc = RTFileOpen(&File, pszFilename, uOpenFlags & VD_OPEN_FLAGS_READONLY
+                    ? RTFILE_O_READ      | RTFILE_O_OPEN | RTFILE_O_DENY_NONE
+                    : RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+    if (VBOX_FAILURE(rc))
+    {
+        /* Do NOT signal an appropriate error here, as the VD layer has the
+         * choice of retrying the open if it failed. */
+        goto out;
+    }
+    pImage->File = File;
+    rc = RTFileReadAt(File, 0, &u32Magic, sizeof(u32Magic), NULL);
+    AssertRC(rc);
+    if (VBOX_FAILURE(rc))
+    {
+        rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error reading the magic number from file '%s'"), pszFilename);
+        goto out;
+    }
+
+    /* Handle the file according to its magic number. */
+    if (RT_LE2H_U32(u32Magic) == VMDK_SPARSE_MAGICNUMBER)
+    {
+        /* It's a hosted sparse single-extent image. */
+        rc = vmdkCreateExtents(pImage, 1);
+        if (VBOX_FAILURE(rc))
+            goto out;
+        /* The opened file is passed to the extent. No separate descriptor
+         * file, so no need to keep anything open for the image. */
+        pExtent = &pImage->pExtents[0];
+        pExtent->File = File;
+        pImage->File = NIL_RTFILE;
+        rc = vmdkReadMetaSparseExtent(pExtent);
+        if (VBOX_FAILURE(rc))
+            goto out;
+        /* As we're dealing with a monolithic sparse image here, there must
+         * be a descriptor embedded in the image file. */
+        if (!pExtent->uDescriptorSector || !pExtent->cDescriptorSectors)
+        {
+            rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: monolithic image without descriptor in file '%s'"), pszFilename);
+            goto out;
+        }
+        /* Read the descriptor from the extent. */
+        pExtent->pDescData = (char *)RTMemAllocZ(VMDK_SECTOR2BYTE(pExtent->cDescriptorSectors));
+        if (!pExtent->pDescData)
+        {
+            rc = VERR_NO_MEMORY;
+            goto out;
+        }
+        rc = RTFileReadAt(pExtent->File,
+                          VMDK_SECTOR2BYTE(pExtent->uDescriptorSector),
+                          pExtent->pDescData,
+                          VMDK_SECTOR2BYTE(pExtent->cDescriptorSectors), NULL);
+        AssertRC(rc);
+        if (VBOX_FAILURE(rc))
+        {
+            rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: read error for descriptor in file '%s'"), pExtent->pszFullname);
+            goto out;
+        }
+
+        rc = vmdkParseDescriptor(pImage, pExtent->pDescData,
+                                 VMDK_SECTOR2BYTE(pExtent->cDescriptorSectors));
+        if (VBOX_FAILURE(rc))
+            goto out;
+
+        /* Mark the extent as unclean if opened in read-write mode. */
+        if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
+        {
+            pExtent->fUncleanShutdown = true;
+            pExtent->fMetaDirty = true;
+        }
+    }
+    else
+    {
+        pImage->cbDescAlloc = VMDK_SECTOR2BYTE(20);
+        pImage->pDescData = (char *)RTMemAllocZ(pImage->cbDescAlloc);
+        if (!pImage->pDescData)
+        {
+            rc = VERR_NO_MEMORY;
+            goto out;
+        }
+
+        size_t cbRead;
+        rc = RTFileReadAt(pImage->File, 0, pImage->pDescData,
+                          pImage->cbDescAlloc, &cbRead);
+        if (VBOX_FAILURE(rc))
+        {
+            rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: read error for descriptor in file '%s'"), pExtent->pszFullname);
+            goto out;
+        }
+        if (cbRead == pImage->cbDescAlloc)
+        {
+            /* Likely the read is truncated. Better fail a bit too early
+             * (normally the descriptor is much smaller than our buffer). */
+            rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: cannot read descriptor in file '%s'"), pExtent->pszFullname);
+            goto out;
+        }
+
+        rc = vmdkParseDescriptor(pImage, pImage->pDescData, pImage->cbDescAlloc);
+        if (VBOX_FAILURE(rc))
+            goto out;
+
+        for (unsigned i = 0; i < pImage->cExtents; i++)
+        {
+            PVMDKEXTENT pExtent = &pImage->pExtents[i];
+
+            if (pExtent->pszBasename)
+            {
+                /* Hack to figure out whether the specified name in the
+                 * extent descriptor is absolute. Doesn't always work, but
+                 * should be good enough for now. */
+                /** @todo implement proper path absolute check. */
+                if (pExtent->pszBasename[0] == RTPATH_SLASH)
+                {
+                    pszFilename = RTStrDup(pExtent->pszBasename);
+                    if (!pszFilename)
+                    {
+                        rc = VERR_NO_MEMORY;
+                        goto out;
+                    }
+                }
+                else
+                {
+                    size_t cbDirname;
+                    char *pszDirname = RTStrDup(pImage->pszFilename);
+                    if (!pszDirname)
+                    {
+                        rc = VERR_NO_MEMORY;
+                        goto out;
+                    }
+                    RTPathStripFilename(pszDirname);
+                    cbDirname = strlen(pszDirname);
+                    char *pszFullname;
+                    rc = RTStrAPrintf(&pszFullname, "%s%c%s", pszDirname,
+                                      RTPATH_SLASH, pExtent->pszBasename);
+                    RTStrFree(pszDirname);
+                    if (VBOX_FAILURE(rc))
+                        goto out;
+                    pExtent->pszFullname = pszFullname;
+                }
+            }
+            else
+                pExtent->pszFullname = NULL;
+
+            switch (pExtent->enmType)
+            {
+                case VMDKETYPE_HOSTED_SPARSE:
+                    rc = RTFileOpen(&pExtent->File, pExtent->pszFullname,
+                                    uOpenFlags & VD_OPEN_FLAGS_READONLY
+                                      ? RTFILE_O_READ      | RTFILE_O_OPEN | RTFILE_O_DENY_NONE
+                                      : RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+                    if (VBOX_FAILURE(rc))
+                    {
+                        /* Do NOT signal an appropriate error here, as the VD
+                         * layer has the choice of retrying the open if it
+                         * failed. */
+                        goto out;
+                    }
+                    rc = vmdkReadMetaSparseExtent(pExtent);
+                    if (VBOX_FAILURE(rc))
+                        goto out;
+
+                    /* Mark the extent as unclean if opened in read-write mode. */
+                    if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
+                    {
+                        pExtent->fUncleanShutdown = true;
+                        pExtent->fMetaDirty = true;
+                    }
+                    break;
+                case VMDKETYPE_FLAT:
+                    rc = RTFileOpen(&pExtent->File, pExtent->pszFullname,
+                                    uOpenFlags & VD_OPEN_FLAGS_READONLY
+                                      ? RTFILE_O_READ      | RTFILE_O_OPEN | RTFILE_O_DENY_NONE
+                                      : RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+                    if (VBOX_FAILURE(rc))
+                    {
+                        /* Do NOT signal an appropriate error here, as the VD
+                         * layer has the choice of retrying the open if it
+                         * failed. */
+                        goto out;
+                    }
+                    break;
+                case VMDKETYPE_ZERO:
+                    /* Nothing to do. */
+                    break;
+                default:
+                    AssertMsgFailed(("unknown vmdk extent type %d\n", pExtent->enmType));
+            }
+        }
+    }
+
+    /* Make sure this is not reached accidentally with an error status. */
+    AssertRC(rc);
+
+    /* Update the image metadata now in case has changed. */
+    rc = vmdkFlushImage(pImage);
+    if (VBOX_FAILURE(rc))
+        goto out;
+
+    /* Figure out a few per-image constants from the extents. */
+    pImage->cbSize = 0;
+    for (unsigned i = 0; i < pImage->cExtents; i++)
+    {
+        pExtent = &pImage->pExtents[i];
+        if (    pExtent->enmType == VMDKETYPE_HOSTED_SPARSE
+#ifdef VBOX_WITH_VMDK_ESX
+            ||  pExtent->enmType == VMDKETYPE_ESX_SPARSE
+#endif /* VBOX_WITH_VMDK_ESX */
+           )
+        {
+            /* Here used to be a check whether the nominal size of an extent
+             * is a multiple of the grain size. The spec says that this is
+             * always the case, but unfortunately some files out there in the
+             * wild violate the spec (e.g. ReactOS 0.3.1). */
+        }
+        pImage->cbSize += VMDK_SECTOR2BYTE(pExtent->cNominalSectors);
+    }
+
+    pImage->enmImageType = VD_IMAGE_TYPE_NORMAL;
+    for (unsigned i = 0; i < pImage->cExtents; i++)
+    {
+        pExtent = &pImage->pExtents[i];
+        if (    pImage->pExtents[i].enmType == VMDKETYPE_FLAT
+            ||  pImage->pExtents[i].enmType == VMDKETYPE_ZERO)
+        {
+            pImage->enmImageType = VD_IMAGE_TYPE_FIXED;
+            break;
+        }
+    }
+
+    /* Allocate grain table cache if any sparse extent is present. */
+    for (unsigned i = 0; i < pImage->cExtents; i++)
+    {
+        pExtent = &pImage->pExtents[i];
+        if (    pExtent->enmType == VMDKETYPE_HOSTED_SPARSE
+#ifdef VBOX_WITH_VMDK_ESX
+            ||  pExtent->enmType == VMDKETYPE_ESX_SPARSE
+#endif /* VBOX_WITH_VMDK_ESX */
+           )
+        {
+            /* Allocate grain table cache. */
+            pImage->pGTCache = (PVMDKGTCACHE)RTMemAllocZ(sizeof(VMDKGTCACHE));
+            if (!pImage->pGTCache)
+            {
+                rc = VERR_NO_MEMORY;
+                goto out;
+            }
+            for (unsigned i = 0; i < VMDK_GT_CACHE_SIZE; i++)
+            {
+                PVMDKGTCACHEENTRY pGCE = &pImage->pGTCache->aGTCache[i];
+                pGCE->uExtent = UINT32_MAX;
+            }
+            pImage->pGTCache->cEntries = VMDK_GT_CACHE_SIZE;
+            break;
+        }
+    }
+
+out:
+    if (VBOX_FAILURE(rc))
+        vmdkFreeImage(pImage);
+    return rc;
+}
+
+static void vmdkFreeImage(PVMDKIMAGE pImage)
+{
+    if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+    {
+        /* Mark all extents as clean. */
+        for (unsigned i = 0; i < pImage->cExtents; i++)
+        {
+            if ((   pImage->pExtents[i].enmType == VMDKETYPE_HOSTED_SPARSE
+#ifdef VBOX_WITH_VMDK_ESX
+                 || pImage->pExtents[i].enmType == VMDKETYPE_ESX_SPARSE
+#endif /* VBOX_WITH_VMDK_ESX */
+                )
+                &&  pImage->pExtents[i].fUncleanShutdown)
+            {
+                pImage->pExtents[i].fUncleanShutdown = false;
+                pImage->pExtents[i].fMetaDirty = true;
+            }
+        }
+    }
+    (void)vmdkFlushImage(pImage);
+    if (pImage->pExtents != NULL)
+    {
+        for (unsigned i = 0 ; i < pImage->cExtents; i++)
+            vmdkFreeExtentData(&pImage->pExtents[i]);
+        RTMemFree(pImage->pExtents);
+        pImage->pExtents = NULL;
+    }
+    if (pImage->File != NIL_RTFILE)
+    {
+        RTFileClose(pImage->File);
+        pImage->File = NIL_RTFILE;
+    }
+}
+
+static int vmdkFlushImage(PVMDKIMAGE pImage)
+{
+    PVMDKEXTENT pExtent;
+    int rc = VINF_SUCCESS;
+
+    /* Update descriptor if changed. */
+    if (pImage->Descriptor.fDirty)
+    {
+        rc = vmdkWriteDescriptor(pImage);
+        if (VBOX_FAILURE(rc))
+            goto out;
+    }
+
+    for (unsigned i = 0; i < pImage->cExtents; i++)
+    {
+        pExtent = &pImage->pExtents[i];
+        if (pExtent->File != NIL_RTFILE && pExtent->fMetaDirty)
+        {
+            switch (pExtent->enmType)
+            {
+                case VMDKETYPE_HOSTED_SPARSE:
+                    rc = vmdkWriteMetaSparseExtent(pExtent);
+                    if (VBOX_FAILURE(rc))
+                        goto out;
+                    break;
+#ifdef VBOX_WITH_VMDK_ESX
+                case VMDKETYPE_ESX_SPARSE:
+                    /** @todo update the header. */
+                    break;
+#endif /* VBOX_WITH_VMDK_ESX */
+                case VMDKETYPE_FLAT:
+                    /* Nothing to do. */
+                    break;
+                case VMDKETYPE_ZERO:
+                default:
+                    AssertMsgFailed(("extent with type %d marked as dirty\n",
+                                     pExtent->enmType));
+                    break;
+            }
+        }
+        switch (pExtent->enmType)
+        {
+            case VMDKETYPE_HOSTED_SPARSE:
+#ifdef VBOX_WITH_VMDK_ESX
+            case VMDKETYPE_ESX_SPARSE:
+#endif /* VBOX_WITH_VMDK_ESX */
+            case VMDKETYPE_FLAT:
+                if (pExtent->File != NIL_RTFILE)
+                    rc = RTFileFlush(pExtent->File);
+                break;
+            case VMDKETYPE_ZERO:
+                /* No need to do anything for this extent. */
+                break;
+            default:
+                AssertMsgFailed(("unknown extent type %d\n", pExtent->enmType));
+                break;
+        }
+    }
+
+out:
+    return rc;
+}
+
+static int vmdkFindExtent(PVMDKIMAGE pImage, uint64_t offSector, PVMDKEXTENT *ppExtent, uint64_t *puSectorInExtent)
+{
+    PVMDKEXTENT pExtent = NULL;
+    int rc = VINF_SUCCESS;
+
+    for (unsigned i = 0; i < pImage->cExtents; i++)
+    {
+        if (offSector < pImage->pExtents[i].cNominalSectors)
+        {
+            pExtent = &pImage->pExtents[i];
+            *puSectorInExtent = offSector + pImage->pExtents[i].uSectorOffset;
+            break;
+        }
+        offSector -= pImage->pExtents[i].cNominalSectors;
+    }
+
+    if (pExtent)
+        *ppExtent = pExtent;
+    else
+        rc = VERR_IO_SECTOR_NOT_FOUND;
+
+    return rc;
+}
+
+static uint32_t vmdkGTCacheHash(PVMDKGTCACHE pCache, uint64_t uSector, unsigned uExtent)
+{
+    /** @todo this hash function is quite simple, maybe use a better one which
+     * scrambles the bits better. */
+    return (uSector + uExtent) % pCache->cEntries;
+}
+
+static int vmdkGetSector(PVMDKGTCACHE pCache, PVMDKEXTENT pExtent,
+                         uint64_t uSector, uint64_t *puExtentSector)
+{
+    uint64_t uGDIndex, uGTSector, uGTBlock;
+    uint32_t uGTHash, uGTBlockIndex;
+    PVMDKGTCACHEENTRY pGTCacheEntry;
+    uint32_t aGTDataTmp[VMDK_GT_CACHELINE_SIZE];
+    int rc;
+
+    uGDIndex = uSector / pExtent->cSectorsPerGDE;
+    if (uGDIndex >= pExtent->cGDEntries)
+        return VERR_OUT_OF_RANGE;
+    uGTSector = pExtent->pGD[uGDIndex];
+    if (!uGTSector)
+    {
+        /* There is no grain table referenced by this grain directory
+         * entry. So there is absolutely no data in this area. */
+        *puExtentSector = 0;
+        return VINF_SUCCESS;
+    }
+
+    uGTBlock = uSector / (pExtent->cSectorsPerGrain * VMDK_GT_CACHELINE_SIZE);
+    uGTHash = vmdkGTCacheHash(pCache, uGTBlock, pExtent->uExtent);
+    pGTCacheEntry = &pCache->aGTCache[uGTHash];
+    if (    pGTCacheEntry->uExtent != pExtent->uExtent
+        ||  pGTCacheEntry->uGTBlock != uGTBlock)
+    {
+        /* Cache miss, fetch data from disk. */
+        rc = RTFileReadAt(pExtent->File,
+                          VMDK_SECTOR2BYTE(uGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
+                          aGTDataTmp, sizeof(aGTDataTmp), NULL);
+        if (VBOX_FAILURE(rc))
+            return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot read grain table entry in file '%s'"), pExtent->pszFullname);
+        pGTCacheEntry->uExtent = pExtent->uExtent;
+        pGTCacheEntry->uGTBlock = uGTBlock;
+        for (unsigned i = 0; i < VMDK_GT_CACHELINE_SIZE; i++)
+            pGTCacheEntry->aGTData[i] = RT_LE2H_U32(aGTDataTmp[i]);
+    }
+    uGTBlockIndex = (uSector / pExtent->cSectorsPerGrain) % VMDK_GT_CACHELINE_SIZE;
+    uint64_t uGrainSector = pGTCacheEntry->aGTData[uGTBlockIndex];
+    if (uGrainSector)
+        *puExtentSector = uGrainSector + uSector % pExtent->cSectorsPerGrain;
+    else
+        *puExtentSector = 0;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Internal. Allocates a new grain table (if necessary), writes the grain
+ * and updates the grain table. The cache is also updated by this operation.
+ * This is separate from vmdkGetSector, because that should be as fast as
+ * possible. Most code from vmdkGetSector also appears here.
+ */
+static int vmdkAllocGrain(PVMDKGTCACHE pCache, PVMDKEXTENT pExtent,
+                          uint64_t uSector, const void *pvBuf, uint64_t cbWrite)
+{
+    uint64_t uGDIndex, uGTSector, uRGTSector, uGTBlock;
+    uint64_t cbExtentSize;
+    uint32_t uGTHash, uGTBlockIndex;
+    PVMDKGTCACHEENTRY pGTCacheEntry;
+    uint32_t aGTDataTmp[VMDK_GT_CACHELINE_SIZE];
+    int rc;
+
+    uGDIndex = uSector / pExtent->cSectorsPerGDE;
+    if (uGDIndex >= pExtent->cGDEntries)
+        return VERR_OUT_OF_RANGE;
+    uGTSector = pExtent->pGD[uGDIndex];
+    uRGTSector = pExtent->pRGD[uGDIndex];
+    if (!uGTSector)
+    {
+        /* There is no grain table referenced by this grain directory
+         * entry. So there is absolutely no data in this area. Allocate
+         * a new grain table and put the reference to it in the GDs. */
+        rc = RTFileGetSize(pExtent->File, &cbExtentSize);
+        if (VBOX_FAILURE(rc))
+            return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error getting size of file '%s'"), pExtent->pszFullname);
+        Assert(!(cbExtentSize % 512));
+        uGTSector = VMDK_BYTE2SECTOR(cbExtentSize);
+        /* Normally the grain table is preallocated for hosted sparse extents
+         * that support more than 32 bit sector numbers. So this shouldn't
+         * ever happen on a valid extent. */
+        if (uGTSector > UINT32_MAX)
+            return VERR_VDI_INVALID_HEADER;
+        /* Write grain table by writing the required number of grain table
+         * cache chunks. Avoids dynamic memory allocation, but is a bit
+         * slower. But as this is a pretty infrequently occurring case it
+         * should be acceptable. */
+        memset(aGTDataTmp, '\0', sizeof(aGTDataTmp));
+        for (unsigned i = 0; i < pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE; i++)
+        {
+            rc = RTFileWriteAt(pExtent->File, VMDK_SECTOR2BYTE(uGTSector) + i * sizeof(aGTDataTmp), aGTDataTmp, sizeof(aGTDataTmp), NULL);
+            if (VBOX_FAILURE(rc))
+                return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write grain table allocation in file '%s'"), pExtent->pszFullname);
+        }
+        if (pExtent->pRGD)
+        {
+            rc = RTFileGetSize(pExtent->File, &cbExtentSize);
+            if (VBOX_FAILURE(rc))
+                return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error getting size of file '%s'"), pExtent->pszFullname);
+            Assert(!(cbExtentSize % 512));
+            uRGTSector = VMDK_BYTE2SECTOR(cbExtentSize);
+            /* Write backup grain table by writing the required number of grain
+             * table cache chunks. Avoids dynamic memory allocation, but is a
+             * bit slower. But as this is a pretty infrequently occurring case
+             * it should be acceptable. */
+            for (unsigned i = 0; i < pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE; i++)
+            {
+                rc = RTFileWriteAt(pExtent->File, VMDK_SECTOR2BYTE(uRGTSector) + i * sizeof(aGTDataTmp), aGTDataTmp, sizeof(aGTDataTmp), NULL);
+                if (VBOX_FAILURE(rc))
+                    return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write backup grain table allocation in file '%s'"), pExtent->pszFullname);
+            }
+        }
+
+        /* Update the grain directory on disk (doing it before writing the
+         * grain table will result in a garbled extent if the operation is
+         * aborted for some reason. Otherwise the worst that can happen is
+         * some unused sectors in the extent. */
+        uint32_t uGTSectorLE = RT_H2LE_U64(uGTSector);
+        rc = RTFileWriteAt(pExtent->File, VMDK_SECTOR2BYTE(pExtent->uSectorGD) + uGDIndex * sizeof(uGTSectorLE), &uGTSectorLE, sizeof(uGTSectorLE), NULL);
+        if (VBOX_FAILURE(rc))
+            return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write grain directory entry in file '%s'"), pExtent->pszFullname);
+        if (pExtent->pRGD)
+        {
+            uint32_t uRGTSectorLE = RT_H2LE_U64(uRGTSector);
+            rc = RTFileWriteAt(pExtent->File, VMDK_SECTOR2BYTE(pExtent->uSectorRGD) + uGDIndex * sizeof(uRGTSectorLE), &uRGTSectorLE, sizeof(uRGTSectorLE), NULL);
+            if (VBOX_FAILURE(rc))
+                return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write backup grain directory entry in file '%s'"), pExtent->pszFullname);
+        }
+
+        /* As the final step update the in-memory copy of the GDs. */
+        pExtent->pGD[uGDIndex] = uGTSector;
+        if (pExtent->pRGD)
+            pExtent->pRGD[uGDIndex] = uRGTSector;
+    }
+
+    rc = RTFileGetSize(pExtent->File, &cbExtentSize);
+    if (VBOX_FAILURE(rc))
+        return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error getting size of file '%s'"), pExtent->pszFullname);
+    Assert(!(cbExtentSize % 512));
+
+    /* Write the data. */
+    rc = RTFileWriteAt(pExtent->File, cbExtentSize, pvBuf, cbWrite, NULL);
+    if (VBOX_FAILURE(rc))
+        return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write allocated data block in file '%s'"), pExtent->pszFullname);
+
+    /* Update the grain table (and the cache). */
+    uGTBlock = uSector / (pExtent->cSectorsPerGrain * VMDK_GT_CACHELINE_SIZE);
+    uGTHash = vmdkGTCacheHash(pCache, uGTBlock, pExtent->uExtent);
+    pGTCacheEntry = &pCache->aGTCache[uGTHash];
+    if (    pGTCacheEntry->uExtent != pExtent->uExtent
+        ||  pGTCacheEntry->uGTBlock != uGTBlock)
+    {
+        /* Cache miss, fetch data from disk. */
+        rc = RTFileReadAt(pExtent->File,
+                          VMDK_SECTOR2BYTE(uGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
+                          aGTDataTmp, sizeof(aGTDataTmp), NULL);
+        if (VBOX_FAILURE(rc))
+            return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot read allocated grain table entry in file '%s'"), pExtent->pszFullname);
+        pGTCacheEntry->uExtent = pExtent->uExtent;
+        pGTCacheEntry->uGTBlock = uGTBlock;
+        for (unsigned i = 0; i < VMDK_GT_CACHELINE_SIZE; i++)
+            pGTCacheEntry->aGTData[i] = RT_LE2H_U32(aGTDataTmp[i]);
+    }
+    else
+    {
+        /* Cache hit. Convert grain table block back to disk format, otherwise
+         * the code below will write garbage for all but the updated entry. */
+        for (unsigned i = 0; i < VMDK_GT_CACHELINE_SIZE; i++)
+            aGTDataTmp[i] = RT_H2LE_U32(pGTCacheEntry->aGTData[i]);
+    }
+    uGTBlockIndex = (uSector / pExtent->cSectorsPerGrain) % VMDK_GT_CACHELINE_SIZE;
+    aGTDataTmp[uGTBlockIndex] = RT_H2LE_U32(VMDK_BYTE2SECTOR(cbExtentSize));
+    pGTCacheEntry->aGTData[uGTBlockIndex] = VMDK_BYTE2SECTOR(cbExtentSize);
+    /* Update grain table on disk. */
+    rc = RTFileWriteAt(pExtent->File,
+                       VMDK_SECTOR2BYTE(uGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
+                       aGTDataTmp, sizeof(aGTDataTmp), NULL);
+    if (VBOX_FAILURE(rc))
+        return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write updated grain table in file '%s'"), pExtent->pszFullname);
+    if (pExtent->pRGD)
+    {
+        /* Update backup grain table on disk. */
+        rc = RTFileWriteAt(pExtent->File,
+                           VMDK_SECTOR2BYTE(uRGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
+                           aGTDataTmp, sizeof(aGTDataTmp), NULL);
+        if (VBOX_FAILURE(rc))
+            return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write updated backup grain table in file '%s'"), pExtent->pszFullname);
+    }
+#ifdef VBOX_WITH_VMDK_ESX
+    if (VBOX_SUCCESS(rc) && pExtent->enmType == VMDKETYPE_ESX_SPARSE)
+    {
+        pExtent->uFreeSector = uGTSector + VMDK_BYTE2SECTOR(cbWrite);
+        pExtent->fMetaDirty = true;
+    }
+#endif /* VBOX_WITH_VMDK_ESX */
+    return rc;
+}
+
+static int vmdkOpen(const char *pszFilename, unsigned uOpenFlags, PFNVDERROR pfnError, void *pvErrorUser, void **ppvBackendData)
+{
+    int rc;
+    PVMDKIMAGE pImage;
+
+    /* Check open flags. Just readonly flag is supported. */
+    if (uOpenFlags & ~VD_OPEN_FLAGS_READONLY)
+    {
+        rc = VERR_INVALID_PARAMETER;
+        goto out;
+    }
+
+    pImage = (PVMDKIMAGE)RTMemAllocZ(sizeof(VMDKIMAGE));
+    if (!pImage)
+    {
+        rc = VERR_NO_MEMORY;
+        goto out;
+    }
+    pImage->pszFilename = pszFilename;
+    pImage->File = NIL_RTFILE;
+    pImage->pExtents = NULL;
+    pImage->pGTCache = NULL;
+    pImage->pDescData = NULL;
+    pImage->pfnError = pfnError;
+    pImage->pvErrorUser = pvErrorUser;
+
+    if (VBOX_SUCCESS(rc))
+    {
+        rc = vmdkOpenImage(pImage, pszFilename, uOpenFlags);
+        if (VBOX_SUCCESS(rc))
+            *ppvBackendData = pImage;
+    }
+
+out:
+    LogFlow(("%s: returned %Vrc\n", __FUNCTION__, rc));
+    return rc;
+}
+
+static int vmdkClose(void *pBackendData)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    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)
+        vmdkFreeImage(pImage);
+
+    LogFlow(("%s: returned %Vrc\n", __FUNCTION__, rc));
+    return rc;
+}
+
+static int vmdkRead(void *pBackendData, uint64_t uOffset, void *pvBuf, size_t cbRead, size_t *pcbActuallyRead)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    PVMDKEXTENT pExtent;
+    uint64_t uSectorInExtent;
+    uint64_t uSectorOffset;
+    int rc;
+
+    Assert(uOffset % 512 == 0);
+    Assert(cbRead % 512 == 0);
+
+    if (uOffset + cbRead > pImage->cbSize)
+    {
+        rc = VERR_INVALID_PARAMETER;
+        goto out;
+    }
+
+    rc = vmdkFindExtent(pImage, VMDK_BYTE2SECTOR(uOffset),
+                        &pExtent, &uSectorInExtent);
+    if (VBOX_FAILURE(rc))
+        goto out;
+
+    /* Check access permissions as defined in the extent descriptor. */
+    if (pExtent->enmAccess == VMDKACCESS_NOACCESS)
+    {
+        rc = VERR_VDI_INVALID_STATE;
+        goto out;
+    }
+
+    /* Clip read range to remain in this extent. */
+    cbRead = RT_MIN(cbRead, VMDK_SECTOR2BYTE(pExtent->cNominalSectors - uSectorInExtent));
+
+    /* Handle the read according to the current extent type. */
+    switch (pExtent->enmType)
+    {
+        case VMDKETYPE_HOSTED_SPARSE:
+#ifdef VBOX_WITH_VMDK_ESX
+        case VMDKETYPE_ESX_SPARSE:
+#endif /* VBOX_WITH_VMDK_ESX */
+            /* Clip read range to at most the rest of the grain. */
+            cbRead = RT_MIN(cbRead, VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain - (uSectorOffset % pExtent->cSectorsPerGrain)));
+            rc = vmdkGetSector(pImage->pGTCache, pExtent, uSectorInExtent,
+                               &uSectorOffset);
+            if (VBOX_FAILURE(rc))
+                goto out;
+            if (uSectorOffset == 0)
+                rc = VINF_VDI_BLOCK_FREE;
+            else
+                rc = RTFileReadAt(pExtent->File,
+                                  VMDK_SECTOR2BYTE(uSectorOffset),
+                                  pvBuf, cbRead, NULL);
+            break;
+        case VMDKETYPE_FLAT:
+            rc = RTFileReadAt(pExtent->File, VMDK_SECTOR2BYTE(uSectorInExtent),
+                              pvBuf, cbRead, NULL);
+            break;
+        case VMDKETYPE_ZERO:
+            memset(pvBuf, '\0', cbRead);
+            break;
+    }
+    *pcbActuallyRead = cbRead;
+
+out:
+    return rc;
+}
+
+static int vmdkWrite(void *pBackendData, uint64_t uOffset, const void *pvBuf, size_t cbWrite, size_t *pcbWriteProcess, size_t *pcbPreRead, size_t *pcbPostRead)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    PVMDKEXTENT pExtent;
+    uint64_t uSectorInExtent;
+    uint64_t uSectorOffset;
+    int rc;
+
+    Assert(uOffset % 512 == 0);
+    Assert(cbWrite % 512 == 0);
+
+    if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+    {
+        rc = VERR_VDI_IMAGE_READ_ONLY;
+        goto out;
+    }
+
+    /* No size check here, will do that later when the extent is located.
+     * There are sparse images out there which according to the spec are
+     * invalid, because the total size is not a multiple of the grain size.
+     * Also for sparse images which are stitched together in odd ways (not at
+     * grain boundaries, and with the nominal size not being a multiple of the
+     * grain size), this would prevent writing to the last grain. */
+
+    rc = vmdkFindExtent(pImage, VMDK_BYTE2SECTOR(uOffset),
+                        &pExtent, &uSectorInExtent);
+    if (VBOX_FAILURE(rc))
+        goto out;
+
+    /* Check access permissions as defined in the extent descriptor. */
+    if (pExtent->enmAccess != VMDKACCESS_READWRITE)
+    {
+        rc = VERR_VDI_INVALID_STATE;
+        goto out;
+    }
+
+    /* Handle the write according to the current extent type. */
+    switch (pExtent->enmType)
+    {
+        case VMDKETYPE_HOSTED_SPARSE:
+#ifdef VBOX_WITH_VMDK_ESX
+        case VMDKETYPE_ESX_SPARSE:
+#endif /* VBOX_WITH_VMDK_ESX */
+            /* Clip write range to at most the rest of the grain. */
+            cbWrite = RT_MIN(cbWrite, VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain - (uSectorOffset % pExtent->cSectorsPerGrain)));
+            rc = vmdkGetSector(pImage->pGTCache, pExtent, uSectorInExtent,
+                               &uSectorOffset);
+            if (VBOX_FAILURE(rc))
+                goto out;
+            if (uSectorOffset == 0)
+            {
+                if (cbWrite == VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain))
+                {
+                    /* Full block write to a previously unallocated block.
+                     * Allocate GT and find out where to store the grain. */
+                    rc = vmdkAllocGrain(pImage->pGTCache, pExtent,
+                                        uSectorInExtent, pvBuf, cbWrite);
+                }
+                else
+                {
+                    /* Clip write range to remain in this extent. */
+                    cbWrite = RT_MIN(cbWrite, VMDK_SECTOR2BYTE(pExtent->cNominalSectors - uSectorInExtent));
+                    *pcbPreRead = VMDK_SECTOR2BYTE(uSectorInExtent % pExtent->cSectorsPerGrain);
+                    *pcbPostRead = VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain) - cbWrite - *pcbPreRead;
+                    rc = VINF_VDI_BLOCK_FREE;
+                }
+            }
+            else
+                rc = RTFileWriteAt(pExtent->File,
+                                   VMDK_SECTOR2BYTE(uSectorOffset),
+                                   pvBuf, cbWrite, NULL);
+            break;
+        case VMDKETYPE_FLAT:
+            /* Clip write range to remain in this extent. */
+            cbWrite = RT_MIN(cbWrite, VMDK_SECTOR2BYTE(pExtent->cNominalSectors - uSectorInExtent));
+            rc = RTFileWriteAt(pExtent->File, VMDK_SECTOR2BYTE(uSectorInExtent), pvBuf, cbWrite, NULL);
+            break;
+        case VMDKETYPE_ZERO:
+            /* Clip write range to remain in this extent. */
+            cbWrite = RT_MIN(cbWrite, VMDK_SECTOR2BYTE(pExtent->cNominalSectors - uSectorInExtent));
+            break;
+    }
+    if (pcbWriteProcess)
+        *pcbWriteProcess = cbWrite;
+
+out:
+    return rc;
+}
+
+static int vmdkFlush(void *pBackendData)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+    int rc = vmdkFlushImage(pImage);
+
+    return rc;
+}
+
+static int vmdkGetImageType(void *pBackendData, PVDIMAGETYPE penmImageType)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    int rc = VINF_SUCCESS;
+
+    Assert(pImage);
+    Assert(penmImageType);
+
+    if (pImage && pImage->cExtents != 0)
+        *penmImageType = pImage->enmImageType;
+    else
+        rc = VERR_VDI_NOT_OPENED;
+
+    return rc;
+}
+
+static uint64_t vmdkGetSize(void *pBackendData)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+
+    Assert(pImage);
+
+    if (pImage)
+        return pImage->cbSize;
+    else
+        return 0;
+}
+
+static int vmdkGetGeometry(void *pBackendData, unsigned *pcCylinders, unsigned *pcHeads, unsigned *pcSectors)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    int rc;
+
+    Assert(pImage);
+
+    if (pImage)
+    {
+        if (pImage->cCylinders)
+        {
+            *pcCylinders = pImage->cCylinders;
+            *pcHeads = pImage->cHeads;
+            *pcSectors = pImage->cSectors;
+            rc = VINF_SUCCESS;
+        }
+        else
+            rc = VERR_VDI_GEOMETRY_NOT_SET;
+    }
+    else
+        rc = VERR_VDI_NOT_OPENED;
+    LogFlow(("%s: returned %Vrc (CHS=%u/%u/%u)\n", __FUNCTION__, rc,
+             pImage->cCylinders, pImage->cHeads, pImage->cSectors));
+    return rc;
+}
+
+static int vmdkSetGeometry(void *pBackendData, unsigned cCylinders, unsigned cHeads, unsigned cSectors)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    int rc;
+
+    Assert(pImage);
+
+    if (pImage)
+    {
+        if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+        {
+            rc = VERR_VDI_IMAGE_READ_ONLY;
+            goto out;
+        }
+        rc = vmdkDescDDBSetU32(pImage, &pImage->Descriptor,
+                               "ddb.geometry.cylinders", cCylinders);
+        if (VBOX_FAILURE(rc))
+            goto out;
+        rc = vmdkDescDDBSetU32(pImage, &pImage->Descriptor,
+                               "ddb.geometry.heads", cHeads);
+        if (VBOX_FAILURE(rc))
+            goto out;
+        rc = vmdkDescDDBSetU32(pImage, &pImage->Descriptor,
+                               "ddb.geometry.sectors", cSectors);
+        if (VBOX_FAILURE(rc))
+            goto out;
+
+        pImage->cCylinders = cCylinders;
+        pImage->cHeads = cHeads;
+        pImage->cSectors = cSectors;
+        rc = VINF_SUCCESS;
+    }
+    else
+        rc = VERR_VDI_NOT_OPENED;
+
+out:
+    LogFlow(("%s: returned %Vrc\n", __FUNCTION__, rc));
+    return rc;
+}
+
+static int vmdkGetTranslation(void *pBackendData, PPDMBIOSTRANSLATION penmTranslation)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    int rc;
+
+    Assert(pImage);
+
+    if (pImage)
+    {
+        if (pImage->enmTranslation)
+        {
+            *penmTranslation = pImage->enmTranslation;
+            rc = VINF_SUCCESS;
+        }
+        else
+            rc = VERR_VDI_GEOMETRY_NOT_SET;
+    }
+    else
+        rc = VERR_VDI_NOT_OPENED;
+    LogFlow(("%s: returned %Vrc (%d)\n", __FUNCTION__, rc,
+             pImage->enmTranslation));
+    return rc;
+}
+
+static int vmdkSetTranslation(void *pBackendData, PDMBIOSTRANSLATION enmTranslation)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    int rc;
+
+    Assert(pImage);
+
+    if (pImage)
+    {
+        /** @todo maybe store this in the image descriptor */
+        pImage->enmTranslation = enmTranslation;
+        rc = VINF_SUCCESS;
+    }
+    else
+        rc = VERR_VDI_NOT_OPENED;
+    LogFlow(("%s: returned %Vrc\n", __FUNCTION__, rc));
+    return rc;
+}
+
+static unsigned vmdkGetOpenFlags(void *pBackendData)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    unsigned uOpenFlags;
+
+    Assert(pImage);
+
+    if (pImage)
+        uOpenFlags = pImage->uOpenFlags;
+    else
+        uOpenFlags = 0;
+
+    LogFlow(("%s: returned %d\n", __FUNCTION__, uOpenFlags));
+    return uOpenFlags;
+}
+
+static int vmdkSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    int rc;
+    const char *pszFilename;
+
+    /* Image must be opened and the new flags must be valid. Just readonly flag
+     * is supported. */
+    if (!pImage || uOpenFlags & ~VD_OPEN_FLAGS_READONLY)
+    {
+        rc = VERR_INVALID_PARAMETER;
+        goto out;
+    }
+
+    /* Implement this operation via reopening the image. */
+    pszFilename = pImage->pszFilename;
+    vmdkFreeImage(pImage);
+    rc = vmdkOpenImage(pImage, pszFilename, uOpenFlags);
+
+out:
+    LogFlow(("%s: returned %Vrc\n", __FUNCTION__, rc));
+    return rc;
+}
+
+static int vmdkGetUuid(void *pBackendData, PRTUUID pUuid)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    int rc;
+
+    Assert(pImage);
+
+    if (pImage)
+    {
+        *pUuid = pImage->ImageUuid;
+        rc = VINF_SUCCESS;
+    }
+    else
+        rc = VERR_VDI_NOT_OPENED;
+    LogFlow(("%s: returned %Vrc (%Vuuid)\n", __FUNCTION__, rc, pUuid));
+    return rc;
+}
+
+static int vmdkSetUuid(void *pBackendData, PCRTUUID pUuid)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    int rc;
+
+    LogFlow(("%s: %Vuuid\n", pUuid));
+    Assert(pImage);
+
+    if (pImage)
+    {
+        pImage->ImageUuid = *pUuid;
+        rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+                                "ddb.uuid.image", pUuid);
+        if (VBOX_FAILURE(rc))
+            return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing image UUID in descriptor"));
+        rc = VINF_SUCCESS;
+    }
+    else
+        rc = VERR_VDI_NOT_OPENED;
+    LogFlow(("%s: returned %Vrc\n", __FUNCTION__, rc));
+    return rc;
+}
+
+static int vmdkGetModificationUuid(void *pBackendData, PRTUUID pUuid)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    int rc;
+
+    Assert(pImage);
+
+    if (pImage)
+    {
+        *pUuid = pImage->ModificationUuid;
+        rc = VINF_SUCCESS;
+    }
+    else
+        rc = VERR_VDI_NOT_OPENED;
+    LogFlow(("%s: returned %Vrc (%Vuuid)\n", __FUNCTION__, rc, pUuid));
+    return rc;
+}
+
+static int vmdkSetModificationUuid(void *pBackendData, PCRTUUID pUuid)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    int rc;
+
+    LogFlow(("%s: %Vuuid\n", pUuid));
+    Assert(pImage);
+
+    if (pImage)
+    {
+        pImage->ModificationUuid = *pUuid;
+        rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+                                "ddb.uuid.modification", pUuid);
+        if (VBOX_FAILURE(rc))
+            return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing modification UUID in descriptor"));
+        rc = VINF_SUCCESS;
+    }
+    else
+        rc = VERR_VDI_NOT_OPENED;
+    LogFlow(("%s: returned %Vrc\n", __FUNCTION__, rc));
+    return rc;
+}
+
+static int vmdkGetParentUuid(void *pBackendData, PRTUUID pUuid)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    int rc;
+
+    Assert(pImage);
+
+    if (pImage)
+    {
+        *pUuid = pImage->ParentUuid;
+        rc = VINF_SUCCESS;
+    }
+    else
+        rc = VERR_VDI_NOT_OPENED;
+    LogFlow(("%s: returned %Vrc (%Vuuid)\n", __FUNCTION__, rc, pUuid));
+    return rc;
+}
+
+static int vmdkSetParentUuid(void *pBackendData, PCRTUUID pUuid)
+{
+    PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData;
+    int rc;
+
+    LogFlow(("%s: %Vuuid\n", pUuid));
+    Assert(pImage);
+
+    if (pImage)
+    {
+        pImage->ParentUuid = *pUuid;
+        rc = vmdkDescDDBSetUuid(pImage, &pImage->Descriptor,
+                                "ddb.uuid.parent", pUuid);
+        if (VBOX_FAILURE(rc))
+            return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing parent image UUID in descriptor"));
+        rc = VINF_SUCCESS;
+    }
+    else
+        rc = VERR_VDI_NOT_OPENED;
+    LogFlow(("%s: returned %Vrc\n", __FUNCTION__, rc));
+    return rc;
+}
+
+
+VBOXHDDBACKEND g_VmdkBackend =
+{
+    /* pfnOpen */
+    vmdkOpen,
+    /* pfnClose */
+    vmdkClose,
+    /* pfnRead */
+    vmdkRead,
+    /* pfnWrite */
+    vmdkWrite,
+    /* pfnFlush */
+    vmdkFlush,
+    /* pfnGetImageType */
+    vmdkGetImageType,
+    /* pfnGetSize */
+    vmdkGetSize,
+    /* pfnGetGeometry */
+    vmdkGetGeometry,
+    /* pfnSetGeometry */
+    vmdkSetGeometry,
+    /* pfnGetTranslation */
+    vmdkGetTranslation,
+    /* pfnSetTranslation */
+    vmdkSetTranslation,
+    /* pfnGetOpenFlags */
+    vmdkGetOpenFlags,
+    /* pfnSetOpenFlags */
+    vmdkSetOpenFlags,
+    /* pfnGetUuid */
+    vmdkGetUuid,
+    /* pfnGetUuid */
+    vmdkSetUuid,
+    /* pfnGetModificationUuid */
+    vmdkGetModificationUuid,
+    /* pfnSetModificationUuid */
+    vmdkSetModificationUuid,
+    /* pfnGetParentUuid */
+    vmdkGetParentUuid,
+    /* pfnSetParentUuid */
+    vmdkSetParentUuid
+};
