Index: trunk/src/VBox/Storage/VD.cpp
===================================================================
--- trunk/src/VBox/Storage/VD.cpp	(revision 44615)
+++ trunk/src/VBox/Storage/VD.cpp	(revision 44616)
@@ -355,4 +355,13 @@
             /** S/G buffer */
             RTSGBUF              SgBuf;
+            /** Flag whether all reads from the image chain returned VERR_VD_BLOCK_FREE
+             * so far. */
+            bool                 fAllFree;
+            /** Number of bytes to clear in the buffer before the current read. */
+            size_t               cbBufClear;
+            /** Number of images to read. */
+            unsigned             cImagesRead;
+            /** Override for the parent image to start reading from. */
+            PVDIMAGE             pImageParentOverride;
         } Io;
         /** Discard requests. */
@@ -424,9 +433,22 @@
 
 /** Default flags for an I/O context, i.e. unblocked and async. */
-#define VDIOCTX_FLAGS_DEFAULT (0)
+#define VDIOCTX_FLAGS_DEFAULT                   (0)
 /** Flag whether the context is blocked. */
-#define VDIOCTX_FLAGS_BLOCKED RT_BIT_32(0)
+#define VDIOCTX_FLAGS_BLOCKED          RT_BIT_32(0)
 /** Flag whether the I/O context is using synchronous I/O. */
-#define VDIOCTX_FLAGS_SYNC    RT_BIT_32(1)
+#define VDIOCTX_FLAGS_SYNC             RT_BIT_32(1)
+/** Flag whether the read should update the cache. */
+#define VDIOCTX_FLAGS_READ_UDATE_CACHE RT_BIT_32(2)
+/** Flag whether free blocks should be zeroed.
+ * If false and no image has data for sepcified
+ * range VERR_VD_BLOCK_FREE is returned for the I/O context.
+ * Note that unallocated blocks are still zeroed
+ * if at least one image has valid data for a part
+ * of the range.
+ */
+#define VDIOCTX_FLAGS_ZERO_FREE_BLOCKS RT_BIT_32(3)
+/** Don't free the I/O context when complete because
+ * it was alloacted elsewhere (stack, ...). */
+#define VDIOCTX_FLAGS_DONT_FREE        RT_BIT_32(4)
 
 /** NIL I/O context pointer value. */
@@ -577,6 +599,8 @@
 /** Forward declaration of the async discard helper. */
 static int vdDiscardHelperAsync(PVDIOCTX pIoCtx);
+static int vdWriteHelperAsync(PVDIOCTX pIoCtx);
 static void vdDiskProcessBlockedIoCtx(PVBOXHDD pDisk);
 static int vdDiskUnlock(PVBOXHDD pDisk, PVDIOCTX pIoCtxRc);
+static DECLCALLBACK(void) vdIoCtxSyncComplete(void *pvUser1, void *pvUser2, int rcReq);
 
 /**
@@ -809,4 +833,7 @@
     pIoCtx->Req.Io.pImageStart    = pImageStart;
     pIoCtx->Req.Io.pImageCur      = pImageStart;
+    pIoCtx->Req.Io.fAllFree       = true;
+    pIoCtx->Req.Io.cbBufClear     = 0;
+    pIoCtx->Req.Io.pImageParentOverride = NULL;
     pIoCtx->cDataTransfersPending = 0;
     pIoCtx->cMetaTransfersPending = 0;
@@ -817,4 +844,5 @@
     pIoCtx->pfnIoCtxTransferNext  = NULL;
     pIoCtx->rcReq                 = VINF_SUCCESS;
+    pIoCtx->pIoCtxParent          = NULL;
 
     /* There is no S/G list for a flush request. */
@@ -845,5 +873,5 @@
  */
 static int vdCacheReadHelper(PVDCACHE pCache, uint64_t uOffset,
-                             PVDIOCTX pIoCtx, size_t cbRead, size_t *pcbRead)
+                             size_t cbRead, PVDIOCTX pIoCtx, size_t *pcbRead)
 {
     int rc = VINF_SUCCESS;
@@ -908,24 +936,774 @@
 
 /**
+ * Creates a new empty discard state.
+ *
+ * @returns Pointer to the new discard state or NULL if out of memory.
+ */
+static PVDDISCARDSTATE vdDiscardStateCreate(void)
+{
+    PVDDISCARDSTATE pDiscard = (PVDDISCARDSTATE)RTMemAllocZ(sizeof(VDDISCARDSTATE));
+
+    if (pDiscard)
+    {
+        RTListInit(&pDiscard->ListLru);
+        pDiscard->pTreeBlocks = (PAVLRU64TREE)RTMemAllocZ(sizeof(AVLRU64TREE));
+        if (!pDiscard->pTreeBlocks)
+        {
+            RTMemFree(pDiscard);
+            pDiscard = NULL;
+        }
+    }
+
+    return pDiscard;
+}
+
+/**
+ * Removes the least recently used blocks from the waiting list until
+ * the new value is reached.
+ *
+ * @returns VBox status code.
+ * @param   pDisk              VD disk container.
+ * @param   pDiscard           The discard state.
+ * @param   cbDiscardingNew    How many bytes should be waiting on success.
+ *                             The number of bytes waiting can be less.
+ */
+static int vdDiscardRemoveBlocks(PVBOXHDD pDisk, PVDDISCARDSTATE pDiscard, size_t cbDiscardingNew)
+{
+    int rc = VINF_SUCCESS;
+
+    LogFlowFunc(("pDisk=%#p pDiscard=%#p cbDiscardingNew=%zu\n",
+                 pDisk, pDiscard, cbDiscardingNew));
+
+    while (pDiscard->cbDiscarding > cbDiscardingNew)
+    {
+        PVDDISCARDBLOCK pBlock = RTListGetLast(&pDiscard->ListLru, VDDISCARDBLOCK, NodeLru);
+
+        Assert(!RTListIsEmpty(&pDiscard->ListLru));
+
+        /* Go over the allocation bitmap and mark all discarded sectors as unused. */
+        uint64_t offStart = pBlock->Core.Key;
+        uint32_t idxStart = 0;
+        size_t cbLeft = pBlock->cbDiscard;
+        bool fAllocated = ASMBitTest(pBlock->pbmAllocated, idxStart);
+        uint32_t cSectors = pBlock->cbDiscard / 512;
+
+        while (cbLeft > 0)
+        {
+            int32_t idxEnd;
+            size_t cbThis = cbLeft;
+
+            if (fAllocated)
+            {
+                /* Check for the first unallocated bit. */
+                idxEnd = ASMBitNextClear(pBlock->pbmAllocated, cSectors, idxStart);
+                if (idxEnd != -1)
+                {
+                    cbThis = (idxEnd - idxStart) * 512;
+                    fAllocated = false;
+                }
+            }
+            else
+            {
+                /* Mark as unused and check for the first set bit. */
+                idxEnd = ASMBitNextSet(pBlock->pbmAllocated, cSectors, idxStart);
+                if (idxEnd != -1)
+                    cbThis = (idxEnd - idxStart) * 512;
+
+
+                VDIOCTX IoCtx;
+                vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_DISCARD, 0, 0, NULL,
+                            NULL, NULL, NULL, VDIOCTX_FLAGS_SYNC);
+                rc = pDisk->pLast->Backend->pfnDiscard(pDisk->pLast->pBackendData,
+                                                            &IoCtx, offStart, cbThis, NULL,
+                                                            NULL, &cbThis, NULL,
+                                                            VD_DISCARD_MARK_UNUSED);
+                if (RT_FAILURE(rc))
+                    break;
+
+                fAllocated = true;
+            }
+
+            idxStart  = idxEnd;
+            offStart += cbThis;
+            cbLeft   -= cbThis;
+        }
+
+        if (RT_FAILURE(rc))
+            break;
+
+        PVDDISCARDBLOCK pBlockRemove = (PVDDISCARDBLOCK)RTAvlrU64RangeRemove(pDiscard->pTreeBlocks, pBlock->Core.Key);
+        Assert(pBlockRemove == pBlock);
+        RTListNodeRemove(&pBlock->NodeLru);
+
+        pDiscard->cbDiscarding -= pBlock->cbDiscard;
+        RTMemFree(pBlock->pbmAllocated);
+        RTMemFree(pBlock);
+    }
+
+    Assert(RT_FAILURE(rc) || pDiscard->cbDiscarding <= cbDiscardingNew);
+
+    LogFlowFunc(("returns rc=%Rrc\n", rc));
+    return rc;
+}
+
+/**
+ * Destroys the current discard state, writing any waiting blocks to the image.
+ *
+ * @returns VBox status code.
+ * @param   pDisk    VD disk container.
+ */
+static int vdDiscardStateDestroy(PVBOXHDD pDisk)
+{
+    int rc = VINF_SUCCESS;
+
+    if (pDisk->pDiscard)
+    {
+        rc = vdDiscardRemoveBlocks(pDisk, pDisk->pDiscard, 0 /* Remove all blocks. */);
+        AssertRC(rc);
+        RTMemFree(pDisk->pDiscard->pTreeBlocks);
+        RTMemFree(pDisk->pDiscard);
+        pDisk->pDiscard = NULL;
+    }
+
+    return rc;
+}
+
+/**
+ * Marks the given range as allocated in the image.
+ * Required if there are discards in progress and a write to a block which can get discarded
+ * is written to.
+ *
+ * @returns VBox status code.
+ * @param   pDisk    VD container data.
+ * @param   uOffset  First byte to mark as allocated.
+ * @param   cbRange  Number of bytes to mark as allocated.
+ */
+static int vdDiscardSetRangeAllocated(PVBOXHDD pDisk, uint64_t uOffset, size_t cbRange)
+{
+    PVDDISCARDSTATE pDiscard = pDisk->pDiscard;
+    int rc = VINF_SUCCESS;
+
+    if (pDiscard)
+    {
+        do
+        {
+            size_t cbThisRange = cbRange;
+            PVDDISCARDBLOCK pBlock = (PVDDISCARDBLOCK)RTAvlrU64RangeGet(pDiscard->pTreeBlocks, uOffset);
+
+            if (pBlock)
+            {
+                int32_t idxStart, idxEnd;
+
+                Assert(!(cbThisRange % 512));
+                Assert(!((uOffset - pBlock->Core.Key) % 512));
+
+                cbThisRange = RT_MIN(cbThisRange, pBlock->Core.KeyLast - uOffset + 1);
+
+                idxStart = (uOffset - pBlock->Core.Key) / 512;
+                idxEnd = idxStart + (cbThisRange / 512);
+                ASMBitSetRange(pBlock->pbmAllocated, idxStart, idxEnd);
+            }
+            else
+            {
+                pBlock = (PVDDISCARDBLOCK)RTAvlrU64GetBestFit(pDiscard->pTreeBlocks, uOffset, true);
+                if (pBlock)
+                    cbThisRange = RT_MIN(cbThisRange, pBlock->Core.Key - uOffset);
+            }
+
+            Assert(cbRange >= cbThisRange);
+
+            uOffset += cbThisRange;
+            cbRange -= cbThisRange;
+        } while (cbRange != 0);
+    }
+
+    return rc;
+}
+
+DECLINLINE(PVDIOCTX) vdIoCtxAlloc(PVBOXHDD pDisk, VDIOCTXTXDIR enmTxDir,
+                                  uint64_t uOffset, size_t cbTransfer,
+                                  PVDIMAGE pImageStart,PCRTSGBUF pcSgBuf,
+                                  void *pvAllocation, PFNVDIOCTXTRANSFER pfnIoCtxTransfer,
+                                  uint32_t fFlags)
+{
+    PVDIOCTX pIoCtx = NULL;
+
+    pIoCtx = (PVDIOCTX)RTMemCacheAlloc(pDisk->hMemCacheIoCtx);
+    if (RT_LIKELY(pIoCtx))
+    {
+        vdIoCtxInit(pIoCtx, pDisk, enmTxDir, uOffset, cbTransfer, pImageStart,
+                    pcSgBuf, pvAllocation, pfnIoCtxTransfer, fFlags);
+    }
+
+    return pIoCtx;
+}
+
+DECLINLINE(PVDIOCTX) vdIoCtxRootAlloc(PVBOXHDD pDisk, VDIOCTXTXDIR enmTxDir,
+                                      uint64_t uOffset, size_t cbTransfer,
+                                      PVDIMAGE pImageStart, PCRTSGBUF pcSgBuf,
+                                      PFNVDASYNCTRANSFERCOMPLETE pfnComplete,
+                                      void *pvUser1, void *pvUser2,
+                                      void *pvAllocation,
+                                      PFNVDIOCTXTRANSFER pfnIoCtxTransfer,
+                                      uint32_t fFlags)
+{
+    PVDIOCTX pIoCtx = vdIoCtxAlloc(pDisk, enmTxDir, uOffset, cbTransfer, pImageStart,
+                                   pcSgBuf, pvAllocation, pfnIoCtxTransfer, fFlags);
+
+    if (RT_LIKELY(pIoCtx))
+    {
+        pIoCtx->pIoCtxParent          = NULL;
+        pIoCtx->Type.Root.pfnComplete = pfnComplete;
+        pIoCtx->Type.Root.pvUser1     = pvUser1;
+        pIoCtx->Type.Root.pvUser2     = pvUser2;
+    }
+
+    LogFlow(("Allocated root I/O context %#p\n", pIoCtx));
+    return pIoCtx;
+}
+
+DECLINLINE(PVDIOCTX) vdIoCtxDiscardAlloc(PVBOXHDD pDisk, PCRTRANGE paRanges,
+                                         unsigned cRanges,
+                                         PFNVDASYNCTRANSFERCOMPLETE pfnComplete,
+                                         void *pvUser1, void *pvUser2,
+                                         void *pvAllocation,
+                                         PFNVDIOCTXTRANSFER pfnIoCtxTransfer,
+                                         uint32_t fFlags)
+{
+    PVDIOCTX pIoCtx = NULL;
+
+    pIoCtx = (PVDIOCTX)RTMemCacheAlloc(pDisk->hMemCacheIoCtx);
+    if (RT_LIKELY(pIoCtx))
+    {
+        pIoCtx->pIoCtxNext                = NULL;
+        pIoCtx->pDisk                     = pDisk;
+        pIoCtx->enmTxDir                  = VDIOCTXTXDIR_DISCARD;
+        pIoCtx->cDataTransfersPending     = 0;
+        pIoCtx->cMetaTransfersPending     = 0;
+        pIoCtx->fComplete                 = false;
+        pIoCtx->fFlags                    = fFlags;
+        pIoCtx->pvAllocation              = pvAllocation;
+        pIoCtx->pfnIoCtxTransfer          = pfnIoCtxTransfer;
+        pIoCtx->pfnIoCtxTransferNext      = NULL;
+        pIoCtx->rcReq                     = VINF_SUCCESS;
+        pIoCtx->Req.Discard.paRanges      = paRanges;
+        pIoCtx->Req.Discard.cRanges       = cRanges;
+        pIoCtx->Req.Discard.idxRange      = 0;
+        pIoCtx->Req.Discard.cbDiscardLeft = 0;
+        pIoCtx->Req.Discard.offCur        = 0;
+        pIoCtx->Req.Discard.cbThisDiscard = 0;
+
+        pIoCtx->pIoCtxParent          = NULL;
+        pIoCtx->Type.Root.pfnComplete = pfnComplete;
+        pIoCtx->Type.Root.pvUser1     = pvUser1;
+        pIoCtx->Type.Root.pvUser2     = pvUser2;
+    }
+
+    LogFlow(("Allocated discard I/O context %#p\n", pIoCtx));
+    return pIoCtx;
+}
+
+DECLINLINE(PVDIOCTX) vdIoCtxChildAlloc(PVBOXHDD pDisk, VDIOCTXTXDIR enmTxDir,
+                                       uint64_t uOffset, size_t cbTransfer,
+                                       PVDIMAGE pImageStart, PCRTSGBUF pcSgBuf,
+                                       PVDIOCTX pIoCtxParent, size_t cbTransferParent,
+                                       size_t cbWriteParent, void *pvAllocation,
+                                       PFNVDIOCTXTRANSFER pfnIoCtxTransfer)
+{
+    PVDIOCTX pIoCtx = vdIoCtxAlloc(pDisk, enmTxDir, uOffset, cbTransfer, pImageStart,
+                                   pcSgBuf, pvAllocation, pfnIoCtxTransfer, 0);
+
+    AssertPtr(pIoCtxParent);
+    Assert(!pIoCtxParent->pIoCtxParent);
+
+    if (RT_LIKELY(pIoCtx))
+    {
+        pIoCtx->pIoCtxParent                   = pIoCtxParent;
+        pIoCtx->Type.Child.uOffsetSaved        = uOffset;
+        pIoCtx->Type.Child.cbTransferLeftSaved = cbTransfer;
+        pIoCtx->Type.Child.cbTransferParent    = cbTransferParent;
+        pIoCtx->Type.Child.cbWriteParent       = cbWriteParent;
+    }
+
+    LogFlow(("Allocated child I/O context %#p\n", pIoCtx));
+    return pIoCtx;
+}
+
+DECLINLINE(PVDIOTASK) vdIoTaskUserAlloc(PVDIOSTORAGE pIoStorage, PFNVDXFERCOMPLETED pfnComplete, void *pvUser, PVDIOCTX pIoCtx, uint32_t cbTransfer)
+{
+    PVDIOTASK pIoTask = NULL;
+
+    pIoTask = (PVDIOTASK)RTMemCacheAlloc(pIoStorage->pVDIo->pDisk->hMemCacheIoTask);
+    if (pIoTask)
+    {
+        pIoTask->pIoStorage           = pIoStorage;
+        pIoTask->pfnComplete          = pfnComplete;
+        pIoTask->pvUser               = pvUser;
+        pIoTask->fMeta                = false;
+        pIoTask->Type.User.cbTransfer = cbTransfer;
+        pIoTask->Type.User.pIoCtx     = pIoCtx;
+    }
+
+    return pIoTask;
+}
+
+DECLINLINE(PVDIOTASK) vdIoTaskMetaAlloc(PVDIOSTORAGE pIoStorage, PFNVDXFERCOMPLETED pfnComplete, void *pvUser, PVDMETAXFER pMetaXfer)
+{
+    PVDIOTASK pIoTask = NULL;
+
+    pIoTask = (PVDIOTASK)RTMemCacheAlloc(pIoStorage->pVDIo->pDisk->hMemCacheIoTask);
+    if (pIoTask)
+    {
+        pIoTask->pIoStorage          = pIoStorage;
+        pIoTask->pfnComplete         = pfnComplete;
+        pIoTask->pvUser              = pvUser;
+        pIoTask->fMeta               = true;
+        pIoTask->Type.Meta.pMetaXfer = pMetaXfer;
+    }
+
+    return pIoTask;
+}
+
+DECLINLINE(void) vdIoCtxFree(PVBOXHDD pDisk, PVDIOCTX pIoCtx)
+{
+    LogFlow(("Freeing I/O context %#p\n", pIoCtx));
+
+    if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_DONT_FREE))
+    {
+        if (pIoCtx->pvAllocation)
+            RTMemFree(pIoCtx->pvAllocation);
+#ifdef DEBUG
+        memset(pIoCtx, 0xff, sizeof(VDIOCTX));
+#endif
+        RTMemCacheFree(pDisk->hMemCacheIoCtx, pIoCtx);
+    }
+}
+
+DECLINLINE(void) vdIoTaskFree(PVBOXHDD pDisk, PVDIOTASK pIoTask)
+{
+    RTMemCacheFree(pDisk->hMemCacheIoTask, pIoTask);
+}
+
+DECLINLINE(void) vdIoCtxChildReset(PVDIOCTX pIoCtx)
+{
+    AssertPtr(pIoCtx->pIoCtxParent);
+
+    RTSgBufReset(&pIoCtx->Req.Io.SgBuf);
+    pIoCtx->Req.Io.uOffset        = pIoCtx->Type.Child.uOffsetSaved;
+    pIoCtx->Req.Io.cbTransferLeft = pIoCtx->Type.Child.cbTransferLeftSaved;
+}
+
+DECLINLINE(PVDMETAXFER) vdMetaXferAlloc(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;
+}
+
+DECLINLINE(void) vdIoCtxAddToWaitingList(volatile PVDIOCTX *ppList, PVDIOCTX pIoCtx)
+{
+    /* Put it on the waiting list. */
+    PVDIOCTX pNext = ASMAtomicUoReadPtrT(ppList, PVDIOCTX);
+    PVDIOCTX pHeadOld;
+    pIoCtx->pIoCtxNext = pNext;
+    while (!ASMAtomicCmpXchgExPtr(ppList, pIoCtx, pNext, &pHeadOld))
+    {
+        pNext = pHeadOld;
+        Assert(pNext != pIoCtx);
+        pIoCtx->pIoCtxNext = pNext;
+        ASMNopPause();
+    }
+}
+
+DECLINLINE(void) vdIoCtxDefer(PVBOXHDD pDisk, PVDIOCTX pIoCtx)
+{
+    LogFlowFunc(("Deferring write pIoCtx=%#p\n", pIoCtx));
+
+    Assert(!pIoCtx->pIoCtxParent && !(pIoCtx->fFlags & VDIOCTX_FLAGS_BLOCKED));
+    pIoCtx->fFlags |= VDIOCTX_FLAGS_BLOCKED;
+    vdIoCtxAddToWaitingList(&pDisk->pIoCtxBlockedHead, pIoCtx);
+}
+
+static size_t vdIoCtxCopy(PVDIOCTX pIoCtxDst, PVDIOCTX pIoCtxSrc, size_t cbData)
+{
+    return RTSgBufCopy(&pIoCtxDst->Req.Io.SgBuf, &pIoCtxSrc->Req.Io.SgBuf, cbData);
+}
+
+static int vdIoCtxCmp(PVDIOCTX pIoCtx1, PVDIOCTX pIoCtx2, size_t cbData)
+{
+    return RTSgBufCmp(&pIoCtx1->Req.Io.SgBuf, &pIoCtx2->Req.Io.SgBuf, cbData);
+}
+
+static size_t vdIoCtxCopyTo(PVDIOCTX pIoCtx, const uint8_t *pbData, size_t cbData)
+{
+    return RTSgBufCopyFromBuf(&pIoCtx->Req.Io.SgBuf, pbData, cbData);
+}
+
+static size_t vdIoCtxCopyFrom(PVDIOCTX pIoCtx, uint8_t *pbData, size_t cbData)
+{
+    return RTSgBufCopyToBuf(&pIoCtx->Req.Io.SgBuf, pbData, cbData);
+}
+
+static size_t vdIoCtxSet(PVDIOCTX pIoCtx, uint8_t ch, size_t cbData)
+{
+    return RTSgBufSet(&pIoCtx->Req.Io.SgBuf, ch, cbData);
+}
+
+/**
+ * Process the I/O context, core method which assumes that the I/O context
+ * acquired the lock.
+ *
+ * @returns VBox status code.
+ * @param   pIoCtx    I/O context to process.
+ */
+static int vdIoCtxProcessLocked(PVDIOCTX pIoCtx)
+{
+    int rc = VINF_SUCCESS;
+
+    VD_IS_LOCKED(pIoCtx->pDisk);
+
+    LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+    if (   !pIoCtx->cMetaTransfersPending
+        && !pIoCtx->cDataTransfersPending
+        && !pIoCtx->pfnIoCtxTransfer)
+    {
+        rc = VINF_VD_ASYNC_IO_FINISHED;
+        goto out;
+    }
+
+    /*
+     * We complete the I/O context in case of an error
+     * if there is no I/O task pending.
+     */
+    if (   RT_FAILURE(pIoCtx->rcReq)
+        && !pIoCtx->cMetaTransfersPending
+        && !pIoCtx->cDataTransfersPending)
+    {
+        rc = VINF_VD_ASYNC_IO_FINISHED;
+        goto out;
+    }
+
+    /* Don't change anything if there is a metadata transfer pending or we are blocked. */
+    if (   pIoCtx->cMetaTransfersPending
+        || (pIoCtx->fFlags & VDIOCTX_FLAGS_BLOCKED))
+    {
+        rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+        goto out;
+    }
+
+    if (pIoCtx->pfnIoCtxTransfer)
+    {
+        /* Call the transfer function advancing to the next while there is no error. */
+        while (   pIoCtx->pfnIoCtxTransfer
+               && !pIoCtx->cMetaTransfersPending
+               && RT_SUCCESS(rc))
+        {
+            LogFlowFunc(("calling transfer function %#p\n", pIoCtx->pfnIoCtxTransfer));
+            rc = pIoCtx->pfnIoCtxTransfer(pIoCtx);
+
+            /* Advance to the next part of the transfer if the current one succeeded. */
+            if (RT_SUCCESS(rc))
+            {
+                pIoCtx->pfnIoCtxTransfer = pIoCtx->pfnIoCtxTransferNext;
+                pIoCtx->pfnIoCtxTransferNext = NULL;
+            }
+        }
+    }
+
+    if (   RT_SUCCESS(rc)
+        && !pIoCtx->cMetaTransfersPending
+        && !pIoCtx->cDataTransfersPending)
+        rc = VINF_VD_ASYNC_IO_FINISHED;
+    else if (   RT_SUCCESS(rc)
+             || rc == VERR_VD_NOT_ENOUGH_METADATA
+             || rc == VERR_VD_IOCTX_HALT)
+        rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+    else if (RT_FAILURE(rc) && (rc != VERR_VD_ASYNC_IO_IN_PROGRESS))
+    {
+        ASMAtomicCmpXchgS32(&pIoCtx->rcReq, rc, VINF_SUCCESS);
+        /*
+         * The I/O context completed if we have an error and there is no data
+         * or meta data transfer pending.
+         */
+        if (   !pIoCtx->cMetaTransfersPending
+            && !pIoCtx->cDataTransfersPending)
+            rc = VINF_VD_ASYNC_IO_FINISHED;
+        else
+            rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+    }
+
+out:
+    LogFlowFunc(("pIoCtx=%#p rc=%Rrc cDataTransfersPending=%u cMetaTransfersPending=%u fComplete=%RTbool\n",
+                 pIoCtx, rc, pIoCtx->cDataTransfersPending, pIoCtx->cMetaTransfersPending,
+                 pIoCtx->fComplete));
+
+    return rc;
+}
+
+/**
+ * Processes the list of waiting I/O contexts.
+ *
+ * @returns VBox status code.
+ * @param   pDisk    The disk structure.
+ * @param   pIoCtxRc An I/O context handle which waits on the list. When processed
+ *                   The status code is returned. NULL if there is no I/O context
+ *                   to return the status code for.
+ */
+static int vdDiskProcessWaitingIoCtx(PVBOXHDD pDisk, PVDIOCTX pIoCtxRc)
+{
+    int rc = VINF_SUCCESS;
+
+    LogFlowFunc(("pDisk=%#p pIoCtxRc=%#p\n", pDisk, pIoCtxRc));
+
+    VD_IS_LOCKED(pDisk);
+
+    /* Get the waiting list and process it in FIFO order. */
+    PVDIOCTX pIoCtxHead = ASMAtomicXchgPtrT(&pDisk->pIoCtxHead, NULL, PVDIOCTX);
+
+    /* Reverse it. */
+    PVDIOCTX pCur = pIoCtxHead;
+    pIoCtxHead = NULL;
+    while (pCur)
+    {
+        PVDIOCTX pInsert = pCur;
+        pCur = pCur->pIoCtxNext;
+        pInsert->pIoCtxNext = pIoCtxHead;
+        pIoCtxHead = pInsert;
+    }
+
+    /* Process now. */
+    pCur = pIoCtxHead;
+    while (pCur)
+    {
+        int rcTmp;
+        PVDIOCTX pTmp = pCur;
+
+        pCur = pCur->pIoCtxNext;
+        pTmp->pIoCtxNext = NULL;
+
+        /*
+         * Need to clear the sync flag here if there is a new I/O context
+         * with it set and the context is not given in pIoCtxRc.
+         * This happens most likely on a different thread and that one shouldn't
+         * process the context synchronously.
+         *
+         * The thread who issued the context will wait on the event semaphore
+         * anyway which is signalled when the completion handler is called.
+         */
+        if (   pTmp->fFlags & VDIOCTX_FLAGS_SYNC
+            && pTmp != pIoCtxRc)
+            pTmp->fFlags &= ~VDIOCTX_FLAGS_SYNC;
+
+        rcTmp = vdIoCtxProcessLocked(pTmp);
+        if (pTmp == pIoCtxRc)
+        {
+            /* The given I/O context was processed, pass the return code to the caller. */
+            rc = rcTmp;
+        }
+        else if (   rcTmp == VINF_VD_ASYNC_IO_FINISHED
+                 && ASMAtomicCmpXchgBool(&pTmp->fComplete, true, false))
+        {
+            LogFlowFunc(("Waiting I/O context completed pTmp=%#p\n", pTmp));
+            vdThreadFinishWrite(pDisk);
+            pTmp->Type.Root.pfnComplete(pTmp->Type.Root.pvUser1,
+                                        pTmp->Type.Root.pvUser2,
+                                        pTmp->rcReq);
+            vdIoCtxFree(pDisk, pTmp);
+        }
+    }
+
+    LogFlowFunc(("returns rc=%Rrc\n", rc));
+    return rc;
+}
+
+/**
+ * Processes the list of blocked I/O contexts.
+ *
+ * @returns nothing.
+ * @param   pDisk    The disk structure.
+ */
+static void vdDiskProcessBlockedIoCtx(PVBOXHDD pDisk)
+{
+    LogFlowFunc(("pDisk=%#p\n", pDisk));
+
+    VD_IS_LOCKED(pDisk);
+
+    /* Get the waiting list and process it in FIFO order. */
+    PVDIOCTX pIoCtxHead = ASMAtomicXchgPtrT(&pDisk->pIoCtxBlockedHead, NULL, PVDIOCTX);
+
+    /* Reverse it. */
+    PVDIOCTX pCur = pIoCtxHead;
+    pIoCtxHead = NULL;
+    while (pCur)
+    {
+        PVDIOCTX pInsert = pCur;
+        pCur = pCur->pIoCtxNext;
+        pInsert->pIoCtxNext = pIoCtxHead;
+        pIoCtxHead = pInsert;
+    }
+
+    /* Process now. */
+    pCur = pIoCtxHead;
+    while (pCur)
+    {
+        int rc;
+        PVDIOCTX pTmp = pCur;
+
+        pCur = pCur->pIoCtxNext;
+        pTmp->pIoCtxNext = NULL;
+
+        Assert(!pTmp->pIoCtxParent);
+        Assert(pTmp->fFlags & VDIOCTX_FLAGS_BLOCKED);
+        pTmp->fFlags &= ~VDIOCTX_FLAGS_BLOCKED;
+
+        rc = vdIoCtxProcessLocked(pTmp);
+        if (   rc == VINF_VD_ASYNC_IO_FINISHED
+            && ASMAtomicCmpXchgBool(&pTmp->fComplete, true, false))
+        {
+            LogFlowFunc(("Waiting I/O context completed pTmp=%#p\n", pTmp));
+            vdThreadFinishWrite(pDisk);
+            pTmp->Type.Root.pfnComplete(pTmp->Type.Root.pvUser1,
+                                        pTmp->Type.Root.pvUser2,
+                                        pTmp->rcReq);
+            vdIoCtxFree(pDisk, pTmp);
+        }
+    }
+
+    LogFlowFunc(("returns\n"));
+}
+
+/**
+ * Processes the I/O context trying to lock the criticial section.
+ * The context is deferred if the critical section is busy.
+ *
+ * @returns VBox status code.
+ * @param   pIoCtx    The I/O context to process.
+ */
+static int vdIoCtxProcessTryLockDefer(PVDIOCTX pIoCtx)
+{
+    int rc = VINF_SUCCESS;
+    PVBOXHDD pDisk = pIoCtx->pDisk;
+
+    LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+    /* Put it on the waiting list first. */
+    vdIoCtxAddToWaitingList(&pDisk->pIoCtxHead, pIoCtx);
+
+    if (ASMAtomicCmpXchgBool(&pDisk->fLocked, true, false))
+    {
+        /* Leave it again, the context will be processed just before leaving the lock. */
+        LogFlowFunc(("Successfully acquired the lock\n"));
+        rc = vdDiskUnlock(pDisk, pIoCtx);
+    }
+    else
+    {
+        LogFlowFunc(("Lock is held\n"));
+        rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+    }
+
+    return rc;
+}
+
+/**
+ * Process the I/O context in a synchronous manner, waiting
+ * for it to complete.
+ *
+ * @returns VBox status code of the completed request.
+ * @param   pIoCtx    The sync I/O context.
+ */
+static int vdIoCtxProcessSync(PVDIOCTX pIoCtx)
+{
+    int rc = VINF_SUCCESS;
+    PVBOXHDD pDisk = pIoCtx->pDisk;
+
+    LogFlowFunc(("pIoCtx=%p\n", pIoCtx));
+
+    AssertMsg(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC,
+              ("I/O context is not marked as synchronous\n"));
+
+    rc = vdIoCtxProcessTryLockDefer(pIoCtx);
+    if (rc == VINF_VD_ASYNC_IO_FINISHED)
+        rc = VINF_SUCCESS;
+
+    if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+    {
+        rc = RTSemEventWait(pDisk->hEventSemSyncIo, RT_INDEFINITE_WAIT);
+        AssertRC(rc);
+
+        rc = pDisk->rcSync;
+    }
+    else /* Success or error. */
+        vdIoCtxFree(pDisk, pIoCtx);
+
+    return rc;
+}
+
+DECLINLINE(bool) vdIoCtxIsDiskLockOwner(PVBOXHDD pDisk, PVDIOCTX pIoCtx)
+{
+    return pDisk->pIoCtxLockOwner == pIoCtx;
+}
+
+static int vdIoCtxLockDisk(PVBOXHDD pDisk, PVDIOCTX pIoCtx)
+{
+    int rc = VINF_SUCCESS;
+
+    VD_IS_LOCKED(pDisk);
+
+    LogFlowFunc(("pDisk=%#p pIoCtx=%#p\n", pDisk, pIoCtx));
+
+    if (!ASMAtomicCmpXchgPtr(&pDisk->pIoCtxLockOwner, pIoCtx, NIL_VDIOCTX))
+    {
+        Assert(pDisk->pIoCtxLockOwner != pIoCtx); /* No nesting allowed. */
+        vdIoCtxDefer(pDisk, pIoCtx);
+        rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+    }
+
+    LogFlowFunc(("returns -> %Rrc\n", rc));
+    return rc;
+}
+
+static void vdIoCtxUnlockDisk(PVBOXHDD pDisk, PVDIOCTX pIoCtx, bool fProcessBlockedReqs)
+{
+    LogFlowFunc(("pDisk=%#p pIoCtx=%#p fProcessBlockedReqs=%RTbool\n",
+                 pDisk, pIoCtx, fProcessBlockedReqs));
+
+    VD_IS_LOCKED(pDisk);
+
+    LogFlow(("Unlocking disk lock owner is %#p\n", pDisk->pIoCtxLockOwner));
+    Assert(pDisk->pIoCtxLockOwner == pIoCtx);
+    ASMAtomicXchgPtrT(&pDisk->pIoCtxLockOwner, NIL_VDIOCTX, PVDIOCTX);
+
+    if (fProcessBlockedReqs)
+    {
+        /* Process any blocked writes if the current request didn't caused another growing. */
+        vdDiskProcessBlockedIoCtx(pDisk);
+    }
+
+    LogFlowFunc(("returns\n"));
+}
+
+/**
  * Internal: Reads a given amount of data from the image chain of the disk.
  **/
 static int vdDiskReadHelper(PVBOXHDD pDisk, PVDIMAGE pImage, PVDIMAGE pImageParentOverride,
-                            uint64_t uOffset, void *pvBuf, size_t cbRead, size_t *pcbThisRead)
+                            uint64_t uOffset, size_t cbRead, PVDIOCTX pIoCtx, size_t *pcbThisRead)
 {
     int rc = VINF_SUCCESS;
     size_t cbThisRead = cbRead;
-    RTSGSEG SegmentBuf;
-    RTSGBUF SgBuf;
-    VDIOCTX IoCtx;
 
     AssertPtr(pcbThisRead);
 
     *pcbThisRead = 0;
-
-    SegmentBuf.pvSeg = pvBuf;
-    SegmentBuf.cbSeg = VD_MERGE_BUFFER_SIZE;
-    RTSgBufInit(&SgBuf, &SegmentBuf, 1);
-    vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_READ, 0, 0, NULL,
-                &SgBuf, NULL, NULL, VDIOCTX_FLAGS_SYNC);
 
     /*
@@ -934,6 +1712,6 @@
      */
     rc = pImage->Backend->pfnRead(pImage->pBackendData,
-                                       uOffset, cbThisRead, &IoCtx,
-                                       &cbThisRead);
+                                  uOffset, cbThisRead, pIoCtx,
+                                  &cbThisRead);
 
     if (rc == VERR_VD_BLOCK_FREE)
@@ -944,6 +1722,6 @@
         {
             rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData,
-                                                   uOffset, cbThisRead, &IoCtx,
-                                                   &cbThisRead);
+                                              uOffset, cbThisRead, pIoCtx,
+                                              &cbThisRead);
         }
     }
@@ -952,4 +1730,174 @@
         *pcbThisRead = cbThisRead;
 
+    return rc;
+}
+
+/**
+ * internal: read the specified amount of data in whatever blocks the backend
+ * will give us - async version.
+ */
+static int vdReadHelperAsync(PVDIOCTX pIoCtx)
+{
+    int rc;
+    PVBOXHDD pDisk                = pIoCtx->pDisk;
+    size_t cbToRead               = pIoCtx->Req.Io.cbTransfer;
+    uint64_t uOffset              = pIoCtx->Req.Io.uOffset;
+    PVDIMAGE pCurrImage           = pIoCtx->Req.Io.pImageCur;
+    PVDIMAGE pImageParentOverride = pIoCtx->Req.Io.pImageParentOverride;
+    unsigned cImagesRead          = pIoCtx->Req.Io.cImagesRead;
+    size_t cbThisRead;
+
+    /* Loop until all reads started or we have a backend which needs to read metadata. */
+    do
+    {
+        /* Search for image with allocated block. Do not attempt to read more
+         * than the previous reads marked as valid. Otherwise this would return
+         * stale data when different block sizes are used for the images. */
+        cbThisRead = cbToRead;
+
+        if (   pDisk->pCache
+            && !pImageParentOverride)
+        {
+            rc = vdCacheReadHelper(pDisk->pCache, uOffset, cbThisRead,
+                                   pIoCtx, &cbThisRead);
+            if (rc == VERR_VD_BLOCK_FREE)
+            {
+                rc = vdDiskReadHelper(pDisk, pCurrImage, NULL, uOffset, cbThisRead,
+                                      pIoCtx, &cbThisRead);
+
+                /* If the read was successful, write the data back into the cache. */
+                if (   RT_SUCCESS(rc)
+                    && pIoCtx->fFlags & VDIOCTX_FLAGS_READ_UDATE_CACHE)
+                {
+                    rc = vdCacheWriteHelper(pDisk->pCache, uOffset, cbThisRead,
+                                            pIoCtx, NULL);
+                }
+            }
+        }
+        else
+        {
+
+            /*
+             * Try to read from the given image.
+             * If the block is not allocated read from override chain if present.
+             */
+            rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData,
+                                              uOffset, cbThisRead, pIoCtx,
+                                              &cbThisRead);
+
+            if (   rc == VERR_VD_BLOCK_FREE
+                && cImagesRead != 1)
+            {
+                unsigned cImagesToProcess = cImagesRead;
+
+                pCurrImage = pImageParentOverride ? pImageParentOverride : pCurrImage->pPrev;
+                pIoCtx->Req.Io.pImageParentOverride = NULL;
+
+                while (pCurrImage && rc == VERR_VD_BLOCK_FREE)
+                {
+                    rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData,
+                                                      uOffset, cbThisRead,
+                                                      pIoCtx, &cbThisRead);
+                    if (cImagesToProcess == 1)
+                        break;
+                    else if (cImagesToProcess > 0)
+                        cImagesToProcess--;
+
+                    if (rc == VERR_VD_BLOCK_FREE)
+                        pCurrImage = pCurrImage->pPrev;
+                }
+            }
+        }
+
+        /* The task state will be updated on success already, don't do it here!. */
+        if (rc == VERR_VD_BLOCK_FREE)
+        {
+            /* No image in the chain contains the data for the block. */
+            ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, cbThisRead);
+
+            /* Fill the free space with 0 if we are told to do so
+             * or a previous read returned valid data. */
+            if (   (pIoCtx->fFlags & VDIOCTX_FLAGS_ZERO_FREE_BLOCKS)
+                || !pIoCtx->Req.Io.fAllFree)
+                vdIoCtxSet(pIoCtx, '\0', cbThisRead);
+            else
+                pIoCtx->Req.Io.cbBufClear += cbThisRead;
+
+            if (pIoCtx->Req.Io.pImageCur->uOpenFlags & VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS)
+                rc = VINF_VD_NEW_ZEROED_BLOCK;
+            else
+                rc = VINF_SUCCESS;
+        }
+        else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+            rc = VINF_SUCCESS;
+        else if (rc == VERR_VD_IOCTX_HALT)
+        {
+            uOffset  += cbThisRead;
+            cbToRead -= cbThisRead;
+            pIoCtx->fFlags |= VDIOCTX_FLAGS_BLOCKED;
+        }
+        else if (   RT_SUCCESS(rc)
+                 && !(pIoCtx->fFlags & VDIOCTX_FLAGS_ZERO_FREE_BLOCKS))
+        {
+            /* First not free block, fill the space before with 0. */
+            RTSGBUF SgBuf;
+            RTSgBufClone(&SgBuf, &pIoCtx->Req.Io.SgBuf);
+            RTSgBufReset(&SgBuf);
+            RTSgBufSet(&SgBuf, 0, pIoCtx->Req.Io.cbBufClear);
+            pIoCtx->Req.Io.cbBufClear = 0;
+            pIoCtx->Req.Io.fAllFree = false;
+        }
+
+        if (RT_FAILURE(rc))
+            break;
+
+        cbToRead -= cbThisRead;
+        uOffset  += cbThisRead;
+        pCurrImage = pIoCtx->Req.Io.pImageStart; /* Start with the highest image in the chain. */
+    } while (cbToRead != 0 && RT_SUCCESS(rc));
+
+    if (   rc == VERR_VD_NOT_ENOUGH_METADATA
+        || rc == VERR_VD_IOCTX_HALT)
+    {
+        /* Save the current state. */
+        pIoCtx->Req.Io.uOffset    = uOffset;
+        pIoCtx->Req.Io.cbTransfer = cbToRead;
+        pIoCtx->Req.Io.pImageCur  = pCurrImage ? pCurrImage : pIoCtx->Req.Io.pImageStart;
+    }
+
+    return (!(pIoCtx->fFlags & VDIOCTX_FLAGS_ZERO_FREE_BLOCKS) && pIoCtx->Req.Io.fAllFree)
+           ? VERR_VD_BLOCK_FREE
+           : rc;
+}
+
+/**
+ * internal: parent image read wrapper for compacting.
+ */
+static int vdParentRead(void *pvUser, uint64_t uOffset, void *pvBuf,
+                        size_t cbRead)
+{
+    PVDPARENTSTATEDESC pParentState = (PVDPARENTSTATEDESC)pvUser;
+
+    /** @todo
+     * Only used for compaction so far which is not possible to mix with async I/O.
+     * Needs to be changed if we want to support online compaction of images.
+     */
+    bool fLocked = ASMAtomicXchgBool(&pParentState->pDisk->fLocked, true);
+    AssertMsgReturn(!fLocked,
+                    ("Calling synchronous parent read while another thread holds the disk lock\n"),
+                    VERR_VD_INVALID_STATE);
+
+    /* Fake an I/O context. */
+    RTSGSEG Segment;
+    RTSGBUF SgBuf;
+    VDIOCTX IoCtx;
+
+    Segment.pvSeg = pvBuf;
+    Segment.cbSeg = cbRead;
+    RTSgBufInit(&SgBuf, &Segment, 1);
+    vdIoCtxInit(&IoCtx, pParentState->pDisk, VDIOCTXTXDIR_READ, uOffset, cbRead, pParentState->pImage,
+                &SgBuf, NULL, NULL, VDIOCTX_FLAGS_SYNC);
+    int rc = vdReadHelperAsync(&IoCtx);
+    ASMAtomicXchgBool(&pParentState->pDisk->fLocked, false);
     return rc;
 }
@@ -984,112 +1932,26 @@
                           bool fZeroFreeBlocks, bool fUpdateCache, unsigned cImagesRead)
 {
-    int rc = VINF_SUCCESS;
-    size_t cbThisRead;
-    bool fAllFree = true;
-    size_t cbBufClear = 0;
-
-    /* Loop until all read. */
-    do
-    {
-        /* Search for image with allocated block. Do not attempt to read more
-         * than the previous reads marked as valid. Otherwise this would return
-         * stale data when different block sizes are used for the images. */
-        cbThisRead = cbRead;
-
-        if (   pDisk->pCache
-            && !pImageParentOverride)
-        {
-#if 0 /** @todo: Will go soon when the sync and async read helper versions are merged. */
-            rc = vdCacheReadHelper(pDisk->pCache, uOffset, pvBuf,
-                                   cbThisRead, &cbThisRead);
-#endif
-            if (rc == VERR_VD_BLOCK_FREE)
-            {
-                rc = vdDiskReadHelper(pDisk, pImage, NULL, uOffset, pvBuf, cbThisRead,
-                                      &cbThisRead);
-
-                /* If the read was successful, write the data back into the cache. */
-                if (   RT_SUCCESS(rc)
-                    && fUpdateCache)
-                {
-#if 0 /** @todo: Will go soon when the sync and async read helper versions are merged. */
-                    rc = vdCacheWriteHelper(pDisk->pCache, uOffset, pvBuf,
-                                            cbThisRead, NULL);
-#endif
-                }
-            }
-        }
-        else
-        {
-            RTSGSEG SegmentBuf;
-            RTSGBUF SgBuf;
-            VDIOCTX IoCtx;
-
-            SegmentBuf.pvSeg = pvBuf;
-            SegmentBuf.cbSeg = cbThisRead;
-            RTSgBufInit(&SgBuf, &SegmentBuf, 1);
-            vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_READ, 0, 0, NULL,
-                        &SgBuf, NULL, NULL, VDIOCTX_FLAGS_SYNC);
-
-            /*
-             * Try to read from the given image.
-             * If the block is not allocated read from override chain if present.
-             */
-            rc = pImage->Backend->pfnRead(pImage->pBackendData,
-                                               uOffset, cbThisRead, &IoCtx,
-                                               &cbThisRead);
-
-            if (   rc == VERR_VD_BLOCK_FREE
-                && cImagesRead != 1)
-            {
-                unsigned cImagesToProcess = cImagesRead;
-
-                for (PVDIMAGE pCurrImage = pImageParentOverride ? pImageParentOverride : pImage->pPrev;
-                     pCurrImage != NULL && rc == VERR_VD_BLOCK_FREE;
-                     pCurrImage = pCurrImage->pPrev)
-                {
-                    rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData,
-                                                           uOffset, cbThisRead,
-                                                           &IoCtx, &cbThisRead);
-                    if (cImagesToProcess == 1)
-                        break;
-                    else if (cImagesToProcess > 0)
-                        cImagesToProcess--;
-                }
-            }
-        }
-
-        /* No image in the chain contains the data for the block. */
-        if (rc == VERR_VD_BLOCK_FREE)
-        {
-            /* Fill the free space with 0 if we are told to do so
-             * or a previous read returned valid data. */
-            if (fZeroFreeBlocks || !fAllFree)
-                memset(pvBuf, '\0', cbThisRead);
-            else
-                cbBufClear += cbThisRead;
-
-            if (pImage->uOpenFlags & VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS)
-                rc = VINF_VD_NEW_ZEROED_BLOCK;
-            else
-                rc = VINF_SUCCESS;
-        }
-        else if (RT_SUCCESS(rc))
-        {
-            /* First not free block, fill the space before with 0. */
-            if (!fZeroFreeBlocks)
-            {
-                memset((char *)pvBuf - cbBufClear, '\0', cbBufClear);
-                cbBufClear = 0;
-                fAllFree = false;
-            }
-        }
-
-        cbRead -= cbThisRead;
-        uOffset += cbThisRead;
-        pvBuf = (char *)pvBuf + cbThisRead;
-    } while (cbRead != 0 && RT_SUCCESS(rc));
-
-    return (!fZeroFreeBlocks && fAllFree) ? VERR_VD_BLOCK_FREE : rc;
+    uint32_t fFlags = VDIOCTX_FLAGS_SYNC | VDIOCTX_FLAGS_DONT_FREE;
+    RTSGSEG Segment;
+    RTSGBUF SgBuf;
+    VDIOCTX IoCtx;
+
+    if (fZeroFreeBlocks)
+        fFlags |= VDIOCTX_FLAGS_ZERO_FREE_BLOCKS;
+    if (fUpdateCache)
+        fFlags |= VDIOCTX_FLAGS_READ_UDATE_CACHE;
+
+    Segment.pvSeg = pvBuf;
+    Segment.cbSeg = cbRead;
+    RTSgBufInit(&SgBuf, &Segment, 1);
+    vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_READ, uOffset, cbRead, pImage, &SgBuf,
+                NULL, vdReadHelperAsync, fFlags);
+
+    IoCtx.Req.Io.pImageParentOverride = pImageParentOverride;
+    IoCtx.Req.Io.cImagesRead = cImagesRead;
+    IoCtx.Type.Root.pfnComplete = vdIoCtxSyncComplete;
+    IoCtx.Type.Root.pvUser1     = pDisk;
+    IoCtx.Type.Root.pvUser2     = NULL;
+    return vdIoCtxProcessSync(&IoCtx);
 }
 
@@ -1103,834 +1965,4 @@
     return vdReadHelperEx(pDisk, pImage, NULL, uOffset, pvBuf, cbRead,
                           true /* fZeroFreeBlocks */, fUpdateCache, 0);
-}
-
-/**
- * Creates a new empty discard state.
- *
- * @returns Pointer to the new discard state or NULL if out of memory.
- */
-static PVDDISCARDSTATE vdDiscardStateCreate(void)
-{
-    PVDDISCARDSTATE pDiscard = (PVDDISCARDSTATE)RTMemAllocZ(sizeof(VDDISCARDSTATE));
-
-    if (pDiscard)
-    {
-        RTListInit(&pDiscard->ListLru);
-        pDiscard->pTreeBlocks = (PAVLRU64TREE)RTMemAllocZ(sizeof(AVLRU64TREE));
-        if (!pDiscard->pTreeBlocks)
-        {
-            RTMemFree(pDiscard);
-            pDiscard = NULL;
-        }
-    }
-
-    return pDiscard;
-}
-
-/**
- * Removes the least recently used blocks from the waiting list until
- * the new value is reached.
- *
- * @returns VBox status code.
- * @param   pDisk              VD disk container.
- * @param   pDiscard           The discard state.
- * @param   cbDiscardingNew    How many bytes should be waiting on success.
- *                             The number of bytes waiting can be less.
- */
-static int vdDiscardRemoveBlocks(PVBOXHDD pDisk, PVDDISCARDSTATE pDiscard, size_t cbDiscardingNew)
-{
-    int rc = VINF_SUCCESS;
-
-    LogFlowFunc(("pDisk=%#p pDiscard=%#p cbDiscardingNew=%zu\n",
-                 pDisk, pDiscard, cbDiscardingNew));
-
-    while (pDiscard->cbDiscarding > cbDiscardingNew)
-    {
-        PVDDISCARDBLOCK pBlock = RTListGetLast(&pDiscard->ListLru, VDDISCARDBLOCK, NodeLru);
-
-        Assert(!RTListIsEmpty(&pDiscard->ListLru));
-
-        /* Go over the allocation bitmap and mark all discarded sectors as unused. */
-        uint64_t offStart = pBlock->Core.Key;
-        uint32_t idxStart = 0;
-        size_t cbLeft = pBlock->cbDiscard;
-        bool fAllocated = ASMBitTest(pBlock->pbmAllocated, idxStart);
-        uint32_t cSectors = pBlock->cbDiscard / 512;
-
-        while (cbLeft > 0)
-        {
-            int32_t idxEnd;
-            size_t cbThis = cbLeft;
-
-            if (fAllocated)
-            {
-                /* Check for the first unallocated bit. */
-                idxEnd = ASMBitNextClear(pBlock->pbmAllocated, cSectors, idxStart);
-                if (idxEnd != -1)
-                {
-                    cbThis = (idxEnd - idxStart) * 512;
-                    fAllocated = false;
-                }
-            }
-            else
-            {
-                /* Mark as unused and check for the first set bit. */
-                idxEnd = ASMBitNextSet(pBlock->pbmAllocated, cSectors, idxStart);
-                if (idxEnd != -1)
-                    cbThis = (idxEnd - idxStart) * 512;
-
-
-                VDIOCTX IoCtx;
-                vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_DISCARD, 0, 0, NULL,
-                            NULL, NULL, NULL, VDIOCTX_FLAGS_SYNC);
-                rc = pDisk->pLast->Backend->pfnDiscard(pDisk->pLast->pBackendData,
-                                                            &IoCtx, offStart, cbThis, NULL,
-                                                            NULL, &cbThis, NULL,
-                                                            VD_DISCARD_MARK_UNUSED);
-                if (RT_FAILURE(rc))
-                    break;
-
-                fAllocated = true;
-            }
-
-            idxStart  = idxEnd;
-            offStart += cbThis;
-            cbLeft   -= cbThis;
-        }
-
-        if (RT_FAILURE(rc))
-            break;
-
-        PVDDISCARDBLOCK pBlockRemove = (PVDDISCARDBLOCK)RTAvlrU64RangeRemove(pDiscard->pTreeBlocks, pBlock->Core.Key);
-        Assert(pBlockRemove == pBlock);
-        RTListNodeRemove(&pBlock->NodeLru);
-
-        pDiscard->cbDiscarding -= pBlock->cbDiscard;
-        RTMemFree(pBlock->pbmAllocated);
-        RTMemFree(pBlock);
-    }
-
-    Assert(RT_FAILURE(rc) || pDiscard->cbDiscarding <= cbDiscardingNew);
-
-    LogFlowFunc(("returns rc=%Rrc\n", rc));
-    return rc;
-}
-
-/**
- * Destroys the current discard state, writing any waiting blocks to the image.
- *
- * @returns VBox status code.
- * @param   pDisk    VD disk container.
- */
-static int vdDiscardStateDestroy(PVBOXHDD pDisk)
-{
-    int rc = VINF_SUCCESS;
-
-    if (pDisk->pDiscard)
-    {
-        rc = vdDiscardRemoveBlocks(pDisk, pDisk->pDiscard, 0 /* Remove all blocks. */);
-        AssertRC(rc);
-        RTMemFree(pDisk->pDiscard->pTreeBlocks);
-        RTMemFree(pDisk->pDiscard);
-        pDisk->pDiscard = NULL;
-    }
-
-    return rc;
-}
-
-/**
- * Marks the given range as allocated in the image.
- * Required if there are discards in progress and a write to a block which can get discarded
- * is written to.
- *
- * @returns VBox status code.
- * @param   pDisk    VD container data.
- * @param   uOffset  First byte to mark as allocated.
- * @param   cbRange  Number of bytes to mark as allocated.
- */
-static int vdDiscardSetRangeAllocated(PVBOXHDD pDisk, uint64_t uOffset, size_t cbRange)
-{
-    PVDDISCARDSTATE pDiscard = pDisk->pDiscard;
-    int rc = VINF_SUCCESS;
-
-    if (pDiscard)
-    {
-        do
-        {
-            size_t cbThisRange = cbRange;
-            PVDDISCARDBLOCK pBlock = (PVDDISCARDBLOCK)RTAvlrU64RangeGet(pDiscard->pTreeBlocks, uOffset);
-
-            if (pBlock)
-            {
-                int32_t idxStart, idxEnd;
-
-                Assert(!(cbThisRange % 512));
-                Assert(!((uOffset - pBlock->Core.Key) % 512));
-
-                cbThisRange = RT_MIN(cbThisRange, pBlock->Core.KeyLast - uOffset + 1);
-
-                idxStart = (uOffset - pBlock->Core.Key) / 512;
-                idxEnd = idxStart + (cbThisRange / 512);
-                ASMBitSetRange(pBlock->pbmAllocated, idxStart, idxEnd);
-            }
-            else
-            {
-                pBlock = (PVDDISCARDBLOCK)RTAvlrU64GetBestFit(pDiscard->pTreeBlocks, uOffset, true);
-                if (pBlock)
-                    cbThisRange = RT_MIN(cbThisRange, pBlock->Core.Key - uOffset);
-            }
-
-            Assert(cbRange >= cbThisRange);
-
-            uOffset += cbThisRange;
-            cbRange -= cbThisRange;
-        } while (cbRange != 0);
-    }
-
-    return rc;
-}
-
-DECLINLINE(PVDIOCTX) vdIoCtxAlloc(PVBOXHDD pDisk, VDIOCTXTXDIR enmTxDir,
-                                  uint64_t uOffset, size_t cbTransfer,
-                                  PVDIMAGE pImageStart,PCRTSGBUF pcSgBuf,
-                                  void *pvAllocation, PFNVDIOCTXTRANSFER pfnIoCtxTransfer,
-                                  uint32_t fFlags)
-{
-    PVDIOCTX pIoCtx = NULL;
-
-    pIoCtx = (PVDIOCTX)RTMemCacheAlloc(pDisk->hMemCacheIoCtx);
-    if (RT_LIKELY(pIoCtx))
-    {
-        vdIoCtxInit(pIoCtx, pDisk, enmTxDir, uOffset, cbTransfer, pImageStart,
-                    pcSgBuf, pvAllocation, pfnIoCtxTransfer, fFlags);
-    }
-
-    return pIoCtx;
-}
-
-DECLINLINE(PVDIOCTX) vdIoCtxRootAlloc(PVBOXHDD pDisk, VDIOCTXTXDIR enmTxDir,
-                                      uint64_t uOffset, size_t cbTransfer,
-                                      PVDIMAGE pImageStart, PCRTSGBUF pcSgBuf,
-                                      PFNVDASYNCTRANSFERCOMPLETE pfnComplete,
-                                      void *pvUser1, void *pvUser2,
-                                      void *pvAllocation,
-                                      PFNVDIOCTXTRANSFER pfnIoCtxTransfer,
-                                      uint32_t fFlags)
-{
-    PVDIOCTX pIoCtx = vdIoCtxAlloc(pDisk, enmTxDir, uOffset, cbTransfer, pImageStart,
-                                   pcSgBuf, pvAllocation, pfnIoCtxTransfer, fFlags);
-
-    if (RT_LIKELY(pIoCtx))
-    {
-        pIoCtx->pIoCtxParent          = NULL;
-        pIoCtx->Type.Root.pfnComplete = pfnComplete;
-        pIoCtx->Type.Root.pvUser1     = pvUser1;
-        pIoCtx->Type.Root.pvUser2     = pvUser2;
-    }
-
-    LogFlow(("Allocated root I/O context %#p\n", pIoCtx));
-    return pIoCtx;
-}
-
-DECLINLINE(PVDIOCTX) vdIoCtxDiscardAlloc(PVBOXHDD pDisk, PCRTRANGE paRanges,
-                                         unsigned cRanges,
-                                         PFNVDASYNCTRANSFERCOMPLETE pfnComplete,
-                                         void *pvUser1, void *pvUser2,
-                                         void *pvAllocation,
-                                         PFNVDIOCTXTRANSFER pfnIoCtxTransfer,
-                                         uint32_t fFlags)
-{
-    PVDIOCTX pIoCtx = NULL;
-
-    pIoCtx = (PVDIOCTX)RTMemCacheAlloc(pDisk->hMemCacheIoCtx);
-    if (RT_LIKELY(pIoCtx))
-    {
-        pIoCtx->pIoCtxNext                = NULL;
-        pIoCtx->pDisk                     = pDisk;
-        pIoCtx->enmTxDir                  = VDIOCTXTXDIR_DISCARD;
-        pIoCtx->cDataTransfersPending     = 0;
-        pIoCtx->cMetaTransfersPending     = 0;
-        pIoCtx->fComplete                 = false;
-        pIoCtx->fFlags                    = fFlags;
-        pIoCtx->pvAllocation              = pvAllocation;
-        pIoCtx->pfnIoCtxTransfer          = pfnIoCtxTransfer;
-        pIoCtx->pfnIoCtxTransferNext      = NULL;
-        pIoCtx->rcReq                     = VINF_SUCCESS;
-        pIoCtx->Req.Discard.paRanges      = paRanges;
-        pIoCtx->Req.Discard.cRanges       = cRanges;
-        pIoCtx->Req.Discard.idxRange      = 0;
-        pIoCtx->Req.Discard.cbDiscardLeft = 0;
-        pIoCtx->Req.Discard.offCur        = 0;
-        pIoCtx->Req.Discard.cbThisDiscard = 0;
-
-        pIoCtx->pIoCtxParent          = NULL;
-        pIoCtx->Type.Root.pfnComplete = pfnComplete;
-        pIoCtx->Type.Root.pvUser1     = pvUser1;
-        pIoCtx->Type.Root.pvUser2     = pvUser2;
-    }
-
-    LogFlow(("Allocated discard I/O context %#p\n", pIoCtx));
-    return pIoCtx;
-}
-
-DECLINLINE(PVDIOCTX) vdIoCtxChildAlloc(PVBOXHDD pDisk, VDIOCTXTXDIR enmTxDir,
-                                       uint64_t uOffset, size_t cbTransfer,
-                                       PVDIMAGE pImageStart, PCRTSGBUF pcSgBuf,
-                                       PVDIOCTX pIoCtxParent, size_t cbTransferParent,
-                                       size_t cbWriteParent, void *pvAllocation,
-                                       PFNVDIOCTXTRANSFER pfnIoCtxTransfer)
-{
-    PVDIOCTX pIoCtx = vdIoCtxAlloc(pDisk, enmTxDir, uOffset, cbTransfer, pImageStart,
-                                   pcSgBuf, pvAllocation, pfnIoCtxTransfer, 0);
-
-    AssertPtr(pIoCtxParent);
-    Assert(!pIoCtxParent->pIoCtxParent);
-
-    if (RT_LIKELY(pIoCtx))
-    {
-        pIoCtx->pIoCtxParent                   = pIoCtxParent;
-        pIoCtx->Type.Child.uOffsetSaved        = uOffset;
-        pIoCtx->Type.Child.cbTransferLeftSaved = cbTransfer;
-        pIoCtx->Type.Child.cbTransferParent    = cbTransferParent;
-        pIoCtx->Type.Child.cbWriteParent       = cbWriteParent;
-    }
-
-    LogFlow(("Allocated child I/O context %#p\n", pIoCtx));
-    return pIoCtx;
-}
-
-DECLINLINE(PVDIOTASK) vdIoTaskUserAlloc(PVDIOSTORAGE pIoStorage, PFNVDXFERCOMPLETED pfnComplete, void *pvUser, PVDIOCTX pIoCtx, uint32_t cbTransfer)
-{
-    PVDIOTASK pIoTask = NULL;
-
-    pIoTask = (PVDIOTASK)RTMemCacheAlloc(pIoStorage->pVDIo->pDisk->hMemCacheIoTask);
-    if (pIoTask)
-    {
-        pIoTask->pIoStorage           = pIoStorage;
-        pIoTask->pfnComplete          = pfnComplete;
-        pIoTask->pvUser               = pvUser;
-        pIoTask->fMeta                = false;
-        pIoTask->Type.User.cbTransfer = cbTransfer;
-        pIoTask->Type.User.pIoCtx     = pIoCtx;
-    }
-
-    return pIoTask;
-}
-
-DECLINLINE(PVDIOTASK) vdIoTaskMetaAlloc(PVDIOSTORAGE pIoStorage, PFNVDXFERCOMPLETED pfnComplete, void *pvUser, PVDMETAXFER pMetaXfer)
-{
-    PVDIOTASK pIoTask = NULL;
-
-    pIoTask = (PVDIOTASK)RTMemCacheAlloc(pIoStorage->pVDIo->pDisk->hMemCacheIoTask);
-    if (pIoTask)
-    {
-        pIoTask->pIoStorage          = pIoStorage;
-        pIoTask->pfnComplete         = pfnComplete;
-        pIoTask->pvUser              = pvUser;
-        pIoTask->fMeta               = true;
-        pIoTask->Type.Meta.pMetaXfer = pMetaXfer;
-    }
-
-    return pIoTask;
-}
-
-DECLINLINE(void) vdIoCtxFree(PVBOXHDD pDisk, PVDIOCTX pIoCtx)
-{
-    LogFlow(("Freeing I/O context %#p\n", pIoCtx));
-    if (pIoCtx->pvAllocation)
-        RTMemFree(pIoCtx->pvAllocation);
-#ifdef DEBUG
-    memset(pIoCtx, 0xff, sizeof(VDIOCTX));
-#endif
-    RTMemCacheFree(pDisk->hMemCacheIoCtx, pIoCtx);
-}
-
-DECLINLINE(void) vdIoTaskFree(PVBOXHDD pDisk, PVDIOTASK pIoTask)
-{
-    RTMemCacheFree(pDisk->hMemCacheIoTask, pIoTask);
-}
-
-DECLINLINE(void) vdIoCtxChildReset(PVDIOCTX pIoCtx)
-{
-    AssertPtr(pIoCtx->pIoCtxParent);
-
-    RTSgBufReset(&pIoCtx->Req.Io.SgBuf);
-    pIoCtx->Req.Io.uOffset        = pIoCtx->Type.Child.uOffsetSaved;
-    pIoCtx->Req.Io.cbTransferLeft = pIoCtx->Type.Child.cbTransferLeftSaved;
-}
-
-DECLINLINE(PVDMETAXFER) vdMetaXferAlloc(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;
-}
-
-DECLINLINE(void) vdIoCtxAddToWaitingList(volatile PVDIOCTX *ppList, PVDIOCTX pIoCtx)
-{
-    /* Put it on the waiting list. */
-    PVDIOCTX pNext = ASMAtomicUoReadPtrT(ppList, PVDIOCTX);
-    PVDIOCTX pHeadOld;
-    pIoCtx->pIoCtxNext = pNext;
-    while (!ASMAtomicCmpXchgExPtr(ppList, pIoCtx, pNext, &pHeadOld))
-    {
-        pNext = pHeadOld;
-        Assert(pNext != pIoCtx);
-        pIoCtx->pIoCtxNext = pNext;
-        ASMNopPause();
-    }
-}
-
-DECLINLINE(void) vdIoCtxDefer(PVBOXHDD pDisk, PVDIOCTX pIoCtx)
-{
-    LogFlowFunc(("Deferring write pIoCtx=%#p\n", pIoCtx));
-
-    Assert(!pIoCtx->pIoCtxParent && !(pIoCtx->fFlags & VDIOCTX_FLAGS_BLOCKED));
-    pIoCtx->fFlags |= VDIOCTX_FLAGS_BLOCKED;
-    vdIoCtxAddToWaitingList(&pDisk->pIoCtxBlockedHead, pIoCtx);
-}
-
-static size_t vdIoCtxCopy(PVDIOCTX pIoCtxDst, PVDIOCTX pIoCtxSrc, size_t cbData)
-{
-    return RTSgBufCopy(&pIoCtxDst->Req.Io.SgBuf, &pIoCtxSrc->Req.Io.SgBuf, cbData);
-}
-
-static int vdIoCtxCmp(PVDIOCTX pIoCtx1, PVDIOCTX pIoCtx2, size_t cbData)
-{
-    return RTSgBufCmp(&pIoCtx1->Req.Io.SgBuf, &pIoCtx2->Req.Io.SgBuf, cbData);
-}
-
-static size_t vdIoCtxCopyTo(PVDIOCTX pIoCtx, const uint8_t *pbData, size_t cbData)
-{
-    return RTSgBufCopyFromBuf(&pIoCtx->Req.Io.SgBuf, pbData, cbData);
-}
-
-static size_t vdIoCtxCopyFrom(PVDIOCTX pIoCtx, uint8_t *pbData, size_t cbData)
-{
-    return RTSgBufCopyToBuf(&pIoCtx->Req.Io.SgBuf, pbData, cbData);
-}
-
-static size_t vdIoCtxSet(PVDIOCTX pIoCtx, uint8_t ch, size_t cbData)
-{
-    return RTSgBufSet(&pIoCtx->Req.Io.SgBuf, ch, cbData);
-}
-
-/**
- * Process the I/O context, core method which assumes that the I/O context
- * acquired the lock.
- *
- * @returns VBox status code.
- * @param   pIoCtx    I/O context to process.
- */
-static int vdIoCtxProcessLocked(PVDIOCTX pIoCtx)
-{
-    int rc = VINF_SUCCESS;
-
-    VD_IS_LOCKED(pIoCtx->pDisk);
-
-    LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
-
-    if (   !pIoCtx->cMetaTransfersPending
-        && !pIoCtx->cDataTransfersPending
-        && !pIoCtx->pfnIoCtxTransfer)
-    {
-        rc = VINF_VD_ASYNC_IO_FINISHED;
-        goto out;
-    }
-
-    /*
-     * We complete the I/O context in case of an error
-     * if there is no I/O task pending.
-     */
-    if (   RT_FAILURE(pIoCtx->rcReq)
-        && !pIoCtx->cMetaTransfersPending
-        && !pIoCtx->cDataTransfersPending)
-    {
-        rc = VINF_VD_ASYNC_IO_FINISHED;
-        goto out;
-    }
-
-    /* Don't change anything if there is a metadata transfer pending or we are blocked. */
-    if (   pIoCtx->cMetaTransfersPending
-        || (pIoCtx->fFlags & VDIOCTX_FLAGS_BLOCKED))
-    {
-        rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
-        goto out;
-    }
-
-    if (pIoCtx->pfnIoCtxTransfer)
-    {
-        /* Call the transfer function advancing to the next while there is no error. */
-        while (   pIoCtx->pfnIoCtxTransfer
-               && !pIoCtx->cMetaTransfersPending
-               && RT_SUCCESS(rc))
-        {
-            LogFlowFunc(("calling transfer function %#p\n", pIoCtx->pfnIoCtxTransfer));
-            rc = pIoCtx->pfnIoCtxTransfer(pIoCtx);
-
-            /* Advance to the next part of the transfer if the current one succeeded. */
-            if (RT_SUCCESS(rc))
-            {
-                pIoCtx->pfnIoCtxTransfer = pIoCtx->pfnIoCtxTransferNext;
-                pIoCtx->pfnIoCtxTransferNext = NULL;
-            }
-        }
-    }
-
-    if (   RT_SUCCESS(rc)
-        && !pIoCtx->cMetaTransfersPending
-        && !pIoCtx->cDataTransfersPending)
-        rc = VINF_VD_ASYNC_IO_FINISHED;
-    else if (   RT_SUCCESS(rc)
-             || rc == VERR_VD_NOT_ENOUGH_METADATA
-             || rc == VERR_VD_IOCTX_HALT)
-        rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
-    else if (RT_FAILURE(rc) && (rc != VERR_VD_ASYNC_IO_IN_PROGRESS))
-    {
-        ASMAtomicCmpXchgS32(&pIoCtx->rcReq, rc, VINF_SUCCESS);
-        /*
-         * The I/O context completed if we have an error and there is no data
-         * or meta data transfer pending.
-         */
-        if (   !pIoCtx->cMetaTransfersPending
-            && !pIoCtx->cDataTransfersPending)
-            rc = VINF_VD_ASYNC_IO_FINISHED;
-        else
-            rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
-    }
-
-out:
-    LogFlowFunc(("pIoCtx=%#p rc=%Rrc cDataTransfersPending=%u cMetaTransfersPending=%u fComplete=%RTbool\n",
-                 pIoCtx, rc, pIoCtx->cDataTransfersPending, pIoCtx->cMetaTransfersPending,
-                 pIoCtx->fComplete));
-
-    return rc;
-}
-
-/**
- * Processes the list of waiting I/O contexts.
- *
- * @returns VBox status code.
- * @param   pDisk    The disk structure.
- * @param   pIoCtxRc An I/O context handle which waits on the list. When processed
- *                   The status code is returned. NULL if there is no I/O context
- *                   to return the status code for.
- */
-static int vdDiskProcessWaitingIoCtx(PVBOXHDD pDisk, PVDIOCTX pIoCtxRc)
-{
-    int rc = VINF_SUCCESS;
-
-    LogFlowFunc(("pDisk=%#p pIoCtxRc=%#p\n", pDisk, pIoCtxRc));
-
-    VD_IS_LOCKED(pDisk);
-
-    /* Get the waiting list and process it in FIFO order. */
-    PVDIOCTX pIoCtxHead = ASMAtomicXchgPtrT(&pDisk->pIoCtxHead, NULL, PVDIOCTX);
-
-    /* Reverse it. */
-    PVDIOCTX pCur = pIoCtxHead;
-    pIoCtxHead = NULL;
-    while (pCur)
-    {
-        PVDIOCTX pInsert = pCur;
-        pCur = pCur->pIoCtxNext;
-        pInsert->pIoCtxNext = pIoCtxHead;
-        pIoCtxHead = pInsert;
-    }
-
-    /* Process now. */
-    pCur = pIoCtxHead;
-    while (pCur)
-    {
-        int rcTmp;
-        PVDIOCTX pTmp = pCur;
-
-        pCur = pCur->pIoCtxNext;
-        pTmp->pIoCtxNext = NULL;
-
-        rcTmp = vdIoCtxProcessLocked(pTmp);
-        if (pTmp == pIoCtxRc)
-        {
-            /* The given I/O context was processed, pass the return code to the caller. */
-            rc = rcTmp;
-        }
-        else if (   rcTmp == VINF_VD_ASYNC_IO_FINISHED
-                 && ASMAtomicCmpXchgBool(&pTmp->fComplete, true, false))
-        {
-            LogFlowFunc(("Waiting I/O context completed pTmp=%#p\n", pTmp));
-            vdThreadFinishWrite(pDisk);
-            pTmp->Type.Root.pfnComplete(pTmp->Type.Root.pvUser1,
-                                        pTmp->Type.Root.pvUser2,
-                                        pTmp->rcReq);
-            vdIoCtxFree(pDisk, pTmp);
-        }
-    }
-
-    LogFlowFunc(("returns rc=%Rrc\n", rc));
-    return rc;
-}
-
-/**
- * Processes the list of blocked I/O contexts.
- *
- * @returns nothing.
- * @param   pDisk    The disk structure.
- */
-static void vdDiskProcessBlockedIoCtx(PVBOXHDD pDisk)
-{
-    LogFlowFunc(("pDisk=%#p\n", pDisk));
-
-    VD_IS_LOCKED(pDisk);
-
-    /* Get the waiting list and process it in FIFO order. */
-    PVDIOCTX pIoCtxHead = ASMAtomicXchgPtrT(&pDisk->pIoCtxBlockedHead, NULL, PVDIOCTX);
-
-    /* Reverse it. */
-    PVDIOCTX pCur = pIoCtxHead;
-    pIoCtxHead = NULL;
-    while (pCur)
-    {
-        PVDIOCTX pInsert = pCur;
-        pCur = pCur->pIoCtxNext;
-        pInsert->pIoCtxNext = pIoCtxHead;
-        pIoCtxHead = pInsert;
-    }
-
-    /* Process now. */
-    pCur = pIoCtxHead;
-    while (pCur)
-    {
-        int rc;
-        PVDIOCTX pTmp = pCur;
-
-        pCur = pCur->pIoCtxNext;
-        pTmp->pIoCtxNext = NULL;
-
-        Assert(!pTmp->pIoCtxParent);
-        Assert(pTmp->fFlags & VDIOCTX_FLAGS_BLOCKED);
-        pTmp->fFlags &= ~VDIOCTX_FLAGS_BLOCKED;
-
-        rc = vdIoCtxProcessLocked(pTmp);
-        if (   rc == VINF_VD_ASYNC_IO_FINISHED
-            && ASMAtomicCmpXchgBool(&pTmp->fComplete, true, false))
-        {
-            LogFlowFunc(("Waiting I/O context completed pTmp=%#p\n", pTmp));
-            vdThreadFinishWrite(pDisk);
-            pTmp->Type.Root.pfnComplete(pTmp->Type.Root.pvUser1,
-                                        pTmp->Type.Root.pvUser2,
-                                        pTmp->rcReq);
-            vdIoCtxFree(pDisk, pTmp);
-        }
-    }
-
-    LogFlowFunc(("returns\n"));
-}
-
-/**
- * Processes the I/O context trying to lock the criticial section.
- * The context is deferred if the critical section is busy.
- *
- * @returns VBox status code.
- * @param   pIoCtx    The I/O context to process.
- */
-static int vdIoCtxProcessTryLockDefer(PVDIOCTX pIoCtx)
-{
-    int rc = VINF_SUCCESS;
-    PVBOXHDD pDisk = pIoCtx->pDisk;
-
-    LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
-
-    /* Put it on the waiting list first. */
-    vdIoCtxAddToWaitingList(&pDisk->pIoCtxHead, pIoCtx);
-
-    if (ASMAtomicCmpXchgBool(&pDisk->fLocked, true, false))
-    {
-        /* Leave it again, the context will be processed just before leaving the lock. */
-        LogFlowFunc(("Successfully acquired the lock\n"));
-        rc = vdDiskUnlock(pDisk, pIoCtx);
-    }
-    else
-    {
-        LogFlowFunc(("Lock is held\n"));
-        rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
-    }
-
-    return rc;
-}
-
-/**
- * Process the I/O context in a synchronous manner, waiting
- * for it to complete.
- *
- * @returns VBox status code of the completed request.
- * @param   pIoCtx    The sync I/O context.
- */
-static int vdIoCtxProcessSync(PVDIOCTX pIoCtx)
-{
-    int rc = VINF_SUCCESS;
-    PVBOXHDD pDisk = pIoCtx->pDisk;
-
-    LogFlowFunc(("pIoCtx=%p\n", pIoCtx));
-
-    AssertMsg(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC,
-              ("I/O context is not marked as synchronous\n"));
-
-    rc = vdIoCtxProcessTryLockDefer(pIoCtx);
-    if (rc == VINF_VD_ASYNC_IO_FINISHED)
-        rc = VINF_SUCCESS;
-
-    if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
-    {
-        rc = RTSemEventWait(pDisk->hEventSemSyncIo, RT_INDEFINITE_WAIT);
-        AssertRC(rc);
-
-        rc = pDisk->rcSync;
-    }
-    else /* Success or error. */
-        vdIoCtxFree(pDisk, pIoCtx);
-
-    return rc;
-}
-
-DECLINLINE(bool) vdIoCtxIsDiskLockOwner(PVBOXHDD pDisk, PVDIOCTX pIoCtx)
-{
-    return pDisk->pIoCtxLockOwner == pIoCtx;
-}
-
-static int vdIoCtxLockDisk(PVBOXHDD pDisk, PVDIOCTX pIoCtx)
-{
-    int rc = VINF_SUCCESS;
-
-    VD_IS_LOCKED(pDisk);
-
-    LogFlowFunc(("pDisk=%#p pIoCtx=%#p\n", pDisk, pIoCtx));
-
-    if (!ASMAtomicCmpXchgPtr(&pDisk->pIoCtxLockOwner, pIoCtx, NIL_VDIOCTX))
-    {
-        Assert(pDisk->pIoCtxLockOwner != pIoCtx); /* No nesting allowed. */
-        vdIoCtxDefer(pDisk, pIoCtx);
-        rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
-    }
-
-    LogFlowFunc(("returns -> %Rrc\n", rc));
-    return rc;
-}
-
-static void vdIoCtxUnlockDisk(PVBOXHDD pDisk, PVDIOCTX pIoCtx, bool fProcessBlockedReqs)
-{
-    LogFlowFunc(("pDisk=%#p pIoCtx=%#p fProcessBlockedReqs=%RTbool\n",
-                 pDisk, pIoCtx, fProcessBlockedReqs));
-
-    VD_IS_LOCKED(pDisk);
-
-    LogFlow(("Unlocking disk lock owner is %#p\n", pDisk->pIoCtxLockOwner));
-    Assert(pDisk->pIoCtxLockOwner == pIoCtx);
-    ASMAtomicXchgPtrT(&pDisk->pIoCtxLockOwner, NIL_VDIOCTX, PVDIOCTX);
-
-    if (fProcessBlockedReqs)
-    {
-        /* Process any blocked writes if the current request didn't caused another growing. */
-        vdDiskProcessBlockedIoCtx(pDisk);
-    }
-
-    LogFlowFunc(("returns\n"));
-}
-
-/**
- * internal: read the specified amount of data in whatever blocks the backend
- * will give us - async version.
- */
-static int vdReadHelperAsync(PVDIOCTX pIoCtx)
-{
-    int rc;
-    size_t cbToRead     = pIoCtx->Req.Io.cbTransfer;
-    uint64_t uOffset    = pIoCtx->Req.Io.uOffset;
-    PVDIMAGE pCurrImage = pIoCtx->Req.Io.pImageCur;;
-    size_t cbThisRead;
-
-    /* Loop until all reads started or we have a backend which needs to read metadata. */
-    do
-    {
-        /* Search for image with allocated block. Do not attempt to read more
-         * than the previous reads marked as valid. Otherwise this would return
-         * stale data when different block sizes are used for the images. */
-        cbThisRead = cbToRead;
-
-        /*
-         * Try to read from the given image.
-         * If the block is not allocated read from override chain if present.
-         */
-        rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData,
-                                          uOffset, cbThisRead,
-                                          pIoCtx, &cbThisRead);
-
-        if (rc == VERR_VD_BLOCK_FREE)
-        {
-            while (   pCurrImage->pPrev != NULL
-                   && rc == VERR_VD_BLOCK_FREE)
-            {
-                pCurrImage =  pCurrImage->pPrev;
-                rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData,
-                                                  uOffset, cbThisRead,
-                                                  pIoCtx, &cbThisRead);
-            }
-        }
-
-        /* The task state will be updated on success already, don't do it here!. */
-        if (rc == VERR_VD_BLOCK_FREE)
-        {
-            /* No image in the chain contains the data for the block. */
-            vdIoCtxSet(pIoCtx, '\0', cbThisRead);
-            ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, cbThisRead);
-            rc = VINF_SUCCESS;
-        }
-        else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
-            rc = VINF_SUCCESS;
-        else if (rc == VERR_VD_IOCTX_HALT)
-        {
-            uOffset  += cbThisRead;
-            cbToRead -= cbThisRead;
-            pIoCtx->fFlags |= VDIOCTX_FLAGS_BLOCKED;
-        }
-
-        if (RT_FAILURE(rc))
-            break;
-
-        cbToRead -= cbThisRead;
-        uOffset  += cbThisRead;
-        pCurrImage = pIoCtx->Req.Io.pImageStart; /* Start with the highest image in the chain. */
-    } while (cbToRead != 0 && RT_SUCCESS(rc));
-
-    if (   rc == VERR_VD_NOT_ENOUGH_METADATA
-        || rc == VERR_VD_IOCTX_HALT)
-    {
-        /* Save the current state. */
-        pIoCtx->Req.Io.uOffset    = uOffset;
-        pIoCtx->Req.Io.cbTransfer = cbToRead;
-        pIoCtx->Req.Io.pImageCur  = pCurrImage ? pCurrImage : pIoCtx->Req.Io.pImageStart;
-    }
-
-    return rc;
-}
-
-/**
- * internal: parent image read wrapper for compacting.
- */
-static int vdParentRead(void *pvUser, uint64_t uOffset, void *pvBuf,
-                        size_t cbRead)
-{
-    PVDPARENTSTATEDESC pParentState = (PVDPARENTSTATEDESC)pvUser;
-    return vdReadHelper(pParentState->pDisk, pParentState->pImage, uOffset,
-                        pvBuf, cbRead, false /* fUpdateCache */);
 }
 
@@ -1984,193 +2016,4 @@
 
 /**
- * internal: write a complete block (only used for diff images), taking the
- * remaining data from parent images. This implementation does not optimize
- * anything (except that it tries to read only that portions from parent
- * images that are really needed).
- */
-static int vdWriteHelperStandard(PVBOXHDD pDisk, PVDIMAGE pImage,
-                                 PVDIMAGE pImageParentOverride,
-                                 uint64_t uOffset, size_t cbWrite,
-                                 size_t cbThisWrite, size_t cbPreRead,
-                                 size_t cbPostRead, const void *pvBuf,
-                                 void *pvTmp)
-{
-    int rc = VINF_SUCCESS;
-
-    LogFlowFunc(("pDisk=%p pImage=%p pImageParentOverride=%p uOffset=%llu cbWrite=%zu\n",
-                 pDisk, pImage, pImageParentOverride, uOffset, cbWrite));
-
-    /* Read the data that goes before the write to fill the block. */
-    if (cbPreRead)
-    {
-        /*
-         * Updating the cache doesn't make sense here because
-         * this will be done after the complete block was written.
-         */
-        rc = vdReadHelperEx(pDisk, pImage, pImageParentOverride,
-                            uOffset - cbPreRead, pvTmp, cbPreRead,
-                            true /* fZeroFreeBlocks*/,
-                            false /* fUpdateCache */, 0);
-        if (RT_FAILURE(rc))
-            return rc;
-    }
-
-    /* Copy the data to the right place in the buffer. */
-    memcpy((char *)pvTmp + cbPreRead, pvBuf, cbThisWrite);
-
-    /* Read the data that goes after the write to fill the block. */
-    if (cbPostRead)
-    {
-        /* If we have data to be written, use that instead of reading
-         * data from the image. */
-        size_t cbWriteCopy;
-        if (cbWrite > cbThisWrite)
-            cbWriteCopy = RT_MIN(cbWrite - cbThisWrite, cbPostRead);
-        else
-            cbWriteCopy = 0;
-        /* Figure out how much we cannot read from the image, because
-         * the last block to write might exceed the nominal size of the
-         * image for technical reasons. */
-        size_t cbFill;
-        if (uOffset + cbThisWrite + cbPostRead > pDisk->cbSize)
-            cbFill = uOffset + cbThisWrite + cbPostRead - pDisk->cbSize;
-        else
-            cbFill = 0;
-        /* The rest must be read from the image. */
-        size_t cbReadImage = cbPostRead - cbWriteCopy - cbFill;
-
-        /* Now assemble the remaining data. */
-        if (cbWriteCopy)
-            memcpy((char *)pvTmp + cbPreRead + cbThisWrite,
-                   (char *)pvBuf + cbThisWrite, cbWriteCopy);
-        if (cbReadImage)
-            rc = vdReadHelperEx(pDisk, pImage, pImageParentOverride,
-                                uOffset + cbThisWrite + cbWriteCopy,
-                                (char *)pvTmp + cbPreRead + cbThisWrite + cbWriteCopy,
-                                cbReadImage, true /* fZeroFreeBlocks */,
-                                false /* fUpdateCache */, 0);
-        if (RT_FAILURE(rc))
-            return rc;
-        /* Zero out the remainder of this block. Will never be visible, as this
-         * is beyond the limit of the image. */
-        if (cbFill)
-            memset((char *)pvTmp + cbPreRead + cbThisWrite + cbWriteCopy + cbReadImage,
-                   '\0', cbFill);
-    }
-
-    /* Write the full block to the virtual disk. */
-    RTSGSEG SegmentBuf;
-    RTSGBUF SgBuf;
-    VDIOCTX IoCtx;
-
-    SegmentBuf.pvSeg = pvTmp;
-    SegmentBuf.cbSeg = cbPreRead + cbThisWrite + cbPostRead;
-    RTSgBufInit(&SgBuf, &SegmentBuf, 1);
-    vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_WRITE, 0, 0, NULL,
-                &SgBuf, NULL, NULL, VDIOCTX_FLAGS_SYNC);
-    rc = pImage->Backend->pfnWrite(pImage->pBackendData, uOffset - cbPreRead,
-                                        cbPreRead + cbThisWrite + cbPostRead,
-                                        &IoCtx, NULL, &cbPreRead, &cbPostRead, 0);
-    Assert(rc != VERR_VD_BLOCK_FREE);
-    Assert(cbPreRead == 0);
-    Assert(cbPostRead == 0);
-
-    return rc;
-}
-
-/**
- * internal: write a complete block (only used for diff images), taking the
- * remaining data from parent images. This implementation optimizes out writes
- * that do not change the data relative to the state as of the parent images.
- * All backends which support differential/growing images support this.
- */
-static int vdWriteHelperOptimized(PVBOXHDD pDisk, PVDIMAGE pImage,
-                                  PVDIMAGE pImageParentOverride,
-                                  uint64_t uOffset, size_t cbWrite,
-                                  size_t cbThisWrite, size_t cbPreRead,
-                                  size_t cbPostRead, const void *pvBuf,
-                                  void *pvTmp, unsigned cImagesRead)
-{
-    size_t cbFill = 0;
-    size_t cbWriteCopy = 0;
-    size_t cbReadImage = 0;
-    int rc;
-
-    LogFlowFunc(("pDisk=%p pImage=%p pImageParentOverride=%p uOffset=%llu cbWrite=%zu\n",
-                 pDisk, pImage, pImageParentOverride, uOffset, cbWrite));
-
-    if (cbPostRead)
-    {
-        /* Figure out how much we cannot read from the image, because
-         * the last block to write might exceed the nominal size of the
-         * image for technical reasons. */
-        if (uOffset + cbThisWrite + cbPostRead > pDisk->cbSize)
-            cbFill = uOffset + cbThisWrite + cbPostRead - pDisk->cbSize;
-
-        /* If we have data to be written, use that instead of reading
-         * data from the image. */
-        if (cbWrite > cbThisWrite)
-            cbWriteCopy = RT_MIN(cbWrite - cbThisWrite, cbPostRead);
-
-        /* The rest must be read from the image. */
-        cbReadImage = cbPostRead - cbWriteCopy - cbFill;
-    }
-
-    /* Read the entire data of the block so that we can compare whether it will
-     * be modified by the write or not. */
-    rc = vdReadHelperEx(pDisk, pImage, pImageParentOverride, uOffset - cbPreRead, pvTmp,
-                        cbPreRead + cbThisWrite + cbPostRead - cbFill,
-                        true /* fZeroFreeBlocks */, false /* fUpdateCache */,
-                        cImagesRead);
-    if (RT_FAILURE(rc))
-        return rc;
-
-    /* Check if the write would modify anything in this block. */
-    if (   !memcmp((char *)pvTmp + cbPreRead, pvBuf, cbThisWrite)
-        && (!cbWriteCopy || !memcmp((char *)pvTmp + cbPreRead + cbThisWrite,
-                                    (char *)pvBuf + cbThisWrite, cbWriteCopy)))
-    {
-        /* Block is completely unchanged, so no need to write anything. */
-        return VINF_SUCCESS;
-    }
-
-    /* Copy the data to the right place in the buffer. */
-    memcpy((char *)pvTmp + cbPreRead, pvBuf, cbThisWrite);
-
-    /* Handle the data that goes after the write to fill the block. */
-    if (cbPostRead)
-    {
-        /* Now assemble the remaining data. */
-        if (cbWriteCopy)
-            memcpy((char *)pvTmp + cbPreRead + cbThisWrite,
-                   (char *)pvBuf + cbThisWrite, cbWriteCopy);
-        /* Zero out the remainder of this block. Will never be visible, as this
-         * is beyond the limit of the image. */
-        if (cbFill)
-            memset((char *)pvTmp + cbPreRead + cbThisWrite + cbWriteCopy + cbReadImage,
-                   '\0', cbFill);
-    }
-
-    /* Write the full block to the virtual disk. */
-    RTSGSEG SegmentBuf;
-    RTSGBUF SgBuf;
-    VDIOCTX IoCtx;
-
-    SegmentBuf.pvSeg = pvTmp;
-    SegmentBuf.cbSeg = cbPreRead + cbThisWrite + cbPostRead;
-    RTSgBufInit(&SgBuf, &SegmentBuf, 1);
-    vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_WRITE, 0, 0, NULL,
-                &SgBuf, NULL, NULL, VDIOCTX_FLAGS_SYNC);
-    rc = pImage->Backend->pfnWrite(pImage->pBackendData, uOffset - cbPreRead,
-                                        cbPreRead + cbThisWrite + cbPostRead,
-                                        &IoCtx, NULL, &cbPreRead, &cbPostRead, 0);
-    Assert(rc != VERR_VD_BLOCK_FREE);
-    Assert(cbPreRead == 0);
-    Assert(cbPostRead == 0);
-
-    return rc;
-}
-
-/**
  * internal: write buffer to the image, taking care of block boundaries and
  * write optimizations.
@@ -2181,83 +2024,25 @@
                            bool fUpdateCache, unsigned cImagesRead)
 {
-    int rc;
-    unsigned fWrite;
-    size_t cbThisWrite;
-    size_t cbPreRead, cbPostRead;
-    uint64_t uOffsetCur = uOffset;
-    size_t cbWriteCur = cbWrite;
-    const void *pcvBufCur = pvBuf;
-    RTSGSEG SegmentBuf;
+    uint32_t fFlags = VDIOCTX_FLAGS_SYNC | VDIOCTX_FLAGS_DONT_FREE;
+    RTSGSEG Segment;
     RTSGBUF SgBuf;
     VDIOCTX IoCtx;
 
-    /* Loop until all written. */
-    do
-    {
-        /* Try to write the possibly partial block to the last opened image.
-         * This works when the block is already allocated in this image or
-         * if it is a full-block write (and allocation isn't suppressed below).
-         * For image formats which don't support zero blocks, it's beneficial
-         * to avoid unnecessarily allocating unchanged blocks. This prevents
-         * unwanted expanding of images. VMDK is an example. */
-        cbThisWrite = cbWriteCur;
-        fWrite =   (pImage->uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME)
-                 ? 0 : VD_WRITE_NO_ALLOC;
-
-        SegmentBuf.pvSeg = (void *)pcvBufCur;
-        SegmentBuf.cbSeg = cbWrite;
-        RTSgBufInit(&SgBuf, &SegmentBuf, 1);
-        vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_WRITE, 0, 0, NULL,
-                    &SgBuf, NULL, NULL, VDIOCTX_FLAGS_SYNC);
-        rc = pImage->Backend->pfnWrite(pImage->pBackendData, uOffsetCur, cbThisWrite,
-                                            &IoCtx, &cbThisWrite, &cbPreRead,
-                                            &cbPostRead, fWrite);
-        if (rc == VERR_VD_BLOCK_FREE)
-        {
-            void *pvTmp = RTMemTmpAlloc(cbPreRead + cbThisWrite + cbPostRead);
-            AssertBreakStmt(VALID_PTR(pvTmp), rc = VERR_NO_MEMORY);
-
-            if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME))
-            {
-                /* Optimized write, suppress writing to a so far unallocated
-                 * block if the data is in fact not changed. */
-                rc = vdWriteHelperOptimized(pDisk, pImage, pImageParentOverride,
-                                            uOffsetCur, cbWriteCur,
-                                            cbThisWrite, cbPreRead, cbPostRead,
-                                            pcvBufCur, pvTmp, cImagesRead);
-            }
-            else
-            {
-                /* Normal write, not optimized in any way. The block will
-                 * be written no matter what. This will usually (unless the
-                 * backend has some further optimization enabled) cause the
-                 * block to be allocated. */
-                rc = vdWriteHelperStandard(pDisk, pImage, pImageParentOverride,
-                                           uOffsetCur, cbWriteCur,
-                                           cbThisWrite, cbPreRead, cbPostRead,
-                                           pcvBufCur, pvTmp);
-            }
-            RTMemTmpFree(pvTmp);
-            if (RT_FAILURE(rc))
-                break;
-        }
-
-        cbWriteCur -= cbThisWrite;
-        uOffsetCur += cbThisWrite;
-        pcvBufCur = (char *)pcvBufCur + cbThisWrite;
-    } while (cbWriteCur != 0 && RT_SUCCESS(rc));
-
-#if 0 /** @todo: Soon removed when sync and async version of the write helper are merged. */
-    /* Update the cache on success */
-    if (   RT_SUCCESS(rc)
-        && pDisk->pCache
-        && fUpdateCache)
-        rc = vdCacheWriteHelper(pDisk->pCache, uOffset, pvBuf, cbWrite, NULL);
-
-    if (RT_SUCCESS(rc))
-        rc = vdDiscardSetRangeAllocated(pDisk, uOffset, cbWrite);
-#endif
-
-    return rc;
+    if (fUpdateCache)
+        fFlags |= VDIOCTX_FLAGS_READ_UDATE_CACHE;
+
+    Segment.pvSeg = (void *)pvBuf;
+    Segment.cbSeg = cbWrite;
+    RTSgBufInit(&SgBuf, &Segment, 1);
+    vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_WRITE, uOffset, cbWrite, pImage, &SgBuf,
+                NULL, vdWriteHelperAsync, fFlags);
+
+    IoCtx.Req.Io.pImageParentOverride = pImageParentOverride;
+    IoCtx.Req.Io.cImagesRead = cImagesRead;
+    IoCtx.pIoCtxParent          = NULL;
+    IoCtx.Type.Root.pfnComplete = vdIoCtxSyncComplete;
+    IoCtx.Type.Root.pvUser1     = pDisk;
+    IoCtx.Type.Root.pvUser2     = NULL;
+    return vdIoCtxProcessSync(&IoCtx);
 }
 
@@ -2491,99 +2276,5 @@
 }
 
-/**
- * internal: write a complete block (only used for diff images), taking the
- * remaining data from parent images. This implementation does not optimize
- * anything (except that it tries to read only that portions from parent
- * images that are really needed) - async version.
- */
-static int vdWriteHelperStandardAsync(PVDIOCTX pIoCtx)
-{
-    int rc = VINF_SUCCESS;
-
-#if 0
-
-    /* Read the data that goes before the write to fill the block. */
-    if (cbPreRead)
-    {
-        rc = vdReadHelperAsync(pIoCtxDst);
-        if (RT_FAILURE(rc))
-            return rc;
-    }
-
-    /* Copy the data to the right place in the buffer. */
-    vdIoCtxCopy(pIoCtxDst, pIoCtxSrc, cbThisWrite);
-
-    /* Read the data that goes after the write to fill the block. */
-    if (cbPostRead)
-    {
-        /* If we have data to be written, use that instead of reading
-         * data from the image. */
-        size_t cbWriteCopy;
-        if (cbWrite > cbThisWrite)
-            cbWriteCopy = RT_MIN(cbWrite - cbThisWrite, cbPostRead);
-        else
-            cbWriteCopy = 0;
-        /* Figure out how much we cannot read from the image, because
-         * the last block to write might exceed the nominal size of the
-         * image for technical reasons. */
-        size_t cbFill;
-        if (uOffset + cbThisWrite + cbPostRead > pDisk->cbSize)
-            cbFill = uOffset + cbThisWrite + cbPostRead - pDisk->cbSize;
-        else
-            cbFill = 0;
-        /* The rest must be read from the image. */
-        size_t cbReadImage = cbPostRead - cbWriteCopy - cbFill;
-
-        /* Now assemble the remaining data. */
-        if (cbWriteCopy)
-        {
-            vdIoCtxCopy(pIoCtxDst, pIoCtxSrc, cbWriteCopy);
-            ASMAtomicSubU32(&pIoCtxDst->cbTransferLeft, cbWriteCopy);
-        }
-
-        if (cbReadImage)
-            rc = vdReadHelperAsync(pDisk, pImage, pImageParentOverride, pIoCtxDst,
-                                   uOffset + cbThisWrite + cbWriteCopy,
-                                   cbReadImage);
-        if (RT_FAILURE(rc))
-            return rc;
-        /* Zero out the remainder of this block. Will never be visible, as this
-         * is beyond the limit of the image. */
-        if (cbFill)
-        {
-            vdIoCtxSet(pIoCtxDst, '\0', cbFill);
-            ASMAtomicSubU32(&pIoCtxDst->cbTransferLeft, cbFill);
-        }
-    }
-
-    if (   !pIoCtxDst->cbTransferLeft
-        && !pIoCtxDst->cMetaTransfersPending
-        && ASMAtomicCmpXchgBool(&pIoCtxDst->fComplete, true, false))
-    {
-        /* Write the full block to the virtual disk. */
-        vdIoCtxChildReset(pIoCtxDst);
-        rc = pImage->Backend->pfnWrite(pImage->pBackendData,
-                                            uOffset - cbPreRead,
-                                            cbPreRead + cbThisWrite + cbPostRead,
-                                            pIoCtxDst,
-                                            NULL, &cbPreRead, &cbPostRead, 0);
-        Assert(rc != VERR_VD_BLOCK_FREE);
-        Assert(cbPreRead == 0);
-        Assert(cbPostRead == 0);
-    }
-    else
-    {
-        LogFlow(("cbTransferLeft=%u cMetaTransfersPending=%u fComplete=%RTbool\n",
-                 pIoCtxDst->cbTransferLeft, pIoCtxDst->cMetaTransfersPending,
-                 pIoCtxDst->fComplete));
-        rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
-    }
-
-    return rc;
-#endif
-    return VERR_NOT_IMPLEMENTED;
-}
-
-static int vdWriteHelperOptimizedCommitAsync(PVDIOCTX pIoCtx)
+static int vdWriteHelperCommitAsync(PVDIOCTX pIoCtx)
 {
     int rc             = VINF_SUCCESS;
@@ -2595,7 +2286,7 @@
     LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
     rc = pImage->Backend->pfnWrite(pImage->pBackendData,
-                                        pIoCtx->Req.Io.uOffset - cbPreRead,
-                                        cbPreRead + cbThisWrite + cbPostRead,
-                                        pIoCtx, NULL, &cbPreRead, &cbPostRead, 0);
+                                   pIoCtx->Req.Io.uOffset - cbPreRead,
+                                   cbPreRead + cbThisWrite + cbPostRead,
+                                   pIoCtx, NULL, &cbPreRead, &cbPostRead, 0);
     Assert(rc != VERR_VD_BLOCK_FREE);
     Assert(rc == VERR_VD_NOT_ENOUGH_METADATA || cbPreRead == 0);
@@ -2686,5 +2377,5 @@
     /* Write the full block to the virtual disk. */
     RTSgBufReset(&pIoCtx->Req.Io.SgBuf);
-    pIoCtx->pfnIoCtxTransferNext = vdWriteHelperOptimizedCommitAsync;
+    pIoCtx->pfnIoCtxTransferNext = vdWriteHelperCommitAsync;
 
     return rc;
@@ -2696,4 +2387,6 @@
 
     LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+    pIoCtx->fFlags |= VDIOCTX_FLAGS_ZERO_FREE_BLOCKS;
 
     if (pIoCtx->Req.Io.cbTransferLeft)
@@ -2762,4 +2455,139 @@
     /* Next step */
     pIoCtx->pfnIoCtxTransferNext = vdWriteHelperOptimizedPreReadAsync;
+    return VINF_SUCCESS;
+}
+
+static int vdWriteHelperStandardAssemble(PVDIOCTX pIoCtx)
+{
+    int rc = VINF_SUCCESS;
+    size_t cbPostRead  = pIoCtx->Type.Child.cbPostRead;
+    size_t cbThisWrite = pIoCtx->Type.Child.cbTransferParent;
+    PVDIOCTX pIoCtxParent = pIoCtx->pIoCtxParent;
+
+    LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+    vdIoCtxCopy(pIoCtx, pIoCtxParent, cbThisWrite);
+    if (cbPostRead)
+    {
+        size_t cbFill = pIoCtx->Type.Child.Write.Optimized.cbFill;
+        size_t cbWriteCopy = pIoCtx->Type.Child.Write.Optimized.cbWriteCopy;
+        size_t cbReadImage = pIoCtx->Type.Child.Write.Optimized.cbReadImage;
+
+        /* Now assemble the remaining data. */
+        if (cbWriteCopy)
+        {
+            /*
+             * The S/G buffer of the parent needs to be cloned because
+             * it is not allowed to modify the state.
+             */
+            RTSGBUF SgBufParentTmp;
+
+            RTSgBufClone(&SgBufParentTmp, &pIoCtxParent->Req.Io.SgBuf);
+            RTSgBufCopy(&pIoCtx->Req.Io.SgBuf, &SgBufParentTmp, cbWriteCopy);
+        }
+
+        /* Zero out the remainder of this block. Will never be visible, as this
+         * is beyond the limit of the image. */
+        if (cbFill)
+        {
+            RTSgBufAdvance(&pIoCtx->Req.Io.SgBuf, cbReadImage);
+            vdIoCtxSet(pIoCtx, '\0', cbFill);
+        }
+
+        if (cbReadImage)
+        {
+            /* Read remaining data. */
+        }
+        else
+        {
+            /* Write the full block to the virtual disk. */
+            RTSgBufReset(&pIoCtx->Req.Io.SgBuf);
+            pIoCtx->pfnIoCtxTransferNext = vdWriteHelperCommitAsync;
+        }
+    }
+    else
+    {
+        /* Write the full block to the virtual disk. */
+        RTSgBufReset(&pIoCtx->Req.Io.SgBuf);
+        pIoCtx->pfnIoCtxTransferNext = vdWriteHelperCommitAsync;
+    }
+
+    return rc;
+}
+
+static int vdWriteHelperStandardPreReadAsync(PVDIOCTX pIoCtx)
+{
+    int rc = VINF_SUCCESS;
+
+    LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+    pIoCtx->fFlags |= VDIOCTX_FLAGS_ZERO_FREE_BLOCKS;
+
+    if (pIoCtx->Req.Io.cbTransferLeft)
+        rc = vdReadHelperAsync(pIoCtx);
+
+    if (   RT_SUCCESS(rc)
+        && (   pIoCtx->Req.Io.cbTransferLeft
+            || pIoCtx->cMetaTransfersPending))
+        rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+     else
+        pIoCtx->pfnIoCtxTransferNext = vdWriteHelperStandardAssemble;
+
+    return rc;
+}
+
+static int vdWriteHelperStandardAsync(PVDIOCTX pIoCtx)
+{
+    PVBOXHDD pDisk = pIoCtx->pDisk;
+    uint64_t uOffset   = pIoCtx->Type.Child.uOffsetSaved;
+    size_t cbThisWrite = pIoCtx->Type.Child.cbTransferParent;
+    size_t cbPreRead   = pIoCtx->Type.Child.cbPreRead;
+    size_t cbPostRead  = pIoCtx->Type.Child.cbPostRead;
+    size_t cbWrite     = pIoCtx->Type.Child.cbWriteParent;
+    size_t cbFill = 0;
+    size_t cbWriteCopy = 0;
+    size_t cbReadImage = 0;
+
+    LogFlowFunc(("pIoCtx=%#p\n", pIoCtx));
+
+    AssertPtr(pIoCtx->pIoCtxParent);
+    Assert(!pIoCtx->pIoCtxParent->pIoCtxParent);
+
+    /* Calculate the amount of data to read that goes after the write to fill the block. */
+    if (cbPostRead)
+    {
+        /* If we have data to be written, use that instead of reading
+         * data from the image. */
+        cbWriteCopy;
+        if (cbWrite > cbThisWrite)
+            cbWriteCopy = RT_MIN(cbWrite - cbThisWrite, cbPostRead);
+
+        /* Figure out how much we cannot read from the image, because
+         * the last block to write might exceed the nominal size of the
+         * image for technical reasons. */
+        if (uOffset + cbThisWrite + cbPostRead > pDisk->cbSize)
+            cbFill = uOffset + cbThisWrite + cbPostRead - pDisk->cbSize;
+
+        /* The rest must be read from the image. */
+        cbReadImage = cbPostRead - cbWriteCopy - cbFill;
+    }
+
+    pIoCtx->Type.Child.Write.Optimized.cbFill      = cbFill;
+    pIoCtx->Type.Child.Write.Optimized.cbWriteCopy = cbWriteCopy;
+    pIoCtx->Type.Child.Write.Optimized.cbReadImage = cbReadImage;
+
+    /* Next step */
+    if (cbPreRead)
+    {
+        pIoCtx->pfnIoCtxTransferNext = vdWriteHelperStandardPreReadAsync;
+
+        /* Read the data that goes before the write to fill the block. */
+        pIoCtx->Req.Io.cbTransferLeft = cbPreRead;
+        pIoCtx->Req.Io.cbTransfer     = pIoCtx->Req.Io.cbTransferLeft;
+        pIoCtx->Req.Io.uOffset       -= cbPreRead;
+    }
+    else
+        pIoCtx->pfnIoCtxTransferNext = vdWriteHelperStandardAssemble;
+
     return VINF_SUCCESS;
 }
@@ -9637,5 +9465,5 @@
                                   pfnComplete, pvUser1, pvUser2,
                                   NULL, vdReadHelperAsync,
-                                  VDIOCTX_FLAGS_DEFAULT);
+                                  VDIOCTX_FLAGS_ZERO_FREE_BLOCKS);
         if (!pIoCtx)
         {
