Index: trunk/src/VBox/Devices/Storage/RawHDDCore.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/RawHDDCore.cpp	(revision 30554)
+++ trunk/src/VBox/Devices/Storage/RawHDDCore.cpp	(revision 30555)
@@ -1228,5 +1228,6 @@
     rc = pImage->pInterfaceIOCallbacks->pfnWriteUserAsync(pImage->pInterfaceIO->pvUser,
                                                          pImage->pStorage,
-                                                         uOffset, pIoCtx, cbWrite);
+                                                         uOffset, pIoCtx, cbWrite,
+                                                         NULL, NULL);
 
     if (RT_SUCCESS(rc))
Index: trunk/src/VBox/Devices/Storage/VBoxHDD.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/VBoxHDD.cpp	(revision 30554)
+++ trunk/src/VBox/Devices/Storage/VBoxHDD.cpp	(revision 30555)
@@ -39,4 +39,5 @@
 #include <iprt/critsect.h>
 #include <iprt/list.h>
+#include <iprt/avl.h>
 
 #include <VBox/VBoxHDD-Plugin.h>
@@ -171,4 +172,10 @@
 };
 
+# define VD_THREAD_IS_CRITSECT_OWNER(Disk) \
+    do \
+    { \
+        AssertMsg(RTCritSectIsOwner(&Disk->CritSect), \
+                  ("Thread does not own critical section\n"));\
+    } while(0)
 
 /**
@@ -208,6 +215,4 @@
 typedef struct VDIOCTX
 {
-    /** Node in the list of deferred requests. */
-    RTLISTNODE                   NodeWriteGrowing;
     /** Disk this is request is for. */
     PVBOXHDD                     pDisk;
@@ -289,4 +294,16 @@
 } VDIOCTX;
 
+typedef struct VDIOCTXDEFERRED
+{
+    /** Node in the list of deferred requests.
+     * A request can be deferred if the image is growing
+     * and the request accesses the same range or if
+     * the backend needs to read or write metadata from the disk
+     * before it can continue. */
+    RTLISTNODE NodeDeferred;
+    /** I/O context this entry points to. */
+    PVDIOCTX   pIoCtx;
+} VDIOCTXDEFERRED, *PVDIOCTXDEFERRED;
+
 /**
  * I/O task.
@@ -294,6 +311,10 @@
 typedef struct VDIOTASK
 {
-    /** Pointer to the I/O context the task belongs. */
-    PVDIOCTX                     pIoCtx;
+    /** Storage this task belongs to. */
+    PVDIOSTORAGE                 pIoStorage;
+    /** Optional completion callback. */
+    PFNVDXFERCOMPLETED           pfnComplete;
+    /** Opaque user data. */
+    void                        *pvUser;
     /** Flag whether this is a meta data transfer. */
     bool                         fMeta;
@@ -306,16 +327,12 @@
             /** Number of bytes this task transfered. */
             uint32_t             cbTransfer;
+            /** Pointer to the I/O context the task belongs. */
+            PVDIOCTX             pIoCtx;
         } User;
         /** Meta data transfer. */
         struct
         {
-            /** Transfer direction (Read/Write) */
-            VDIOCTXTXDIR         enmTxDir;
-            /** Completion callback from the backend */
-            PFNVDMETACOMPLETED   pfnMetaComplete;
-            /** User data */
-            void                *pvMetaUser;
-            /** Image the task was created for. */
-            PVDIMAGE             pImage;
+            /** Meta transfer this task is for. */
+            PVDMETAXFER          pMetaXfer;
         } Meta;
     } Type;
@@ -329,4 +346,6 @@
     /** Image this storage handle belongs to. */
     PVDIMAGE                     pImage;
+    /** AVL tree for pending async metadata transfers. */
+    PAVLRFOFFTREE                pTreeMetaXfers;
     union
     {
@@ -337,4 +356,42 @@
     } u;
 } VDIOSTORAGE;
+
+/**
+ *  Metadata transfer.
+ *
+ *  @note This entry can't be freed if either the list is not empty or
+ *  the reference counter is not 0.
+ *  The assumption is that the backends don't need to read huge amounts of
+ *  metadata to complete a transfer so the additional memory overhead should
+ *  be relatively small.
+ */
+typedef struct VDMETAXFER
+{
+    /** AVL core for fast search (the file offset is the key) */
+    AVLRFOFFNODECORE Core;
+    /** I/O storage for this transfer. */
+    PVDIOSTORAGE     pIoStorage;
+    /** Flags. */
+    uint32_t         fFlags;
+    /** List of I/O contexts waiting for this metadata transfer to complete. */
+    RTLISTNODE       ListIoCtxWaiting;
+    /** Number of references to this entry. */
+    unsigned         cRefs;
+    /** Size of the data stored with this entry. */
+    size_t           cbMeta;
+    /** Data stored - variable size. */
+    uint8_t          abData[1];
+} VDMETAXFER;
+
+/**
+ * The transfer direction for the metadata.
+ */
+#define VDMETAXFER_TXDIR_MASK  0x3
+#define VDMETAXFER_TXDIR_NONE  0x0
+#define VDMETAXFER_TXDIR_WRITE 0x1
+#define VDMETAXFER_TXDIR_READ  0x2
+#define VDMETAXFER_TXDIR_FLUSH 0x3
+#define VDMETAXFER_TXDIR_GET(flags)      ((flags) & VDMETAXFER_TXDIR_MASK)
+#define VDMETAXFER_TXDIR_SET(flags, dir) ((flags) = (flags & ~VDMETAXFER_TXDIR_MASK) | (dir))
 
 extern VBOXHDDBACKEND g_RawBackend;
@@ -667,4 +724,7 @@
                                    paSeg, cSeg, pvAllocation, pfnIoCtxTransfer);
 
+    AssertPtr(pIoCtxParent);
+    Assert(!pIoCtxParent->pIoCtxParent);
+
     if (RT_LIKELY(pIoCtx))
     {
@@ -679,14 +739,17 @@
 }
 
-DECLINLINE(PVDIOTASK) vdIoTaskUserAlloc(PVBOXHDD pDisk, PVDIOCTX pIoCtx, uint32_t cbTransfer)
+DECLINLINE(PVDIOTASK) vdIoTaskUserAlloc(PVDIOSTORAGE pIoStorage, PFNVDXFERCOMPLETED pfnComplete, void *pvUser, PVDIOCTX pIoCtx, uint32_t cbTransfer)
 {
     PVDIOTASK pIoTask = NULL;
 
-    pIoTask = (PVDIOTASK)RTMemCacheAlloc(pDisk->hMemCacheIoTask);
+    pIoTask = (PVDIOTASK)RTMemCacheAlloc(pIoStorage->pImage->pDisk->hMemCacheIoTask);
     if (pIoTask)
     {
-        pIoTask->pIoCtx               = pIoCtx;
+        pIoTask->pIoStorage           = pIoStorage;
+        pIoTask->pfnComplete          = pfnComplete;
+        pIoTask->pvUser               = pvUser;
         pIoTask->fMeta                = false;
         pIoTask->Type.User.cbTransfer = cbTransfer;
+        pIoTask->Type.User.pIoCtx     = pIoCtx;
     }
 
@@ -694,19 +757,16 @@
 }
 
-DECLINLINE(PVDIOTASK) vdIoTaskMetaAlloc(PVBOXHDD pDisk, PVDIOCTX pIoCtx, VDIOCTXTXDIR enmTxDir,
-                                        PVDIMAGE pImage,
-                                        PFNVDMETACOMPLETED pfnMetaComplete, void *pvMetaUser)
+DECLINLINE(PVDIOTASK) vdIoTaskMetaAlloc(PVDIOSTORAGE pIoStorage, PFNVDXFERCOMPLETED pfnComplete, void *pvUser, PVDMETAXFER pMetaXfer)
 {
     PVDIOTASK pIoTask = NULL;
 
-    pIoTask = (PVDIOTASK)RTMemCacheAlloc(pDisk->hMemCacheIoTask);
+    pIoTask = (PVDIOTASK)RTMemCacheAlloc(pIoStorage->pImage->pDisk->hMemCacheIoTask);
     if (pIoTask)
     {
-        pIoTask->pIoCtx                      = pIoCtx;
-        pIoTask->fMeta                       = true;
-        pIoTask->Type.Meta.enmTxDir          = enmTxDir;
-        pIoTask->Type.Meta.pfnMetaComplete   = pfnMetaComplete;
-        pIoTask->Type.Meta.pvMetaUser        = pvMetaUser;
-        pIoTask->Type.Meta.pImage            = pImage;
+        pIoTask->pIoStorage          = pIoStorage;
+        pIoTask->pfnComplete         = pfnComplete;
+        pIoTask->pvUser              = pvUser;
+        pIoTask->fMeta               = true;
+        pIoTask->Type.Meta.pMetaXfer = pMetaXfer;
     }
 
@@ -723,5 +783,4 @@
 DECLINLINE(void) vdIoTaskFree(PVBOXHDD pDisk, PVDIOTASK pIoTask)
 {
-    pIoTask->pIoCtx = NULL;
     RTMemCacheFree(pDisk->hMemCacheIoTask, pIoTask);
 }
@@ -734,4 +793,21 @@
     pIoCtx->uOffset        = pIoCtx->Type.Child.uOffsetSaved;
     pIoCtx->cbTransferLeft = pIoCtx->Type.Child.cbTransferLeftSaved;
+}
+
+DECLINLINE(PVDMETAXFER) vdMetaXferAlloc(PVDIMAGE pImage, PVDIOSTORAGE pIoStorage, uint64_t uOffset, size_t cb)
+{
+    PVDMETAXFER pMetaXfer = (PVDMETAXFER)RTMemAlloc(RT_OFFSETOF(VDMETAXFER, abData[cb]));
+
+    if (RT_LIKELY(pMetaXfer))
+    {
+        pMetaXfer->Core.Key     = uOffset;
+        pMetaXfer->Core.KeyLast = uOffset + cb - 1;
+        pMetaXfer->fFlags       = VDMETAXFER_TXDIR_NONE;
+        pMetaXfer->cbMeta       = cb;
+        pMetaXfer->pIoStorage   = pIoStorage;
+        pMetaXfer->cRefs        = 0;
+        RTListInit(&pMetaXfer->ListIoCtxWaiting);
+    }
+    return pMetaXfer;
 }
 
@@ -784,4 +860,8 @@
         return VINF_VD_ASYNC_IO_FINISHED;
 
+    /* Don't change anything if there is a metadata transfer pending. */
+    if (pIoCtx->cMetaTransfersPending)
+        return VERR_VD_ASYNC_IO_IN_PROGRESS;
+
     if (pIoCtx->pfnIoCtxTransfer)
     {
@@ -809,5 +889,5 @@
         && !pIoCtx->cDataTransfersPending)
         rc = VINF_VD_ASYNC_IO_FINISHED;
-    else if (RT_SUCCESS(rc))
+    else if (RT_SUCCESS(rc) || rc == VERR_VD_NOT_ENOUGH_METADATA)
         rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
     else if (RT_FAILURE(rc) && (rc != VERR_VD_ASYNC_IO_IN_PROGRESS))
@@ -881,4 +961,6 @@
             rc = VINF_SUCCESS;
         }
+        else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+            rc = VINF_SUCCESS;
 
         if (RT_FAILURE(rc))
@@ -895,5 +977,4 @@
         pIoCtx->cbTransfer = cbToRead;
         pIoCtx->pImage     = pCurrImage;
-        rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
     }
 
@@ -1283,4 +1364,5 @@
 
     AssertPtr(pIoCtxParent);
+    Assert(!pIoCtxParent->pIoCtxParent);
     Assert(!pIoCtx->cbTransferLeft && !pIoCtx->cMetaTransfersPending);
 
@@ -1347,4 +1429,6 @@
     Assert(cbPreRead == 0);
     Assert(cbPostRead == 0);
+    if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+        rc = VINF_SUCCESS;
 
     return rc;
@@ -1392,4 +1476,5 @@
 
     AssertPtr(pIoCtx->pIoCtxParent);
+    Assert(!pIoCtx->pIoCtxParent->pIoCtxParent);
 
     if (cbPostRead)
@@ -1464,9 +1549,13 @@
             if (ASMAtomicReadBool(&pDisk->fGrowing))
             {
+                PVDIOCTXDEFERRED pDeferred = (PVDIOCTXDEFERRED)RTMemAllocZ(sizeof(VDIOCTXDEFERRED));
+                AssertPtr(pDeferred);
+
                 LogFlowFunc(("Deferring write pIoCtx=%#p\n", pIoCtx));
-                RTListAppend(&pDisk->ListWriteGrowing, &pIoCtx->NodeWriteGrowing);
+
+                RTListInit(&pDeferred->NodeDeferred);
+                pDeferred->pIoCtx = pIoCtx;
+                RTListAppend(&pDisk->ListWriteGrowing, &pDeferred->NodeDeferred);
                 pIoCtx->fBlocked = true;
-                Assert(pIoCtx->NodeWriteGrowing.pNext == &pDisk->ListWriteGrowing);
-                Assert(pDisk->ListWriteGrowing.pPrev == &pIoCtx->NodeWriteGrowing);
                 rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
                 break;
@@ -1533,9 +1622,12 @@
         }
 
+        if (rc == VERR_VD_NOT_ENOUGH_METADATA)
+            break;
+
         cbWrite -= cbThisWrite;
         uOffset += cbThisWrite;
-    } while (cbWrite != 0 && RT_SUCCESS(rc));
-
-    if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+    } while (cbWrite != 0 && (RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS));
+
+    if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS || rc == VERR_VD_NOT_ENOUGH_METADATA)
     {
         /*
@@ -1564,4 +1656,6 @@
     vdResetModifiedFlag(pDisk);
     rc = pImage->Backend->pfnAsyncFlush(pImage->pvBackendData, pIoCtx);
+    if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+        rc = VINF_SUCCESS;
 
     return rc;
@@ -1818,29 +1912,12 @@
 }
 
-static int vdIOReqCompleted(void *pvUser, int rcReq)
-{
+/**
+ * Internal - Continues an I/O context after
+ * it was halted because of an active transfer.
+ */
+static int vdIoCtxContinue(PVDIOCTX pIoCtx, int rcReq)
+{
+    PVBOXHDD pDisk = pIoCtx->pDisk;
     int rc = VINF_SUCCESS;
-    PVDIOTASK pIoTask = (PVDIOTASK)pvUser;
-    PVDIOCTX  pIoCtx  = pIoTask->pIoCtx;
-    PVBOXHDD  pDisk   = pIoCtx->pDisk;
-
-    LogFlowFunc(("Task completed pIoTask=%#p pIoCtx=%#p pDisk=%#p\n",
-                 pIoTask, pIoCtx, pDisk));
-
-    if (!pIoTask->fMeta)
-    {
-        ASMAtomicSubU32(&pIoCtx->cbTransferLeft, pIoTask->Type.User.cbTransfer);
-        ASMAtomicDecU32(&pIoCtx->cDataTransfersPending);
-    }
-    else
-    {
-        if (pIoTask->Type.Meta.pfnMetaComplete)
-            pIoTask->Type.Meta.pfnMetaComplete(pIoTask->Type.Meta.pImage->pvBackendData,
-                                               pIoCtx,
-                                               pIoTask->Type.Meta.pvMetaUser);
-        ASMAtomicDecU32(&pIoCtx->cMetaTransfersPending);
-    }
-
-    vdIoTaskFree(pDisk, pIoTask);
 
     if (RT_FAILURE(rcReq))
@@ -1883,5 +1960,5 @@
                     && ASMAtomicCmpXchgBool(&pIoCtxParent->fComplete, true, false))
                 {
-                    LogFlowFunc(("Parent I/O context completed pIoCtxParent=%#p\n", pIoCtx));
+                    LogFlowFunc(("Parent I/O context completed pIoCtxParent=%#p rcReq=%Rrc\n", pIoCtxParent, pIoCtxParent->rcReq));
                     pIoCtxParent->Type.Root.pfnComplete(pIoCtxParent->Type.Root.pvUser1,
                                                         pIoCtxParent->Type.Root.pvUser2,
@@ -1911,8 +1988,11 @@
                     do
                     {
-                        PVDIOCTX pIoCtxWait = RTListNodeGetFirst(&ListTmp, VDIOCTX, NodeWriteGrowing);
+                        PVDIOCTXDEFERRED pDeferred = RTListNodeGetFirst(&ListTmp, VDIOCTXDEFERRED, NodeDeferred);
+                        PVDIOCTX pIoCtxWait = pDeferred->pIoCtx;
+
                         AssertPtr(pIoCtxWait);
 
-                        RTListNodeRemove(&pIoCtxWait->NodeWriteGrowing);
+                        RTListNodeRemove(&pDeferred->NodeDeferred);
+                        RTMemFree(pDeferred);
 
                         pIoCtxWait->fBlocked = false;
@@ -1945,4 +2025,5 @@
                     vdThreadFinishRead(pDisk);
 
+                LogFlowFunc(("I/O context completed pIoCtx=%#p rcReq=%Rrc\n", pIoCtx, pIoCtx->rcReq));
                 pIoCtx->Type.Root.pfnComplete(pIoCtx->Type.Root.pvUser1,
                                               pIoCtx->Type.Root.pvUser2,
@@ -1958,4 +2039,144 @@
 
 /**
+ * Internal - Called when user transfer completed.
+ */
+static int vdUserXferCompleted(PVDIOSTORAGE pIoStorage, PVDIOCTX pIoCtx,
+                               PFNVDXFERCOMPLETED pfnComplete, void *pvUser,
+                               size_t cbTransfer, int rcReq)
+{
+    int rc = VINF_SUCCESS;
+    bool fIoCtxContinue = true;
+    PVBOXHDD pDisk = pIoCtx->pDisk;
+
+    LogFlowFunc(("pIoStorage=%#p pIoCtx=%#p pfnComplete=%#p pvUser=%#p cbTransfer=%zu rcReq=%Rrc\n",
+                 pIoStorage, pIoCtx, pfnComplete, pvUser, cbTransfer, rcReq));
+
+    ASMAtomicSubU32(&pIoCtx->cbTransferLeft, cbTransfer);
+    ASMAtomicDecU32(&pIoCtx->cDataTransfersPending);
+
+    if (pfnComplete)
+        rc = pfnComplete(pIoStorage->pImage->pvBackendData, pIoCtx, pvUser, rcReq);
+
+    if (RT_SUCCESS(rc))
+        rc = vdIoCtxContinue(pIoCtx, rcReq);
+    else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+        rc = VINF_SUCCESS;
+
+    return rc;
+}
+
+/**
+ * Internal - Called when a meta transfer completed.
+ */
+static int vdMetaXferCompleted(PVDIOSTORAGE pIoStorage, PFNVDXFERCOMPLETED pfnComplete, void *pvUser,
+                               PVDMETAXFER pMetaXfer, int rcReq)
+{
+    PVBOXHDD pDisk = pIoStorage->pImage->pDisk;
+    RTLISTNODE ListIoCtxWaiting;
+    bool fFlush = VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_FLUSH;
+
+    LogFlowFunc(("pIoStorage=%#p pfnComplete=%#p pvUser=%#p pMetaXfer=%#p rcReq=%Rrc\n",
+                 pIoStorage, pfnComplete, pvUser, pMetaXfer, rcReq));
+
+    VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_NONE);
+
+    if (!fFlush)
+    {
+        RTCritSectEnter(&pDisk->CritSect);
+
+        RTListMove(&ListIoCtxWaiting, &pMetaXfer->ListIoCtxWaiting);
+
+        if (RT_FAILURE(rcReq))
+        {
+            /* Remove from the AVL tree. */
+            bool fRemoved = RTAvlrFileOffsetRemove(pIoStorage->pTreeMetaXfers, pMetaXfer->Core.Key);
+            Assert(fRemoved);
+            RTMemFree(pMetaXfer);
+        }
+        else
+        {
+            /* Increase the reference counter to make sure it doesn't go away before the last context is processed. */
+            pMetaXfer->cRefs++;
+        }
+
+        RTCritSectLeave(&pDisk->CritSect);
+    }
+    else
+    {
+        /*
+         *  Flushes don't need the critical section because they are never accessed concurrently
+         * (they also have only one context attached).
+         */
+        RTListMove(&ListIoCtxWaiting, &pMetaXfer->ListIoCtxWaiting);
+    }
+
+    /* Go through the waiting list and continue the I/O contexts. */
+    while (!RTListIsEmpty(&ListIoCtxWaiting))
+    {
+        int rc = VINF_SUCCESS;
+        bool fContinue = true;
+        PVDIOCTXDEFERRED pDeferred = RTListNodeGetFirst(&ListIoCtxWaiting, VDIOCTXDEFERRED, NodeDeferred);
+        PVDIOCTX pIoCtx = pDeferred->pIoCtx;
+        RTListNodeRemove(&pDeferred->NodeDeferred);
+
+        RTMemFree(pDeferred);
+        ASMAtomicDecU32(&pIoCtx->cMetaTransfersPending);
+
+        if (pfnComplete)
+            rc = pfnComplete(pIoStorage->pImage->pvBackendData, pIoCtx, pvUser, rcReq);
+
+        LogFlow(("Completion callback for I/O context %#p returned %Rrc\n", pIoCtx, rc));
+
+        if (RT_SUCCESS(rc))
+        {
+            rc = vdIoCtxContinue(pIoCtx, rcReq);
+            AssertRC(rc);
+        }
+        else
+            Assert(rc == VERR_VD_ASYNC_IO_IN_PROGRESS);
+    }
+
+    /* Remove if not used anymore. */
+    if (RT_SUCCESS(rcReq) && !fFlush)
+    {
+        RTCritSectEnter(&pDisk->CritSect);
+        pMetaXfer->cRefs--;
+        if (!pMetaXfer->cRefs)
+        {
+            /* Remove from the AVL tree. */
+            bool fRemoved = RTAvlrFileOffsetRemove(pIoStorage->pTreeMetaXfers, pMetaXfer->Core.Key);
+            Assert(fRemoved);
+            RTMemFree(pMetaXfer);
+        }
+        RTCritSectLeave(&pDisk->CritSect);
+    }
+    else if (fFlush)
+        RTMemFree(pMetaXfer);
+
+    return VINF_SUCCESS;
+}
+
+static int vdIOReqCompleted(void *pvUser, int rcReq)
+{
+    int rc = VINF_SUCCESS;
+    PVDIOTASK pIoTask = (PVDIOTASK)pvUser;
+    PVDIOSTORAGE pIoStorage = pIoTask->pIoStorage;
+
+    LogFlowFunc(("Task completed pIoTask=%#p\n", pIoTask));
+
+    if (!pIoTask->fMeta)
+        rc = vdUserXferCompleted(pIoStorage, pIoTask->Type.User.pIoCtx,
+                                 pIoTask->pfnComplete, pIoTask->pvUser,
+                                 pIoTask->Type.User.cbTransfer, rcReq);
+    else
+        rc = vdMetaXferCompleted(pIoStorage, pIoTask->pfnComplete, pIoTask->pvUser,
+                                 pIoTask->Type.Meta.pMetaXfer, rcReq);
+
+    vdIoTaskFree(pIoStorage->pImage->pDisk, pIoTask);
+
+    return rc;
+}
+
+/**
  * VD I/O interface callback for opening a file.
  */
@@ -1971,15 +2192,34 @@
         return VERR_NO_MEMORY;
 
-    rc = pDisk->pInterfaceAsyncIOCallbacks->pfnOpen(pDisk->pInterfaceAsyncIO->pvUser,
-                                                    pszLocation, uOpenFlags,
-                                                    vdIOReqCompleted,
-                                                    pDisk->pVDIfsDisk,
-                                                    &pIoStorage->u.pStorage);
-    if (RT_SUCCESS(rc))
-        *ppIoStorage = pIoStorage;
+    pIoStorage->pImage = pImage;
+
+    /* Create the AVl tree. */
+    pIoStorage->pTreeMetaXfers = (PAVLRFOFFTREE)RTMemAllocZ(sizeof(AVLRFOFFTREE));
+    if (pIoStorage->pTreeMetaXfers)
+    {
+        rc = pDisk->pInterfaceAsyncIOCallbacks->pfnOpen(pDisk->pInterfaceAsyncIO->pvUser,
+                                                        pszLocation, uOpenFlags,
+                                                        vdIOReqCompleted,
+                                                        pDisk->pVDIfsDisk,
+                                                        &pIoStorage->u.pStorage);
+        if (RT_SUCCESS(rc))
+        {
+            *ppIoStorage = pIoStorage;
+            return VINF_SUCCESS;
+        }
+
+        RTMemFree(pIoStorage->pTreeMetaXfers);
+    }
     else
-        RTMemFree(pIoStorage);
-
+        rc = VERR_NO_MEMORY;
+
+    RTMemFree(pIoStorage);
     return rc;
+}
+
+static int vdIOTreeMetaXferDestroy(PAVLRFOFFNODECORE pNode, void *pvUser)
+{
+    AssertMsgFailed(("Tree should be empty at this point!\n"));
+    return VINF_SUCCESS;
 }
 
@@ -1993,4 +2233,6 @@
     AssertRC(rc);
 
+    RTAvlrFileOffsetDestroy(pIoStorage->pTreeMetaXfers, vdIOTreeMetaXferDestroy, NULL);
+    RTMemFree(pIoStorage->pTreeMetaXfers);
     RTMemFree(pIoStorage);
     return VINF_SUCCESS;
@@ -2063,4 +2305,6 @@
                  pvUser, pIoStorage, uOffset, pIoCtx, cbRead));
 
+    VD_THREAD_IS_CRITSECT_OWNER(pDisk);
+
     /* Build the S/G array and spawn a new I/O task */
     while (cbRead)
@@ -2075,4 +2319,72 @@
 
         LogFlow(("Reading %u bytes into %u segments\n", cbTaskRead, cSegments));
+
+#ifdef RT_STRICT
+        for (unsigned i = 0; i < cSegments; i++)
+                AssertMsg(aSeg[i].pvSeg && !(aSeg[i].cbSeg % 512),
+                          ("Segment %u is invalid\n", i));
+#endif
+
+        PVDIOTASK pIoTask = vdIoTaskUserAlloc(pIoStorage, NULL, NULL, pIoCtx, cbTaskRead);
+
+        if (!pIoTask)
+            return VERR_NO_MEMORY;
+
+        ASMAtomicIncU32(&pIoCtx->cDataTransfersPending);
+
+        void *pvTask;
+        rc = pDisk->pInterfaceAsyncIOCallbacks->pfnReadAsync(pDisk->pInterfaceAsyncIO->pvUser,
+                                                             pIoStorage->u.pStorage,
+                                                             uOffset, aSeg, cSegments,
+                                                             cbTaskRead, pIoTask,
+                                                             &pvTask);
+        if (RT_SUCCESS(rc))
+        {
+            AssertMsg(cbTaskRead <= pIoCtx->cbTransferLeft, ("Impossible!\n"));
+            ASMAtomicSubU32(&pIoCtx->cbTransferLeft, cbTaskRead);
+            ASMAtomicDecU32(&pIoCtx->cDataTransfersPending);
+            vdIoTaskFree(pDisk, pIoTask);
+        }
+        else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+        {
+            ASMAtomicDecU32(&pIoCtx->cDataTransfersPending);
+            break;
+        }
+
+        uOffset += cbTaskRead;
+        cbRead  -= cbTaskRead;
+    }
+
+    LogFlowFunc(("returns rc=%Rrc\n", rc));
+    return rc;
+}
+
+static int vdIOWriteUserAsync(void *pvUser, PVDIOSTORAGE pIoStorage,
+                              uint64_t uOffset, PVDIOCTX pIoCtx,
+                              size_t cbWrite,
+                              PFNVDXFERCOMPLETED pfnComplete,
+                              void *pvCompleteUser)
+{
+    int rc = VINF_SUCCESS;
+    PVDIMAGE pImage = (PVDIMAGE)pvUser;
+    PVBOXHDD pDisk  = pImage->pDisk;
+
+    LogFlowFunc(("pvUser=%#p pIoStorage=%#p uOffset=%llu pIoCtx=%#p cbWrite=%u\n",
+                 pvUser, pIoStorage, uOffset, pIoCtx, cbWrite));
+
+    VD_THREAD_IS_CRITSECT_OWNER(pDisk);
+
+    /* Build the S/G array and spawn a new I/O task */
+    while (cbWrite)
+    {
+        RTSGSEG  aSeg[VD_IO_TASK_SEGMENTS_MAX];
+        unsigned cSegments   = VD_IO_TASK_SEGMENTS_MAX;
+        size_t   cbTaskWrite = 0;
+
+        cbTaskWrite = RTSgBufSegArrayCreate(&pIoCtx->SgBuf, aSeg, &cSegments, cbWrite);
+
+        AssertMsg(cbTaskWrite <= cbWrite, ("Invalid number of bytes to write\n"));
+
+        LogFlow(("Writing %u bytes from %u segments\n", cbTaskWrite, cSegments));
 
 #ifdef DEBUG
@@ -2082,5 +2394,5 @@
 #endif
 
-        PVDIOTASK pIoTask = vdIoTaskUserAlloc(pDisk, pIoCtx, cbTaskRead);
+        PVDIOTASK pIoTask = vdIoTaskUserAlloc(pIoStorage, pfnComplete, pvCompleteUser, pIoCtx, cbTaskWrite);
 
         if (!pIoTask)
@@ -2090,75 +2402,10 @@
 
         void *pvTask;
-        int rc2 = pDisk->pInterfaceAsyncIOCallbacks->pfnReadAsync(pDisk->pInterfaceAsyncIO->pvUser,
-                                                                  pIoStorage->u.pStorage,
-                                                                  uOffset, aSeg, cSegments,
-                                                                  cbTaskRead, pIoTask,
-                                                                  &pvTask);
-        if (rc2 == VINF_SUCCESS)
-        {
-            AssertMsg(cbTaskRead <= pIoCtx->cbTransferLeft, ("Impossible!\n"));
-            ASMAtomicSubU32(&pIoCtx->cbTransferLeft, cbTaskRead);
-            ASMAtomicDecU32(&pIoCtx->cDataTransfersPending);
-            vdIoTaskFree(pDisk, pIoTask);
-        }
-        else if (rc2 == VERR_VD_ASYNC_IO_IN_PROGRESS)
-            rc = VINF_SUCCESS;
-        else if (RT_FAILURE(rc2))
-        {
-            rc = rc2;
-            break;
-        }
-
-        uOffset += cbTaskRead;
-        cbRead  -= cbTaskRead;
-    }
-
-    return rc;
-}
-
-static int vdIOWriteUserAsync(void *pvUser, PVDIOSTORAGE pIoStorage,
-                              uint64_t uOffset, PVDIOCTX pIoCtx,
-                              size_t cbWrite)
-{
-    int rc = VINF_SUCCESS;
-    PVDIMAGE pImage = (PVDIMAGE)pvUser;
-    PVBOXHDD pDisk  = pImage->pDisk;
-
-    LogFlowFunc(("pvUser=%#p pIoStorage=%#p uOffset=%llu pIoCtx=%#p cbWrite=%u\n",
-                 pvUser, pIoStorage, uOffset, pIoCtx, cbWrite));
-
-    /* Build the S/G array and spawn a new I/O task */
-    while (cbWrite)
-    {
-        RTSGSEG  aSeg[VD_IO_TASK_SEGMENTS_MAX];
-        unsigned cSegments   = VD_IO_TASK_SEGMENTS_MAX;
-        size_t   cbTaskWrite = 0;
-
-        cbTaskWrite = RTSgBufSegArrayCreate(&pIoCtx->SgBuf, aSeg, &cSegments, cbWrite);
-
-        AssertMsg(cbTaskWrite <= cbWrite, ("Invalid number of bytes to write\n"));
-
-        LogFlow(("Writing %u bytes from %u segments\n", cbTaskWrite, cSegments));
-
-#ifdef DEBUG
-        for (unsigned i = 0; i < cSegments; i++)
-                AssertMsg(aSeg[i].pvSeg && !(aSeg[i].cbSeg % 512),
-                          ("Segment %u is invalid\n", i));
-#endif
-
-        PVDIOTASK pIoTask = vdIoTaskUserAlloc(pDisk, pIoCtx, cbTaskWrite);
-
-        if (!pIoTask)
-            return VERR_NO_MEMORY;
-
-        ASMAtomicIncU32(&pIoCtx->cDataTransfersPending);
-
-        void *pvTask;
-        int rc2 = pDisk->pInterfaceAsyncIOCallbacks->pfnWriteAsync(pDisk->pInterfaceAsyncIO->pvUser,
-                                                                   pIoStorage->u.pStorage,
-                                                                   uOffset, aSeg, cSegments,
-                                                                   cbTaskWrite, pIoTask,
-                                                                   &pvTask);
-        if (rc2 == VINF_SUCCESS)
+        rc = pDisk->pInterfaceAsyncIOCallbacks->pfnWriteAsync(pDisk->pInterfaceAsyncIO->pvUser,
+                                                              pIoStorage->u.pStorage,
+                                                              uOffset, aSeg, cSegments,
+                                                              cbTaskWrite, pIoTask,
+                                                              &pvTask);
+        if (RT_SUCCESS(rc))
         {
             AssertMsg(cbTaskWrite <= pIoCtx->cbTransferLeft, ("Impossible!\n"));
@@ -2167,9 +2414,7 @@
             vdIoTaskFree(pDisk, pIoTask);
         }
-        else if (rc2 == VERR_VD_ASYNC_IO_IN_PROGRESS)
-            rc = VINF_SUCCESS;
-        else if (RT_FAILURE(rc2))
-        {
-            rc = rc2;
+        else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+        {
+            ASMAtomicDecU32(&pIoCtx->cDataTransfersPending);
             break;
         }
@@ -2185,6 +2430,5 @@
                              uint64_t uOffset, void *pvBuf,
                              size_t cbRead, PVDIOCTX pIoCtx,
-                             PFNVDMETACOMPLETED pfnMetaComplete,
-                             void *pvMetaUser)
+                             PPVDMETAXFER ppMetaXfer)
 {
     PVDIMAGE pImage = (PVDIMAGE)pvUser;
@@ -2193,30 +2437,79 @@
     RTSGSEG Seg;
     PVDIOTASK pIoTask;
+    PVDMETAXFER pMetaXfer = NULL;
     void *pvTask = NULL;
 
-    pIoTask = vdIoTaskMetaAlloc(pDisk, pIoCtx, VDIOCTXTXDIR_READ, pImage,
-                                pfnMetaComplete, pvMetaUser);
-    if (!pIoTask)
-        return VERR_NO_MEMORY;
-
-    Seg.cbSeg = cbRead;
-    Seg.pvSeg = pvBuf;
-
-    ASMAtomicIncU32(&pIoCtx->cMetaTransfersPending);
-
-    int rc2 = pDisk->pInterfaceAsyncIOCallbacks->pfnReadAsync(pDisk->pInterfaceAsyncIO->pvUser,
-                                                              pIoStorage->u.pStorage,
-                                                              uOffset, &Seg, 1,
-                                                              cbRead, pIoTask,
-                                                              &pvTask);
-    if (rc2 == VINF_SUCCESS)
-    {
-        ASMAtomicDecU32(&pIoCtx->cMetaTransfersPending);
-        vdIoTaskFree(pDisk, pIoTask);
-    }
-    else if (rc2 == VERR_VD_ASYNC_IO_IN_PROGRESS)
-        rc = VERR_VD_NOT_ENOUGH_METADATA;
-    else if (RT_FAILURE(rc2))
-        rc = rc2;
+    LogFlowFunc(("pvUser=%#p pIoStorage=%#p uOffset=%llu pvBuf=%#p cbRead=%u\n",
+                 pvUser, pIoStorage, uOffset, pvBuf, cbRead));
+
+    VD_THREAD_IS_CRITSECT_OWNER(pDisk);
+
+    pMetaXfer = (PVDMETAXFER)RTAvlrFileOffsetGet(pIoStorage->pTreeMetaXfers, uOffset);
+    if (!pMetaXfer)
+    {
+        /* Allocate a new meta transfer. */
+        pMetaXfer = vdMetaXferAlloc(pImage, pIoStorage, uOffset, cbRead);
+        if (!pMetaXfer)
+            return VERR_NO_MEMORY;
+
+        pIoTask = vdIoTaskMetaAlloc(pIoStorage, NULL, NULL, pMetaXfer);
+        if (!pIoTask)
+        {
+            RTMemFree(pMetaXfer);
+            return VERR_NO_MEMORY;
+        }
+
+        Seg.cbSeg = cbRead;
+        Seg.pvSeg = pMetaXfer->abData;
+
+        VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_READ);
+        rc = pDisk->pInterfaceAsyncIOCallbacks->pfnReadAsync(pDisk->pInterfaceAsyncIO->pvUser,
+                                                             pIoStorage->u.pStorage,
+                                                             uOffset, &Seg, 1,
+                                                             cbRead, pIoTask,
+                                                             &pvTask);
+        if (RT_SUCCESS(rc))
+        {
+            VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_NONE);
+            vdIoTaskFree(pDisk, pIoTask);
+        }
+        else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+            rc = VERR_VD_NOT_ENOUGH_METADATA;
+
+        if (RT_SUCCESS(rc) || rc == VERR_VD_NOT_ENOUGH_METADATA)
+        {
+            bool fInserted = RTAvlrFileOffsetInsert(pIoStorage->pTreeMetaXfers, &pMetaXfer->Core);
+            Assert(fInserted);
+        }
+        else
+            RTMemFree(pMetaXfer);
+    }
+
+    Assert(VALID_PTR(pMetaXfer) || RT_FAILURE(rc));
+
+    if (RT_SUCCESS(rc) || rc == VERR_VD_NOT_ENOUGH_METADATA)
+    {
+        /* If it is pending add the request to the list. */
+        if (VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_READ)
+        {
+            PVDIOCTXDEFERRED pDeferred = (PVDIOCTXDEFERRED)RTMemAllocZ(sizeof(VDIOCTXDEFERRED));
+            AssertPtr(pDeferred);
+
+            RTListInit(&pDeferred->NodeDeferred);
+            pDeferred->pIoCtx = pIoCtx;
+
+            ASMAtomicIncU32(&pIoCtx->cMetaTransfersPending);
+            RTListAppend(&pMetaXfer->ListIoCtxWaiting, &pDeferred->NodeDeferred);
+            rc = VERR_VD_NOT_ENOUGH_METADATA;
+        }
+        else
+        {
+            /* Transfer the data. */
+            pMetaXfer->cRefs++;
+            Assert(pMetaXfer->cbMeta >= cbRead);
+            memcpy(pvBuf, pMetaXfer->abData, cbRead);
+            *ppMetaXfer = pMetaXfer;
+        }
+    }
 
     return rc;
@@ -2226,6 +2519,6 @@
                               uint64_t uOffset, void *pvBuf,
                               size_t cbWrite, PVDIOCTX pIoCtx,
-                              PFNVDMETACOMPLETED pfnMetaComplete,
-                              void *pvMetaUser)
+                              PFNVDXFERCOMPLETED pfnComplete,
+                              void *pvCompleteUser)
 {
     PVDIMAGE pImage = (PVDIMAGE)pvUser;
@@ -2234,36 +2527,100 @@
     RTSGSEG Seg;
     PVDIOTASK pIoTask;
+    PVDMETAXFER pMetaXfer = NULL;
     void *pvTask = NULL;
 
-    pIoTask = vdIoTaskMetaAlloc(pDisk, pIoCtx, VDIOCTXTXDIR_WRITE, pImage,
-                                pfnMetaComplete, pvMetaUser);
-    if (!pIoTask)
-        return VERR_NO_MEMORY;
-
-    Seg.cbSeg = cbWrite;
-    Seg.pvSeg = pvBuf;
-
-    ASMAtomicIncU32(&pIoCtx->cMetaTransfersPending);
-
-    int rc2 = pDisk->pInterfaceAsyncIOCallbacks->pfnWriteAsync(pDisk->pInterfaceAsyncIO->pvUser,
-                                                               pIoStorage->u.pStorage,
-                                                               uOffset, &Seg, 1,
-                                                               cbWrite, pIoTask,
-                                                               &pvTask);
-    if (rc2 == VINF_SUCCESS)
-    {
-        ASMAtomicDecU32(&pIoCtx->cMetaTransfersPending);
-        vdIoTaskFree(pDisk, pIoTask);
-    }
-    else if (rc2 == VERR_VD_ASYNC_IO_IN_PROGRESS)
-        rc = VINF_SUCCESS;
-    else if (RT_FAILURE(rc2))
-        rc = rc2;
+    LogFlowFunc(("pvUser=%#p pIoStorage=%#p uOffset=%llu pvBuf=%#p cbWrite=%u\n",
+                 pvUser, pIoStorage, uOffset, pvBuf, cbWrite));
+
+    VD_THREAD_IS_CRITSECT_OWNER(pDisk);
+
+    pMetaXfer = (PVDMETAXFER)RTAvlrFileOffsetGet(pIoStorage->pTreeMetaXfers, uOffset);
+    if (!pMetaXfer)
+    {
+        /* Allocate a new meta transfer. */
+        pMetaXfer = vdMetaXferAlloc(pImage, pIoStorage, uOffset, cbWrite);
+        if (!pMetaXfer)
+            return VERR_NO_MEMORY;
+
+        pIoTask = vdIoTaskMetaAlloc(pIoStorage, pfnComplete, pvCompleteUser, pMetaXfer);
+        if (!pIoTask)
+        {
+            RTMemFree(pMetaXfer);
+            return VERR_NO_MEMORY;
+        }
+
+        memcpy(pMetaXfer->abData, pvBuf, cbWrite);
+        Seg.cbSeg = cbWrite;
+        Seg.pvSeg = pMetaXfer->abData;
+
+        ASMAtomicIncU32(&pIoCtx->cMetaTransfersPending);
+
+        VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_WRITE);
+        rc = pDisk->pInterfaceAsyncIOCallbacks->pfnWriteAsync(pDisk->pInterfaceAsyncIO->pvUser,
+                                                              pIoStorage->u.pStorage,
+                                                              uOffset, &Seg, 1,
+                                                              cbWrite, pIoTask,
+                                                              &pvTask);
+        if (RT_SUCCESS(rc))
+        {
+            VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_NONE);
+            ASMAtomicDecU32(&pIoCtx->cMetaTransfersPending);
+            vdIoTaskFree(pDisk, pIoTask);
+            RTMemFree(pMetaXfer);
+            pMetaXfer = NULL;
+        }
+        else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+        {
+            PVDIOCTXDEFERRED pDeferred = (PVDIOCTXDEFERRED)RTMemAllocZ(sizeof(VDIOCTXDEFERRED));
+            AssertPtr(pDeferred);
+
+            RTListInit(&pDeferred->NodeDeferred);
+            pDeferred->pIoCtx = pIoCtx;
+
+            bool fInserted = RTAvlrFileOffsetInsert(pIoStorage->pTreeMetaXfers, &pMetaXfer->Core);
+            Assert(fInserted);
+
+            RTListAppend(&pMetaXfer->ListIoCtxWaiting, &pDeferred->NodeDeferred);
+        }
+        else
+        {
+            RTMemFree(pMetaXfer);
+            pMetaXfer = NULL;
+        }
+    }
+
+    Assert(VALID_PTR(pMetaXfer) || RT_SUCCESS(rc) || rc != VERR_VD_ASYNC_IO_IN_PROGRESS);
 
     return rc;
 }
 
+static void vdIOMetaXferRelease(void *pvUser, PVDMETAXFER pMetaXfer)
+{
+    PVDIMAGE pImage = (PVDIMAGE)pvUser;
+    PVDIOSTORAGE pIoStorage = pMetaXfer->pIoStorage;
+
+    VD_THREAD_IS_CRITSECT_OWNER(pImage->pDisk);
+
+    Assert(   VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_NONE
+           || VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_WRITE);
+    Assert(pMetaXfer->cRefs > 0);
+
+    pMetaXfer->cRefs--;
+    if (   !pMetaXfer->cRefs
+        && RTListIsEmpty(&pMetaXfer->ListIoCtxWaiting)
+        && VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_NONE)
+    {
+        /* Free the meta data entry. */
+        bool fRemoved = RTAvlrFileOffsetRemove(pIoStorage->pTreeMetaXfers, pMetaXfer->Core.Key);
+        AssertMsg(fRemoved, ("Metadata transfer wasn't removed\n"));
+
+        RTMemFree(pMetaXfer);
+    }
+}
+
 static int vdIOFlushAsync(void *pvUser, PVDIOSTORAGE pIoStorage,
-                          PVDIOCTX pIoCtx)
+                          PVDIOCTX pIoCtx,
+                          PFNVDXFERCOMPLETED pfnComplete,
+                          void *pvCompleteUser)
 {
     PVDIMAGE pImage = (PVDIMAGE)pvUser;
@@ -2271,26 +2628,48 @@
     int rc = VINF_SUCCESS;
     PVDIOTASK pIoTask;
+    PVDMETAXFER pMetaXfer = NULL;
     void *pvTask = NULL;
 
-    pIoTask = vdIoTaskMetaAlloc(pDisk, pIoCtx, VDIOCTXTXDIR_FLUSH, pImage,
-                                NULL, NULL);
+    VD_THREAD_IS_CRITSECT_OWNER(pDisk);
+
+    LogFlowFunc(("pvUser=%#p pIoStorage=%#p pIoCtx=%#p\n",
+                 pvUser, pIoStorage, pIoCtx));
+
+    /* Allocate a new meta transfer. */
+    pMetaXfer = vdMetaXferAlloc(pImage, pIoStorage, 0, 0);
+    if (!pMetaXfer)
+        return VERR_NO_MEMORY;
+
+    pIoTask = vdIoTaskMetaAlloc(pIoStorage, pfnComplete, pvUser, pMetaXfer);
     if (!pIoTask)
+    {
+        RTMemFree(pMetaXfer);
         return VERR_NO_MEMORY;
+    }
 
     ASMAtomicIncU32(&pIoCtx->cMetaTransfersPending);
 
-    int rc2 = pDisk->pInterfaceAsyncIOCallbacks->pfnFlushAsync(pDisk->pInterfaceAsyncIO->pvUser,
-                                                               pIoStorage->u.pStorage,
-                                                               pIoTask,
-                                                               &pvTask);
-    if (rc2 == VINF_SUCCESS)
-    {
+    PVDIOCTXDEFERRED pDeferred = (PVDIOCTXDEFERRED)RTMemAllocZ(sizeof(VDIOCTXDEFERRED));
+    AssertPtr(pDeferred);
+
+    RTListInit(&pDeferred->NodeDeferred);
+    pDeferred->pIoCtx = pIoCtx;
+
+    RTListAppend(&pMetaXfer->ListIoCtxWaiting, &pDeferred->NodeDeferred);
+    VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_FLUSH);
+    rc = pDisk->pInterfaceAsyncIOCallbacks->pfnFlushAsync(pDisk->pInterfaceAsyncIO->pvUser,
+                                                          pIoStorage->u.pStorage,
+                                                          pIoTask,
+                                                          &pvTask);
+    if (RT_SUCCESS(rc))
+    {
+        VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_NONE);
         ASMAtomicDecU32(&pIoCtx->cMetaTransfersPending);
         vdIoTaskFree(pDisk, pIoTask);
-    }
-    else if (rc2 == VERR_VD_ASYNC_IO_IN_PROGRESS)
-        rc = VINF_SUCCESS;
-    else if (RT_FAILURE(rc2))
-        rc = rc2;
+        RTMemFree(pDeferred);
+        RTMemFree(pMetaXfer);
+    }
+    else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+        RTMemFree(pMetaXfer);
 
     return rc;
@@ -2300,4 +2679,9 @@
                               void *pvBuf, size_t cbBuf)
 {
+    PVDIMAGE pImage = (PVDIMAGE)pvUser;
+    PVBOXHDD pDisk  = pImage->pDisk;
+
+    VD_THREAD_IS_CRITSECT_OWNER(pDisk);
+
     return vdIoCtxCopyTo(pIoCtx, (uint8_t *)pvBuf, cbBuf);
 }
@@ -2306,4 +2690,9 @@
                                 void *pvBuf, size_t cbBuf)
 {
+    PVDIMAGE pImage = (PVDIMAGE)pvUser;
+    PVBOXHDD pDisk  = pImage->pDisk;
+
+    VD_THREAD_IS_CRITSECT_OWNER(pDisk);
+
     return vdIoCtxCopyFrom(pIoCtx, (uint8_t *)pvBuf, cbBuf);
 }
@@ -2312,4 +2701,9 @@
                            int ch, size_t cb)
 {
+    PVDIMAGE pImage = (PVDIMAGE)pvUser;
+    PVBOXHDD pDisk  = pImage->pDisk;
+
+    VD_THREAD_IS_CRITSECT_OWNER(pDisk);
+
     return vdIoCtxSet(pIoCtx, ch, cb);
 }
@@ -2636,21 +3030,22 @@
 
             /* Create the I/O callback table. */
-            pDisk->VDIIOCallbacks.cbSize            = sizeof(VDINTERFACEIO);
-            pDisk->VDIIOCallbacks.enmInterface      = VDINTERFACETYPE_IO;
-            pDisk->VDIIOCallbacks.pfnOpen           = vdIOOpen;
-            pDisk->VDIIOCallbacks.pfnClose          = vdIOClose;
-            pDisk->VDIIOCallbacks.pfnGetSize        = vdIOGetSize;
-            pDisk->VDIIOCallbacks.pfnSetSize        = vdIOSetSize;
-            pDisk->VDIIOCallbacks.pfnReadSync       = vdIOReadSync;
-            pDisk->VDIIOCallbacks.pfnWriteSync      = vdIOWriteSync;
-            pDisk->VDIIOCallbacks.pfnFlushSync      = vdIOFlushSync;
-            pDisk->VDIIOCallbacks.pfnReadUserAsync  = vdIOReadUserAsync;
-            pDisk->VDIIOCallbacks.pfnWriteUserAsync = vdIOWriteUserAsync;
-            pDisk->VDIIOCallbacks.pfnReadMetaAsync  = vdIOReadMetaAsync;
-            pDisk->VDIIOCallbacks.pfnWriteMetaAsync = vdIOWriteMetaAsync;
-            pDisk->VDIIOCallbacks.pfnFlushAsync     = vdIOFlushAsync;
-            pDisk->VDIIOCallbacks.pfnIoCtxCopyFrom  = vdIOIoCtxCopyFrom;
-            pDisk->VDIIOCallbacks.pfnIoCtxCopyTo    = vdIOIoCtxCopyTo;
-            pDisk->VDIIOCallbacks.pfnIoCtxSet       = vdIOIoCtxSet;
+            pDisk->VDIIOCallbacks.cbSize             = sizeof(VDINTERFACEIO);
+            pDisk->VDIIOCallbacks.enmInterface       = VDINTERFACETYPE_IO;
+            pDisk->VDIIOCallbacks.pfnOpen            = vdIOOpen;
+            pDisk->VDIIOCallbacks.pfnClose           = vdIOClose;
+            pDisk->VDIIOCallbacks.pfnGetSize         = vdIOGetSize;
+            pDisk->VDIIOCallbacks.pfnSetSize         = vdIOSetSize;
+            pDisk->VDIIOCallbacks.pfnReadSync        = vdIOReadSync;
+            pDisk->VDIIOCallbacks.pfnWriteSync       = vdIOWriteSync;
+            pDisk->VDIIOCallbacks.pfnFlushSync       = vdIOFlushSync;
+            pDisk->VDIIOCallbacks.pfnReadUserAsync   = vdIOReadUserAsync;
+            pDisk->VDIIOCallbacks.pfnWriteUserAsync  = vdIOWriteUserAsync;
+            pDisk->VDIIOCallbacks.pfnReadMetaAsync   = vdIOReadMetaAsync;
+            pDisk->VDIIOCallbacks.pfnWriteMetaAsync  = vdIOWriteMetaAsync;
+            pDisk->VDIIOCallbacks.pfnMetaXferRelease = vdIOMetaXferRelease;
+            pDisk->VDIIOCallbacks.pfnFlushAsync      = vdIOFlushAsync;
+            pDisk->VDIIOCallbacks.pfnIoCtxCopyFrom   = vdIOIoCtxCopyFrom;
+            pDisk->VDIIOCallbacks.pfnIoCtxCopyTo     = vdIOIoCtxCopyTo;
+            pDisk->VDIIOCallbacks.pfnIoCtxSet        = vdIOIoCtxSet;
 
             *ppDisk = pDisk;
Index: trunk/src/VBox/Devices/Storage/VDIHDDCore.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/VDIHDDCore.cpp	(revision 30554)
+++ trunk/src/VBox/Devices/Storage/VDIHDDCore.cpp	(revision 30555)
@@ -206,19 +206,19 @@
 
 static int vdiFileWriteMetaAsync(PVDIIMAGEDESC pImage, uint64_t off, void *pcvBuf, size_t cbWrite,
-                                 PVDIOCTX pIoCtx, PFNVDMETACOMPLETED pfnMetaComplete, void *pvMetaUser)
+                                 PVDIOCTX pIoCtx)
 {
     return pImage->pInterfaceIOCallbacks->pfnWriteMetaAsync(pImage->pInterfaceIO->pvUser,
                                                             pImage->pStorage,
                                                             off, pcvBuf, cbWrite, pIoCtx,
-                                                            pfnMetaComplete, pvMetaUser);
+                                                            NULL, NULL);
 }
 
 static int vdiFileReadMetaAsync(PVDIIMAGEDESC pImage, uint64_t off, void *pvBuf, size_t cbRead,
-                                PVDIOCTX pIoCtx, PFNVDMETACOMPLETED pfnMetaComplete, void *pvMetaUser)
+                                PVDIOCTX pIoCtx)
 {
     return pImage->pInterfaceIOCallbacks->pfnReadMetaAsync(pImage->pInterfaceIO->pvUser,
                                                            pImage->pStorage,
                                                            off, pvBuf, cbRead, pIoCtx,
-                                                           pfnMetaComplete, pvMetaUser);
+                                                           NULL);
 }
 
@@ -235,5 +235,6 @@
 {
     return pImage->pInterfaceIOCallbacks->pfnFlushAsync(pImage->pInterfaceIO->pvUser,
-                                                        pImage->pStorage, pIoCtx);
+                                                        pImage->pStorage, pIoCtx,
+                                                        NULL, NULL);
 }
 
@@ -873,5 +874,5 @@
             rc = vdiFileWriteMetaAsync(pImage, sizeof(VDIPREHEADER),
                                        &pImage->Header.u.v0, sizeof(pImage->Header.u.v0),
-                                       pIoCtx, NULL, NULL);
+                                       pIoCtx);
             break;
         case 1:
@@ -879,9 +880,9 @@
                 rc = vdiFileWriteMetaAsync(pImage, sizeof(VDIPREHEADER),
                                            &pImage->Header.u.v1, sizeof(pImage->Header.u.v1),
-                                           pIoCtx, NULL, NULL);
+                                           pIoCtx);
             else
                 rc = vdiFileWriteMetaAsync(pImage, sizeof(VDIPREHEADER),
                                            &pImage->Header.u.v1plus, sizeof(pImage->Header.u.v1plus),
-                                           pIoCtx, NULL, NULL);
+                                           pIoCtx);
             break;
         default:
@@ -889,5 +890,6 @@
             break;
     }
-    AssertMsgRC(rc, ("vdiUpdateHeader failed, filename=\"%s\" rc=%Rrc\n", pImage->pszFilename, rc));
+    AssertMsg(RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS,
+              ("vdiUpdateHeader failed, filename=\"%s\" rc=%Rrc\n", pImage->pszFilename, rc));
     return rc;
 }
@@ -922,5 +924,5 @@
     /* Update image header. */
     int rc = vdiUpdateHeaderAsync(pImage, pIoCtx);
-    if (RT_SUCCESS(rc))
+    if (RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
     {
         /* write only one block pointer. */
@@ -929,8 +931,8 @@
                                    &pImage->paBlocks[uBlock],
                                    sizeof(VDIIMAGEBLOCKPOINTER),
-                                   pIoCtx,
-                                   NULL, NULL);
-        AssertMsgRC(rc, ("vdiUpdateBlockInfo failed to update block=%u, filename=\"%s\", rc=%Rrc\n",
-                         uBlock, pImage->pszFilename, rc));
+                                   pIoCtx);
+        AssertMsg(RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS,
+                  ("vdiUpdateBlockInfo failed to update block=%u, filename=\"%s\", rc=%Rrc\n",
+                  uBlock, pImage->pszFilename, rc));
     }
     return rc;
@@ -955,15 +957,21 @@
  * Internal: Flush the image file to disk - async version.
  */
-static void vdiFlushImageAsync(PVDIIMAGEDESC pImage, PVDIOCTX pIoCtx)
-{
+static int vdiFlushImageAsync(PVDIIMAGEDESC pImage, PVDIOCTX pIoCtx)
+{
+    int rc = VINF_SUCCESS;
+
     if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY))
     {
         /* Save header. */
-        int rc = vdiUpdateHeaderAsync(pImage, pIoCtx);
-        AssertMsgRC(rc, ("vdiUpdateHeaderAsync() failed, filename=\"%s\", rc=%Rrc\n",
-                         pImage->pszFilename, rc));
+        rc = vdiUpdateHeaderAsync(pImage, pIoCtx);
+        AssertMsg(RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS,
+                  ("vdiUpdateHeaderAsync() failed, filename=\"%s\", rc=%Rrc\n",
+                  pImage->pszFilename, rc));
         rc = vdiFileFlushAsync(pImage, pIoCtx);
-        AssertMsgRC(rc, ("Flushing data to disk failed rc=%Rrc\n", rc));
-    }
+        AssertMsg(RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS,
+                  ("Flushing data to disk failed rc=%Rrc\n", rc));
+    }
+
+    return rc;
 }
 
@@ -2207,5 +2215,6 @@
                                                                       pImage->pStorage,
                                                                       u64Offset,
-                                                                      pIoCtx, cbToWrite);
+                                                                      pIoCtx, cbToWrite,
+                                                                      NULL, NULL);
                 if (RT_UNLIKELY(RT_FAILURE_NP(rc) && (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)))
                     goto out;
@@ -2214,5 +2223,5 @@
 
                 rc = vdiUpdateBlockInfoAsync(pImage, uBlock, pIoCtx);
-                if (RT_FAILURE(rc))
+                if (RT_FAILURE(rc) && (rc != VERR_VD_ASYNC_IO_IN_PROGRESS))
                     goto out;
 
@@ -2237,5 +2246,6 @@
                                                                   pImage->pStorage,
                                                                   u64Offset,
-                                                                  pIoCtx, cbToWrite);
+                                                                  pIoCtx, cbToWrite,
+                                                                  NULL, NULL);
         }
     } while (0);
@@ -2257,5 +2267,5 @@
     Assert(pImage);
 
-    vdiFlushImageAsync(pImage, pIoCtx);
+    rc = vdiFlushImageAsync(pImage, pIoCtx);
     LogFlowFunc(("returns %Rrc\n", rc));
     return rc;
Index: trunk/src/VBox/Devices/Storage/VHDHDDCore.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/VHDHDDCore.cpp	(revision 30554)
+++ trunk/src/VBox/Devices/Storage/VHDHDDCore.cpp	(revision 30555)
@@ -198,4 +198,49 @@
 } VHDIMAGE, *PVHDIMAGE;
 
+/**
+ * Structure tracking the expansion process of the image
+ * for async access.
+ */
+typedef struct VHDIMAGEEXPAND
+{
+    /** Flag indicating the status of each step. */
+    volatile uint32_t fFlags;
+    /** The index in the  block allocation table which is written. */
+    uint32_t          idxBatAllocated;
+    /** Big endian representation of the block index
+     * which is written in the BAT. */
+    uint32_t          idxBlockBe;
+    /** Old end of the file - used for rollback in case of an error. */
+    uint64_t          cbEofOld;
+    /** Sector bitmap written to the new block - variable in size. */
+    uint8_t           au8Bitmap[1];
+} VHDIMAGEEXPAND, *PVHDIMAGEEXPAND;
+
+/**
+ * Flag defines
+ */
+#define VHDIMAGEEXPAND_STEP_IN_PROGRESS (0x0)
+#define VHDIMAGEEXPAND_STEP_FAILED      (0x2)
+#define VHDIMAGEEXPAND_STEP_SUCCESS     (0x3)
+/** All steps completed successfully. */
+#define VHDIMAGEEXPAND_ALL_SUCCESS      (0xff)
+/** All steps completed (no success indicator) */
+#define VHDIMAGEEXPAND_ALL_COMPLETE     (0xaa)
+
+/** Every status field has 2 bits so we can encode 4 steps in one byte. */
+#define VHDIMAGEEXPAND_STATUS_MASK              0x03
+#define VHDIMAGEEXPAND_BLOCKBITMAP_STATUS_SHIFT 0x00
+#define VHDIMAGEEXPAND_USERBLOCK_STATUS_SHIFT   0x02
+#define VHDIMAGEEXPAND_FOOTER_STATUS_SHIFT      0x04
+#define VHDIMAGEEXPAND_BAT_STATUS_SHIFT         0x06
+
+/**
+ * Helper macros to get and set the status field.
+ */
+#define VHDIMAGEEXPAND_STATUS_GET(fFlags, cShift) \
+    (((fFlags) >> (cShift)) & VHDIMAGEEXPAND_STATUS_MASK)
+#define VHDIMAGEEXPAND_STATUS_SET(fFlags, cShift, uVal) \
+    ASMAtomicOrU32(&(fFlags), ((uVal) & VHDIMAGEEXPAND_STATUS_MASK) << (cShift))
+
 /*******************************************************************************
 *   Static Variables                                                           *
@@ -563,4 +608,102 @@
 }
 
+/**
+ * Internal: called when the async expansion process completed (failure or success).
+ *           Will do the necessary rollback if an error occurred.
+ */
+static int vhdAsyncExpansionComplete(PVHDIMAGE pImage, PVDIOCTX pIoCtx, PVHDIMAGEEXPAND pExpand)
+{
+    int rc = VINF_SUCCESS;
+    uint32_t fFlags = ASMAtomicReadU32(&pExpand->fFlags);
+    bool fIoInProgress = false;
+
+    /* Quick path, check if everything succeeded. */
+    if (fFlags == VHDIMAGEEXPAND_ALL_SUCCESS)
+    {
+        RTMemFree(pExpand);
+    }
+    else
+    {
+        uint32_t uStatus;
+
+        uStatus = VHDIMAGEEXPAND_STATUS_GET(pExpand->fFlags, VHDIMAGEEXPAND_BAT_STATUS_SHIFT);
+        if (   uStatus == VHDIMAGEEXPAND_STEP_FAILED
+            || uStatus == VHDIMAGEEXPAND_STEP_SUCCESS)
+        {
+            /* Undo and restore the old value. */
+            pImage->pBlockAllocationTable[pExpand->idxBatAllocated] == ~0U;
+
+            /* Restore the old value on the disk.
+             * No need for a completion callback because we can't
+             * do anything if this fails. */
+            if (uStatus == VHDIMAGEEXPAND_STEP_SUCCESS)
+            {
+                rc = pImage->pInterfaceIOCallbacks->pfnWriteMetaAsync(pImage->pInterfaceIO->pvUser,
+                                                                      pImage->pStorage,
+                                                                      pImage->uBlockAllocationTableOffset + pExpand->idxBatAllocated * sizeof(uint32_t),
+                                                                      &pImage->pBlockAllocationTable[pExpand->idxBatAllocated], sizeof(uint32_t), pIoCtx,
+                                                                      NULL, NULL);
+                fIoInProgress |= rc == VERR_VD_ASYNC_IO_IN_PROGRESS;
+            }
+        }
+
+        /* Restore old size (including the footer because another application might
+         * fill up the free space making it impossible to add the footer)
+         * and add the footer at the right place again. */
+        rc = pImage->pInterfaceIOCallbacks->pfnSetSize(pImage->pInterfaceIO->pvUser,
+                                                       pImage->pStorage,
+                                                       pExpand->cbEofOld + sizeof(VHDFooter));
+        AssertRC(rc);
+
+        pImage->uCurrentEndOfFile = pExpand->cbEofOld;
+        rc = pImage->pInterfaceIOCallbacks->pfnWriteMetaAsync(pImage->pInterfaceIO->pvUser,
+                                                              pImage->pStorage,
+                                                              pImage->uCurrentEndOfFile,
+                                                              &pImage->vhdFooterCopy, sizeof(VHDFooter), pIoCtx,
+                                                              NULL, NULL);
+        fIoInProgress |= rc == VERR_VD_ASYNC_IO_IN_PROGRESS;
+    }
+
+    return fIoInProgress ? VERR_VD_ASYNC_IO_IN_PROGRESS : rc;
+}
+
+static int vhdAsyncExpansionStepCompleted(void *pvBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq, unsigned iStep)
+{
+   PVHDIMAGE pImage = (PVHDIMAGE)pvBackendData;
+   PVHDIMAGEEXPAND pExpand = (PVHDIMAGEEXPAND)pvUser;
+
+   LogFlowFunc(("pvBackendData=%#p pIoCtx=%#p pvUser=%#p rcReq=%Rrc iStep=%u\n",
+                pvBackendData, pIoCtx, pvUser, rcReq, iStep));
+
+   if (RT_SUCCESS(rcReq))
+       VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, iStep, VHDIMAGEEXPAND_STEP_SUCCESS);
+   else
+       VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, iStep, VHDIMAGEEXPAND_STEP_FAILED);
+
+   if ((pExpand->fFlags & VHDIMAGEEXPAND_ALL_COMPLETE) == VHDIMAGEEXPAND_ALL_COMPLETE)
+       return vhdAsyncExpansionComplete(pImage, pIoCtx, pExpand);
+
+   return VERR_VD_ASYNC_IO_IN_PROGRESS;
+}
+
+static int vhdAsyncExpansionDataBlockBitmapComplete(void *pvBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+    return vhdAsyncExpansionStepCompleted(pvBackendData, pIoCtx, pvUser, rcReq, VHDIMAGEEXPAND_BLOCKBITMAP_STATUS_SHIFT);
+}
+
+static int vhdAsyncExpansionDataComplete(void *pvBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+    return vhdAsyncExpansionStepCompleted(pvBackendData, pIoCtx, pvUser, rcReq, VHDIMAGEEXPAND_USERBLOCK_STATUS_SHIFT);
+}
+
+static int vhdAsyncExpansionBatUpdateComplete(void *pvBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+    return vhdAsyncExpansionStepCompleted(pvBackendData, pIoCtx, pvUser, rcReq, VHDIMAGEEXPAND_BAT_STATUS_SHIFT);
+}
+
+static int vhdAsyncExpansionFooterUpdateComplete(void *pvBackendData, PVDIOCTX pIoCtx, void *pvUser, int rcReq)
+{
+    return vhdAsyncExpansionStepCompleted(pvBackendData, pIoCtx, pvUser, rcReq, VHDIMAGEEXPAND_FOOTER_STATUS_SHIFT);
+}
 
 static int vhdOpenImage(PVHDIMAGE pImage, unsigned uOpenFlags)
@@ -568,7 +711,4 @@
     uint64_t FileSize;
     VHDFooter vhdFooter;
-
-    if (uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO)
-        return VERR_NOT_SUPPORTED;
 
     pImage->uOpenFlags = uOpenFlags;
@@ -1093,5 +1233,5 @@
  * Internal: Sets the given sector in the sector bitmap.
  */
-DECLINLINE(void) vhdBlockBitmapSectorSet(PVHDIMAGE pImage, uint32_t cBlockBitmapEntry)
+DECLINLINE(bool) vhdBlockBitmapSectorSet(PVHDIMAGE pImage, uint8_t *pu8Bitmap, uint32_t cBlockBitmapEntry)
 {
     uint32_t iBitmap = (cBlockBitmapEntry / 8); /* Byte in the block bitmap. */
@@ -1099,13 +1239,13 @@
     /*
      * The index of the bit in the byte of the data block bitmap.
-     * The most signifcant bit stands for a lower sector number.
+     * The most significant bit stands for a lower sector number.
      */
     uint8_t  iBitInByte = (8-1) - (cBlockBitmapEntry % 8);
-    uint8_t  *puBitmap  = pImage->pu8Bitmap + iBitmap;
-
-    AssertMsg(puBitmap < (pImage->pu8Bitmap + pImage->cbDataBlockBitmap),
+    uint8_t  *puBitmap  = pu8Bitmap + iBitmap;
+
+    AssertMsg(puBitmap < (pu8Bitmap + pImage->cbDataBlockBitmap),
                 ("VHD: Current bitmap position exceeds maximum size of the bitmap\n"));
 
-    ASMBitSet(puBitmap, iBitInByte);
+    return !ASMBitTestAndSet(puBitmap, iBitInByte);
 }
 
@@ -1313,15 +1453,20 @@
         if (RT_SUCCESS(rc))
         {
+            bool fChanged = false;
+
             /* Set the bits for all sectors having been written. */
             for (uint32_t iSector = 0; iSector < (cbToWrite / VHD_SECTOR_SIZE); iSector++)
             {
-                vhdBlockBitmapSectorSet(pImage, cBATEntryIndex);
+                fChanged |= vhdBlockBitmapSectorSet(pImage, pImage->pu8Bitmap, cBATEntryIndex);
                 cBATEntryIndex++;
             }
 
-            /* Write the bitmap back. */
-            rc = vhdFileWriteSync(pImage,
-                ((uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry]) * VHD_SECTOR_SIZE,
-                pImage->pu8Bitmap, pImage->cbDataBlockBitmap, NULL);
+            if (fChanged)
+            {
+                /* Write the bitmap back. */
+                rc = vhdFileWriteSync(pImage,
+                                      ((uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry]) * VHD_SECTOR_SIZE,
+                                      pImage->pu8Bitmap, pImage->cbDataBlockBitmap, NULL);
+            }
         }
     }
@@ -2131,22 +2276,362 @@
 static bool vhdIsAsyncIOSupported(void *pvBackendData)
 {
-    return false;
-}
-
-static int vhdAsyncRead(void *pvBackendData, uint64_t uOffset, size_t cbRead,
+    return true;
+}
+
+static int vhdAsyncRead(void *pBackendData, uint64_t uOffset, size_t cbRead,
                         PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
 {
-    int rc = VERR_NOT_IMPLEMENTED;
-    LogFlowFunc(("returns %Rrc\n", rc));
-    return rc;
-}
-
-static int vhdAsyncWrite(void *pvBackendData, uint64_t uOffset, size_t cbWrite,
+    PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+    int rc = VINF_SUCCESS;
+
+    LogFlowFunc(("pBackendData=%p uOffset=%#llx pIoCtx=%#p cbRead=%u pcbActuallyRead=%p\n", pBackendData, uOffset, pIoCtx, cbRead, pcbActuallyRead));
+
+    if (uOffset + cbRead > pImage->cbSize)
+        return VERR_INVALID_PARAMETER;
+
+    /*
+     * If we have a dynamic disk image, we need to find the data block and sector to read.
+     */
+    if (pImage->pBlockAllocationTable)
+    {
+        /*
+         * Get the data block first.
+         */
+        uint32_t cBlockAllocationTableEntry = (uOffset / VHD_SECTOR_SIZE) / pImage->cSectorsPerDataBlock;
+        uint32_t cBATEntryIndex = (uOffset / VHD_SECTOR_SIZE) % pImage->cSectorsPerDataBlock;
+        uint64_t uVhdOffset;
+
+        LogFlowFunc(("cBlockAllocationTableEntry=%u cBatEntryIndex=%u\n", cBlockAllocationTableEntry, cBATEntryIndex));
+        LogFlowFunc(("BlockAllocationEntry=%u\n", pImage->pBlockAllocationTable[cBlockAllocationTableEntry]));
+
+        /*
+         * If the block is not allocated the content of the entry is ~0
+         */
+        if (pImage->pBlockAllocationTable[cBlockAllocationTableEntry] == ~0U)
+        {
+            /* Return block size as read. */
+            *pcbActuallyRead = RT_MIN(cbRead, pImage->cSectorsPerDataBlock * VHD_SECTOR_SIZE);
+            return VERR_VD_BLOCK_FREE;
+        }
+
+        uVhdOffset = ((uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry] + pImage->cDataBlockBitmapSectors + cBATEntryIndex) * VHD_SECTOR_SIZE;
+        LogFlowFunc(("uVhdOffset=%llu cbRead=%u\n", uVhdOffset, cbRead));
+
+        /*
+         * Clip read range to remain in this data block.
+         */
+        cbRead = RT_MIN(cbRead, (pImage->cbDataBlock - (cBATEntryIndex * VHD_SECTOR_SIZE)));
+
+        /* Read in the block's bitmap. */
+        PVDMETAXFER pMetaXfer;
+        rc = pImage->pInterfaceIOCallbacks->pfnReadMetaAsync(pImage->pInterfaceIO->pvUser,
+                                                             pImage->pStorage,
+                                                             ((uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry]) * VHD_SECTOR_SIZE,
+                                                             pImage->pu8Bitmap, pImage->cbDataBlockBitmap, pIoCtx, &pMetaXfer);
+
+        if (RT_SUCCESS(rc))
+        {
+            uint32_t cSectors = 0;
+
+            pImage->pInterfaceIOCallbacks->pfnMetaXferRelease(pImage->pInterfaceIO->pvUser, pMetaXfer);
+            if (vhdBlockBitmapSectorContainsData(pImage, cBATEntryIndex))
+            {
+                cBATEntryIndex++;
+                cSectors = 1;
+
+                /*
+                 * The first sector being read is marked dirty, read as much as we
+                 * can from child. Note that only sectors that are marked dirty
+                 * must be read from child.
+                 */
+                while (   (cSectors < (cbRead / VHD_SECTOR_SIZE))
+                       && vhdBlockBitmapSectorContainsData(pImage, cBATEntryIndex))
+                {
+                    cBATEntryIndex++;
+                    cSectors++;
+                }
+
+                cbRead = cSectors * VHD_SECTOR_SIZE;
+
+                LogFlowFunc(("uVhdOffset=%llu cbRead=%u\n", uVhdOffset, cbRead));
+                rc = pImage->pInterfaceIOCallbacks->pfnReadUserAsync(pImage->pInterfaceIO->pvUser,
+                                                                     pImage->pStorage,
+                                                                     uVhdOffset, pIoCtx, cbRead);
+            }
+            else
+            {
+                /*
+                 * The first sector being read is marked clean, so we should read from
+                 * our parent instead, but only as much as there are the following
+                 * clean sectors, because the block may still contain dirty sectors
+                 * further on. We just need to compute the number of clean sectors
+                 * and pass it to our caller along with the notification that they
+                 * should be read from the parent.
+                 */
+                cBATEntryIndex++;
+                cSectors = 1;
+
+                while (   (cSectors < (cbRead / VHD_SECTOR_SIZE))
+                       && !vhdBlockBitmapSectorContainsData(pImage, cBATEntryIndex))
+                {
+                    cBATEntryIndex++;
+                    cSectors++;
+                }
+
+                cbRead = cSectors * VHD_SECTOR_SIZE;
+                Log(("%s: Sectors free: uVhdOffset=%llu cbRead=%u\n", __FUNCTION__, uVhdOffset, cbRead));
+                rc = VERR_VD_BLOCK_FREE;
+            }
+        }
+        else
+            AssertMsg(rc == VERR_VD_NOT_ENOUGH_METADATA, ("Reading block bitmap failed rc=%Rrc\n", rc));
+    }
+    else
+    {
+        rc = pImage->pInterfaceIOCallbacks->pfnReadUserAsync(pImage->pInterfaceIO->pvUser,
+                                                             pImage->pStorage,
+                                                             uOffset, pIoCtx, cbRead);
+    }
+
+    if (pcbActuallyRead)
+        *pcbActuallyRead = cbRead;
+
+    LogFlowFunc(("returns rc=%Rrc\n", rc));
+    return rc;
+}
+
+static int vhdAsyncWrite(void *pBackendData, uint64_t uOffset, size_t cbWrite,
                          PVDIOCTX pIoCtx,
                          size_t *pcbWriteProcess, size_t *pcbPreRead,
                          size_t *pcbPostRead, unsigned fWrite)
 {
-    int rc = VERR_NOT_IMPLEMENTED;
-    LogFlowFunc(("returns %Rrc\n", rc));
+    PVHDIMAGE pImage = (PVHDIMAGE)pBackendData;
+    int rc = VINF_SUCCESS;
+
+    LogFlowFunc(("pBackendData=%p uOffset=%llu pIoCtx=%#p cbWrite=%u pcbWriteProcess=%p pcbPreRead=%p pcbPostRead=%p fWrite=%u\n",
+             pBackendData, uOffset, pIoCtx, cbWrite, pcbWriteProcess, pcbPreRead, pcbPostRead, fWrite));
+
+    AssertPtr(pImage);
+    Assert(uOffset % VHD_SECTOR_SIZE == 0);
+    Assert(cbWrite % VHD_SECTOR_SIZE == 0);
+
+    if (pImage->pBlockAllocationTable)
+    {
+        /*
+         * Get the data block first.
+         */
+        uint32_t cSector = uOffset / VHD_SECTOR_SIZE;
+        uint32_t cBlockAllocationTableEntry = cSector / pImage->cSectorsPerDataBlock;
+        uint32_t cBATEntryIndex = cSector % pImage->cSectorsPerDataBlock;
+        uint64_t uVhdOffset;
+
+        /*
+         * Clip write range.
+         */
+        cbWrite = RT_MIN(cbWrite, (pImage->cbDataBlock - (cBATEntryIndex * VHD_SECTOR_SIZE)));
+
+        /*
+         * If the block is not allocated the content of the entry is ~0
+         * and we need to allocate a new block. Note that while blocks are
+         * allocated with a relatively big granularity, each sector has its
+         * own bitmap entry, indicating whether it has been written or not.
+         * So that means for the purposes of the higher level that the
+         * granularity is invisible. This means there's no need to return
+         * VERR_VD_BLOCK_FREE unless the block hasn't been allocated yet.
+         */
+        if (pImage->pBlockAllocationTable[cBlockAllocationTableEntry] == ~0U)
+        {
+            /* Check if the block allocation should be suppressed. */
+            if (fWrite & VD_WRITE_NO_ALLOC)
+            {
+                *pcbPreRead = cBATEntryIndex * VHD_SECTOR_SIZE;
+                *pcbPostRead = pImage->cSectorsPerDataBlock * VHD_SECTOR_SIZE - cbWrite - *pcbPreRead;
+
+                if (pcbWriteProcess)
+                    *pcbWriteProcess = cbWrite;
+                return VERR_VD_BLOCK_FREE;
+            }
+
+            PVHDIMAGEEXPAND pExpand = (PVHDIMAGEEXPAND)RTMemAllocZ(RT_OFFSETOF(VHDIMAGEEXPAND, au8Bitmap[pImage->cDataBlockBitmapSectors * VHD_SECTOR_SIZE]));
+            bool fIoInProgress = false;
+
+            if (!pExpand)
+                return VERR_NO_MEMORY;
+
+            pExpand->cbEofOld = pImage->uCurrentEndOfFile;
+            pExpand->idxBatAllocated = cBlockAllocationTableEntry;
+            pExpand->idxBlockBe = RT_H2BE_U32(pImage->uCurrentEndOfFile / VHD_SECTOR_SIZE);
+
+            /* Set the bits for all sectors having been written. */
+            for (uint32_t iSector = 0; iSector < (cbWrite / VHD_SECTOR_SIZE); iSector++)
+            {
+                /* No need to check for a changed value because this is an initial write. */
+                vhdBlockBitmapSectorSet(pImage, pExpand->au8Bitmap, cBATEntryIndex);
+                cBATEntryIndex++;
+            }
+
+            do
+            {
+                /*
+                 * Start with the sector bitmap.
+                 */
+                rc = pImage->pInterfaceIOCallbacks->pfnWriteMetaAsync(pImage->pInterfaceIO->pvUser,
+                                                                      pImage->pStorage,
+                                                                      pImage->uCurrentEndOfFile,
+                                                                      pExpand->au8Bitmap, pImage->cbDataBlockBitmap, pIoCtx,
+                                                                      vhdAsyncExpansionDataBlockBitmapComplete, pExpand);
+                if (RT_SUCCESS(rc))
+                    VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_BLOCKBITMAP_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_SUCCESS);
+                else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+                    fIoInProgress = true;
+                else
+                {
+                    VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_BLOCKBITMAP_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+                    VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_USERBLOCK_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+                    VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_BAT_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+                    VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_FOOTER_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+                    break;
+                }
+
+
+                /*
+                 * Write the new block at the current end of the file.
+                 */
+                rc = pImage->pInterfaceIOCallbacks->pfnWriteUserAsync(pImage->pInterfaceIO->pvUser,
+                                                                      pImage->pStorage,
+                                                                      pImage->uCurrentEndOfFile + pImage->cbDataBlockBitmap,
+                                                                      pIoCtx, cbWrite,
+                                                                      vhdAsyncExpansionDataComplete, pExpand);
+                if (RT_SUCCESS(rc))
+                    VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_USERBLOCK_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_SUCCESS);
+                else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+                    fIoInProgress = true;
+                else
+                {
+                    VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_USERBLOCK_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+                    VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_BAT_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+                    VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_FOOTER_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+                    break;
+                }
+
+                /*
+                 * Write entry in the BAT.
+                 */
+                rc = pImage->pInterfaceIOCallbacks->pfnWriteMetaAsync(pImage->pInterfaceIO->pvUser,
+                                                                      pImage->pStorage,
+                                                                      pImage->uBlockAllocationTableOffset + cBlockAllocationTableEntry * sizeof(uint32_t),
+                                                                      &pExpand->idxBlockBe, sizeof(uint32_t), pIoCtx,
+                                                                      vhdAsyncExpansionBatUpdateComplete, pExpand);
+                if (RT_SUCCESS(rc))
+                    VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_BAT_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_SUCCESS);
+                else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+                    fIoInProgress = true;
+                else
+                {
+                    VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_BAT_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+                    VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_FOOTER_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+                    break;
+                }
+
+                /*
+                 * Set the new end of the file and link the new block into the BAT.
+                 */
+                pImage->pBlockAllocationTable[cBlockAllocationTableEntry] = pImage->uCurrentEndOfFile / VHD_SECTOR_SIZE;
+                pImage->uCurrentEndOfFile += pImage->cbDataBlockBitmap + pImage->cbDataBlock;
+
+                /* Update the footer. */
+                rc = pImage->pInterfaceIOCallbacks->pfnWriteMetaAsync(pImage->pInterfaceIO->pvUser,
+                                                                      pImage->pStorage,
+                                                                      pImage->uCurrentEndOfFile,
+                                                                      &pImage->vhdFooterCopy, sizeof(VHDFooter), pIoCtx,
+                                                                      vhdAsyncExpansionFooterUpdateComplete, pExpand);
+                if (RT_SUCCESS(rc))
+                    VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_FOOTER_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_SUCCESS);
+                else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+                    fIoInProgress = true;
+                else
+                {
+                    VHDIMAGEEXPAND_STATUS_SET(pExpand->fFlags, VHDIMAGEEXPAND_FOOTER_STATUS_SHIFT, VHDIMAGEEXPAND_STEP_FAILED);
+                    break;
+                }
+
+            } while (0);
+
+            if (!fIoInProgress)
+                vhdAsyncExpansionComplete(pImage, pIoCtx, pExpand);
+            else
+                rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+        }
+        else
+        {
+            /*
+             * Calculate the real offset in the file.
+             */
+            uVhdOffset = ((uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry] + pImage->cDataBlockBitmapSectors + cBATEntryIndex) * VHD_SECTOR_SIZE;
+
+            /* Read in the block's bitmap. */
+            PVDMETAXFER pMetaXfer;
+            rc = pImage->pInterfaceIOCallbacks->pfnReadMetaAsync(pImage->pInterfaceIO->pvUser,
+                                                                 pImage->pStorage,
+                                                                 ((uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry]) * VHD_SECTOR_SIZE,
+                                                                 pImage->pu8Bitmap, pImage->cbDataBlockBitmap, pIoCtx, &pMetaXfer);
+            if (RT_SUCCESS(rc))
+            {
+                pImage->pInterfaceIOCallbacks->pfnMetaXferRelease(pImage->pInterfaceIO->pvUser, pMetaXfer);
+
+                /* Write data. */
+                rc = pImage->pInterfaceIOCallbacks->pfnWriteUserAsync(pImage->pInterfaceIO->pvUser,
+                                                                      pImage->pStorage,
+                                                                      uVhdOffset, pIoCtx, cbWrite,
+                                                                      NULL, NULL);
+                if (RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+                {
+                    bool fChanged = false;
+
+                    /* Set the bits for all sectors having been written. */
+                    for (uint32_t iSector = 0; iSector < (cbWrite / VHD_SECTOR_SIZE); iSector++)
+                    {
+                        fChanged |= vhdBlockBitmapSectorSet(pImage, pImage->pu8Bitmap, cBATEntryIndex);
+                        cBATEntryIndex++;
+                    }
+
+                    /* Only write the bitmap if it was changed. */
+                    if (fChanged)
+                    {
+                        /*
+                         * Write the bitmap back.
+                         *
+                         * @note We don't have a completion callback here because we
+                         * can't do anything if the write fails for some reason.
+                         * The error will propagated to the device/guest
+                         * by the generic VD layer already and we don't need
+                         * to rollback anything here.
+                         */
+                        rc = pImage->pInterfaceIOCallbacks->pfnWriteMetaAsync(pImage->pInterfaceIO->pvUser,
+                                                                              pImage->pStorage,
+                                                                              ((uint64_t)pImage->pBlockAllocationTable[cBlockAllocationTableEntry]) * VHD_SECTOR_SIZE,
+                                                                              pImage->pu8Bitmap, pImage->cbDataBlockBitmap, pIoCtx,
+                                                                              NULL, NULL);
+                    }
+                }
+            }
+        }
+    }
+    else
+    {
+        rc = pImage->pInterfaceIOCallbacks->pfnWriteUserAsync(pImage->pInterfaceIO->pvUser,
+                                                              pImage->pStorage,
+                                                              uOffset, pIoCtx, cbWrite,
+                                                              NULL, NULL);
+    }
+
+    if (pcbWriteProcess)
+        *pcbWriteProcess = cbWrite;
+
+    /* Stay on the safe side. Do not run the risk of confusing the higher
+     * level, as that can be pretty lethal to image consistency. */
+    *pcbPreRead = 0;
+    *pcbPostRead = 0;
+
     return rc;
 }
@@ -2154,7 +2639,10 @@
 static int vhdAsyncFlush(void *pvBackendData, PVDIOCTX pIoCtx)
 {
-    int rc = VERR_NOT_IMPLEMENTED;
-    LogFlowFunc(("returns %Rrc\n", rc));
-    return rc;
+    PVHDIMAGE pImage = (PVHDIMAGE)pvBackendData;
+
+    /* No need to write anything here. Data is always updated on a write. */
+    return pImage->pInterfaceIOCallbacks->pfnFlushAsync(pImage->pInterfaceIO->pvUser,
+                                                        pImage->pStorage, pIoCtx,
+                                                        NULL, NULL);
 }
 
@@ -2167,5 +2655,6 @@
     /* uBackendCaps */
     VD_CAP_UUID | VD_CAP_DIFF | VD_CAP_FILE |
-    VD_CAP_CREATE_FIXED | VD_CAP_CREATE_DYNAMIC,
+    VD_CAP_CREATE_FIXED | VD_CAP_CREATE_DYNAMIC |
+    VD_CAP_ASYNC,
     /* papszFileExtensions */
     s_apszVhdFileExtensions,
Index: trunk/src/VBox/Devices/Storage/VmdkHDDCore.cpp
===================================================================
--- trunk/src/VBox/Devices/Storage/VmdkHDDCore.cpp	(revision 30554)
+++ trunk/src/VBox/Devices/Storage/VmdkHDDCore.cpp	(revision 30555)
@@ -799,5 +799,6 @@
 
     return pImage->pInterfaceIOCallbacks->pfnFlushAsync(pImage->pInterfaceIO->pvUser,
-                                                        pVmdkFile->pStorage, pIoCtx);
+                                                        pVmdkFile->pStorage, pIoCtx,
+                                                        NULL, NULL);
 }
 
@@ -3117,11 +3118,4 @@
     if (RT_LE2H_U32(u32Magic) == VMDK_SPARSE_MAGICNUMBER)
     {
-        /* Async I/IO is not supported with these files yet. So fail if opened in async I/O mode. */
-        if (uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO)
-        {
-            rc = VERR_NOT_SUPPORTED;
-            goto out;
-        }
-
         /* It's a hosted single-extent image. */
         rc = vmdkCreateExtents(pImage, 1);
@@ -4363,4 +4357,60 @@
         if (RT_FAILURE(rc))
             return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot read grain table entry in '%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;
+    uint32_t uGrainSector = pGTCacheEntry->aGTData[uGTBlockIndex];
+    if (uGrainSector)
+        *puExtentSector = uGrainSector + uSector % pExtent->cSectorsPerGrain;
+    else
+        *puExtentSector = 0;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Internal. Get sector number in the extent file from the relative sector
+ * number in the extent - version for async access.
+ */
+static int vmdkGetSectorAsync(PVMDKIMAGE pImage, PVDIOCTX pIoCtx,
+                              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. */
+        PVDMETAXFER pMetaXfer;
+        rc = pImage->pInterfaceIOCallbacks->pfnReadMetaAsync(pImage->pInterfaceIO->pvUser,
+                                                             pExtent->pFile->pStorage,
+                                                             VMDK_SECTOR2BYTE(uGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
+                                                             aGTDataTmp, sizeof(aGTDataTmp), pIoCtx, &pMetaXfer);
+        if (RT_FAILURE(rc))
+            return rc;
+        /* We can release the metadata transfer immediately. */
+        pImage->pInterfaceIOCallbacks->pfnMetaXferRelease(pImage->pInterfaceIO->pvUser, pMetaXfer);
         pGTCacheEntry->uExtent = pExtent->uExtent;
         pGTCacheEntry->uGTBlock = uGTBlock;
@@ -6016,4 +6066,6 @@
 {
     PVMDKIMAGE pImage = (PVMDKIMAGE)pvBackendData;
+
+#if 1
     bool fAsyncIOSupported = false;
 
@@ -6033,4 +6085,8 @@
 
     return fAsyncIOSupported;
+#else
+    /* We do not support async I/O for stream optimized VMDK images. */
+    return (pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED) == 0;
+#endif
 }
 
@@ -6079,5 +6135,21 @@
         case VMDKETYPE_ESX_SPARSE:
 #endif /* VBOX_WITH_VMDK_ESX */
-            AssertMsgFailed(("Not supported\n"));
+            rc = vmdkGetSectorAsync(pImage, pIoCtx, pImage->pGTCache, pExtent,
+                                    uSectorExtentRel, &uSectorExtentAbs);
+            if (RT_FAILURE(rc))
+                goto out;
+            /* Clip read range to at most the rest of the grain. */
+            cbRead = RT_MIN(cbRead, VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain - uSectorExtentRel % pExtent->cSectorsPerGrain));
+            Assert(!(cbRead % 512));
+            if (uSectorExtentAbs == 0)
+                rc = VERR_VD_BLOCK_FREE;
+            else
+            {
+                AssertMsg(!(pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED), ("Async I/O is not supported for stream optimized VMDK's\n"));
+                rc = pImage->pInterfaceIOCallbacks->pfnReadUserAsync(pImage->pInterfaceIO->pvUser,
+                                                                     pExtent->pFile->pStorage,
+                                                                     VMDK_SECTOR2BYTE(uSectorExtentAbs),
+                                                                     pIoCtx, cbRead);
+            }
             break;
         case VMDKETYPE_VMFS:
@@ -6161,5 +6233,54 @@
         case VMDKETYPE_ESX_SPARSE:
 #endif /* VBOX_WITH_VMDK_ESX */
-            AssertMsgFailed(("Not supported\n"));
+            rc = vmdkGetSectorAsync(pImage, pIoCtx, pImage->pGTCache, pExtent, uSectorExtentRel,
+                                    &uSectorExtentAbs);
+            if (RT_FAILURE(rc))
+                goto out;
+            /* Clip write range to at most the rest of the grain. */
+            cbWrite = RT_MIN(cbWrite, VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain - uSectorExtentRel % pExtent->cSectorsPerGrain));
+            if (    pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED
+                &&  uSectorExtentRel < (uint64_t)pExtent->uLastGrainWritten * pExtent->cSectorsPerGrain)
+            {
+                rc = VERR_VD_VMDK_INVALID_WRITE;
+                goto out;
+            }
+            if (uSectorExtentAbs == 0)
+            {
+                if (cbWrite == VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain))
+                {
+                    /* Full block write to a previously unallocated block.
+                     * Check if the caller wants to avoid the automatic alloc. */
+                    if (!(fWrite & VD_WRITE_NO_ALLOC))
+                    {
+                        AssertMsgFailed(("Implement\n"));
+#if 0
+                        /* Allocate GT and find out where to store the grain. */
+                        rc = vmdkAllocGrain(pImage->pGTCache, pExtent,
+                                            uSectorExtentRel, pvBuf, cbWrite);
+#endif
+                    }
+                    else
+                        rc = VERR_VD_BLOCK_FREE;
+                    *pcbPreRead = 0;
+                    *pcbPostRead = 0;
+                }
+                else
+                {
+                    /* Clip write range to remain in this extent. */
+                    cbWrite = RT_MIN(cbWrite, VMDK_SECTOR2BYTE(pExtent->uSectorOffset + pExtent->cNominalSectors - uSectorExtentRel));
+                    *pcbPreRead = VMDK_SECTOR2BYTE(uSectorExtentRel % pExtent->cSectorsPerGrain);
+                    *pcbPostRead = VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain) - cbWrite - *pcbPreRead;
+                    rc = VERR_VD_BLOCK_FREE;
+                }
+            }
+            else
+            {
+                Assert(!(pImage->uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED));
+                rc = pImage->pInterfaceIOCallbacks->pfnWriteUserAsync(pImage->pInterfaceIO->pvUser,
+                                                                      pExtent->pFile->pStorage,
+                                                                      VMDK_SECTOR2BYTE(uSectorExtentAbs),
+                                                                      pIoCtx, cbWrite,
+                                                                      NULL, NULL);
+            }
             break;
         case VMDKETYPE_VMFS:
@@ -6170,5 +6291,5 @@
                                                                   pExtent->pFile->pStorage,
                                                                   VMDK_SECTOR2BYTE(uSectorExtentRel),
-                                                                  pIoCtx, cbWrite);
+                                                                  pIoCtx, cbWrite, NULL, NULL);
             break;
         case VMDKETYPE_ZERO:
@@ -6202,6 +6323,6 @@
                 case VMDKETYPE_ESX_SPARSE:
 #endif /* VBOX_WITH_VMDK_ESX */
-                    /** Not supported atm. */
-                    AssertMsgFailed(("Async I/O not supported for sparse images\n"));
+                    /** @todo: Fake success for now */
+                    rc = VINF_SUCCESS;
                     break;
                 case VMDKETYPE_VMFS:
