diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:19:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:19:18 +0000 |
commit | 4035b1bfb1e5843a539a8b624d21952b756974d1 (patch) | |
tree | f1e9cd5bf548cbc57ff2fddfb2b4aa9ae95587e2 /src/VBox/Storage/VD.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-upstream.tar.xz virtualbox-upstream.zip |
Adding upstream version 6.1.22-dfsg.upstream/6.1.22-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Storage/VD.cpp')
-rw-r--r-- | src/VBox/Storage/VD.cpp | 10575 |
1 files changed, 10575 insertions, 0 deletions
diff --git a/src/VBox/Storage/VD.cpp b/src/VBox/Storage/VD.cpp new file mode 100644 index 00000000..33c1c7a7 --- /dev/null +++ b/src/VBox/Storage/VD.cpp @@ -0,0 +1,10575 @@ +/* $Id: VD.cpp $ */ +/** @file + * VD - Virtual disk container implementation. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_VD +#include <VBox/vd.h> +#include <VBox/err.h> +#include <VBox/sup.h> +#include <VBox/log.h> + +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/uuid.h> +#include <iprt/file.h> +#include <iprt/string.h> +#include <iprt/asm.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/sg.h> +#include <iprt/semaphore.h> +#include <iprt/vector.h> + +#include "VDInternal.h" + +/** Buffer size used for merging images. */ +#define VD_MERGE_BUFFER_SIZE (16 * _1M) + +/** Maximum number of segments in one I/O task. */ +#define VD_IO_TASK_SEGMENTS_MAX 64 + +/** Threshold after not recently used blocks are removed from the list. */ +#define VD_DISCARD_REMOVE_THRESHOLD (10 * _1M) /** @todo experiment */ + +/** + * VD async I/O interface storage descriptor. + */ +typedef struct VDIIOFALLBACKSTORAGE +{ + /** File handle. */ + RTFILE File; + /** Completion callback. */ + PFNVDCOMPLETED pfnCompleted; + /** Thread for async access. */ + RTTHREAD ThreadAsync; +} VDIIOFALLBACKSTORAGE, *PVDIIOFALLBACKSTORAGE; + +/** + * uModified bit flags. + */ +#define VD_IMAGE_MODIFIED_FLAG RT_BIT(0) +#define VD_IMAGE_MODIFIED_FIRST RT_BIT(1) +#define VD_IMAGE_MODIFIED_DISABLE_UUID_UPDATE RT_BIT(2) + + +# define VD_IS_LOCKED(a_pDisk) \ + do \ + { \ + NOREF(a_pDisk); \ + AssertMsg((a_pDisk)->fLocked, \ + ("Lock not held\n"));\ + } while(0) + +/** + * VBox parent read descriptor, used internally for compaction. + */ +typedef struct VDPARENTSTATEDESC +{ + /** Pointer to disk descriptor. */ + PVDISK pDisk; + /** Pointer to image descriptor. */ + PVDIMAGE pImage; +} VDPARENTSTATEDESC, *PVDPARENTSTATEDESC; + +/** + * Transfer direction. + */ +typedef enum VDIOCTXTXDIR +{ + /** Read */ + VDIOCTXTXDIR_READ = 0, + /** Write */ + VDIOCTXTXDIR_WRITE, + /** Flush */ + VDIOCTXTXDIR_FLUSH, + /** Discard */ + VDIOCTXTXDIR_DISCARD, + /** 32bit hack */ + VDIOCTXTXDIR_32BIT_HACK = 0x7fffffff +} VDIOCTXTXDIR, *PVDIOCTXTXDIR; + +/** Transfer function */ +typedef DECLCALLBACK(int) FNVDIOCTXTRANSFER (PVDIOCTX pIoCtx); +/** Pointer to a transfer function. */ +typedef FNVDIOCTXTRANSFER *PFNVDIOCTXTRANSFER; + +/** + * I/O context + */ +typedef struct VDIOCTX +{ + /** Pointer to the next I/O context. */ + struct VDIOCTX * volatile pIoCtxNext; + /** Disk this is request is for. */ + PVDISK pDisk; + /** Return code. */ + int rcReq; + /** Various flags for the I/O context. */ + uint32_t fFlags; + /** Number of data transfers currently pending. */ + volatile uint32_t cDataTransfersPending; + /** How many meta data transfers are pending. */ + volatile uint32_t cMetaTransfersPending; + /** Flag whether the request finished */ + volatile bool fComplete; + /** Temporary allocated memory which is freed + * when the context completes. */ + void *pvAllocation; + /** Transfer function. */ + PFNVDIOCTXTRANSFER pfnIoCtxTransfer; + /** Next transfer part after the current one completed. */ + PFNVDIOCTXTRANSFER pfnIoCtxTransferNext; + /** Transfer direction */ + VDIOCTXTXDIR enmTxDir; + /** Request type dependent data. */ + union + { + /** I/O request (read/write). */ + struct + { + /** Number of bytes left until this context completes. */ + volatile uint32_t cbTransferLeft; + /** Current offset */ + volatile uint64_t uOffset; + /** Number of bytes to transfer */ + volatile size_t cbTransfer; + /** Current image in the chain. */ + PVDIMAGE pImageCur; + /** Start image to read from. pImageCur is reset to this + * value after it reached the first image in the chain. */ + PVDIMAGE pImageStart; + /** S/G buffer */ + RTSGBUF SgBuf; + /** 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; + /** Original offset of the transfer - required for filtering read requests. */ + uint64_t uOffsetXferOrig; + /** Original size of the transfer - required for fitlering read requests. */ + size_t cbXferOrig; + } Io; + /** Discard requests. */ + struct + { + /** Pointer to the range descriptor array. */ + PCRTRANGE paRanges; + /** Number of ranges in the array. */ + unsigned cRanges; + /** Range descriptor index which is processed. */ + unsigned idxRange; + /** Start offset to discard currently. */ + uint64_t offCur; + /** How many bytes left to discard in the current range. */ + size_t cbDiscardLeft; + /** How many bytes to discard in the current block (<= cbDiscardLeft). */ + size_t cbThisDiscard; + /** Discard block handled currently. */ + PVDDISCARDBLOCK pBlock; + } Discard; + } Req; + /** Parent I/O context if any. Sets the type of the context (root/child) */ + PVDIOCTX pIoCtxParent; + /** Type dependent data (root/child) */ + union + { + /** Root data */ + struct + { + /** Completion callback */ + PFNVDASYNCTRANSFERCOMPLETE pfnComplete; + /** User argument 1 passed on completion. */ + void *pvUser1; + /** User argument 2 passed on completion. */ + void *pvUser2; + } Root; + /** Child data */ + struct + { + /** Saved start offset */ + uint64_t uOffsetSaved; + /** Saved transfer size */ + size_t cbTransferLeftSaved; + /** Number of bytes transferred from the parent if this context completes. */ + size_t cbTransferParent; + /** Number of bytes to pre read */ + size_t cbPreRead; + /** Number of bytes to post read. */ + size_t cbPostRead; + /** Number of bytes to write left in the parent. */ + size_t cbWriteParent; + /** Write type dependent data. */ + union + { + /** Optimized */ + struct + { + /** Bytes to fill to satisfy the block size. Not part of the virtual disk. */ + size_t cbFill; + /** Bytes to copy instead of reading from the parent */ + size_t cbWriteCopy; + /** Bytes to read from the image. */ + size_t cbReadImage; + } Optimized; + } Write; + } Child; + } Type; +} VDIOCTX; + +/** Default flags for an I/O context, i.e. unblocked and async. */ +#define VDIOCTX_FLAGS_DEFAULT (0) +/** Flag whether the context is blocked. */ +#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) +/** Flag whether the read should update the cache. */ +#define VDIOCTX_FLAGS_READ_UPDATE_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) +/** Don't set the modified flag for this I/O context when writing. */ +#define VDIOCTX_FLAGS_DONT_SET_MODIFIED_FLAG RT_BIT_32(5) +/** The write filter was applied already and shouldn't be applied a second time. + * Used at the beginning of vdWriteHelperAsync() because it might be called + * multiple times. + */ +#define VDIOCTX_FLAGS_WRITE_FILTER_APPLIED RT_BIT_32(6) + +/** NIL I/O context pointer value. */ +#define NIL_VDIOCTX ((PVDIOCTX)0) + +/** + * List node for deferred I/O contexts. + */ +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. + */ +typedef struct VDIOTASK +{ + /** Next I/O task waiting in the list. */ + struct VDIOTASK * volatile pNext; + /** Storage this task belongs to. */ + PVDIOSTORAGE pIoStorage; + /** Optional completion callback. */ + PFNVDXFERCOMPLETED pfnComplete; + /** Opaque user data. */ + void *pvUser; + /** Completion status code for the task. */ + int rcReq; + /** Flag whether this is a meta data transfer. */ + bool fMeta; + /** Type dependent data. */ + union + { + /** User data transfer. */ + struct + { + /** Number of bytes this task transferred. */ + uint32_t cbTransfer; + /** Pointer to the I/O context the task belongs. */ + PVDIOCTX pIoCtx; + } User; + /** Meta data transfer. */ + struct + { + /** Meta transfer this task is for. */ + PVDMETAXFER pMetaXfer; + } Meta; + } Type; +} VDIOTASK; + +/** + * Storage handle. + */ +typedef struct VDIOSTORAGE +{ + /** Image I/O state this storage handle belongs to. */ + PVDIO pVDIo; + /** AVL tree for pending async metadata transfers. */ + PAVLRFOFFTREE pTreeMetaXfers; + /** Storage handle */ + void *pStorage; +} 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; + /** Shadow buffer which is used in case a write is still active and other + * writes update the shadow buffer. */ + uint8_t *pbDataShw; + /** List of I/O contexts updating the shadow buffer while there is a write + * in progress. */ + RTLISTNODE ListIoCtxShwWrites; + /** 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)) + +/** Forward declaration of the async discard helper. */ +static DECLCALLBACK(int) vdDiscardHelperAsync(PVDIOCTX pIoCtx); +static DECLCALLBACK(int) vdWriteHelperAsync(PVDIOCTX pIoCtx); +static void vdDiskProcessBlockedIoCtx(PVDISK pDisk); +static int vdDiskUnlock(PVDISK pDisk, PVDIOCTX pIoCtxRc); +static DECLCALLBACK(void) vdIoCtxSyncComplete(void *pvUser1, void *pvUser2, int rcReq); + +/** + * internal: issue error message. + */ +static int vdError(PVDISK pDisk, int rc, RT_SRC_POS_DECL, + const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + if (pDisk->pInterfaceError) + pDisk->pInterfaceError->pfnError(pDisk->pInterfaceError->Core.pvUser, rc, RT_SRC_POS_ARGS, pszFormat, va); + va_end(va); + return rc; +} + +/** + * internal: thread synchronization, start read. + */ +DECLINLINE(int) vdThreadStartRead(PVDISK pDisk) +{ + int rc = VINF_SUCCESS; + if (RT_UNLIKELY(pDisk->pInterfaceThreadSync)) + rc = pDisk->pInterfaceThreadSync->pfnStartRead(pDisk->pInterfaceThreadSync->Core.pvUser); + return rc; +} + +/** + * internal: thread synchronization, finish read. + */ +DECLINLINE(int) vdThreadFinishRead(PVDISK pDisk) +{ + int rc = VINF_SUCCESS; + if (RT_UNLIKELY(pDisk->pInterfaceThreadSync)) + rc = pDisk->pInterfaceThreadSync->pfnFinishRead(pDisk->pInterfaceThreadSync->Core.pvUser); + return rc; +} + +/** + * internal: thread synchronization, start write. + */ +DECLINLINE(int) vdThreadStartWrite(PVDISK pDisk) +{ + int rc = VINF_SUCCESS; + if (RT_UNLIKELY(pDisk->pInterfaceThreadSync)) + rc = pDisk->pInterfaceThreadSync->pfnStartWrite(pDisk->pInterfaceThreadSync->Core.pvUser); + return rc; +} + +/** + * internal: thread synchronization, finish write. + */ +DECLINLINE(int) vdThreadFinishWrite(PVDISK pDisk) +{ + int rc = VINF_SUCCESS; + if (RT_UNLIKELY(pDisk->pInterfaceThreadSync)) + rc = pDisk->pInterfaceThreadSync->pfnFinishWrite(pDisk->pInterfaceThreadSync->Core.pvUser); + return rc; +} + +/** + * internal: add image structure to the end of images list. + */ +static void vdAddImageToList(PVDISK pDisk, PVDIMAGE pImage) +{ + pImage->pPrev = NULL; + pImage->pNext = NULL; + + if (pDisk->pBase) + { + Assert(pDisk->cImages > 0); + pImage->pPrev = pDisk->pLast; + pDisk->pLast->pNext = pImage; + pDisk->pLast = pImage; + } + else + { + Assert(pDisk->cImages == 0); + pDisk->pBase = pImage; + pDisk->pLast = pImage; + } + + pDisk->cImages++; +} + +/** + * internal: remove image structure from the images list. + */ +static void vdRemoveImageFromList(PVDISK pDisk, PVDIMAGE pImage) +{ + Assert(pDisk->cImages > 0); + + if (pImage->pPrev) + pImage->pPrev->pNext = pImage->pNext; + else + pDisk->pBase = pImage->pNext; + + if (pImage->pNext) + pImage->pNext->pPrev = pImage->pPrev; + else + pDisk->pLast = pImage->pPrev; + + pImage->pPrev = NULL; + pImage->pNext = NULL; + + pDisk->cImages--; +} + +/** + * Release a referene to the filter decrementing the counter and destroying the filter + * when the counter reaches zero. + * + * @returns The new reference count. + * @param pFilter The filter to release. + */ +static uint32_t vdFilterRelease(PVDFILTER pFilter) +{ + uint32_t cRefs = ASMAtomicDecU32(&pFilter->cRefs); + if (!cRefs) + { + pFilter->pBackend->pfnDestroy(pFilter->pvBackendData); + RTMemFree(pFilter); + } + + return cRefs; +} + +/** + * Increments the reference counter of the given filter. + * + * @return The new reference count. + * @param pFilter The filter. + */ +static uint32_t vdFilterRetain(PVDFILTER pFilter) +{ + return ASMAtomicIncU32(&pFilter->cRefs); +} + +/** + * internal: find image by index into the images list. + */ +static PVDIMAGE vdGetImageByNumber(PVDISK pDisk, unsigned nImage) +{ + PVDIMAGE pImage = pDisk->pBase; + if (nImage == VD_LAST_IMAGE) + return pDisk->pLast; + while (pImage && nImage) + { + pImage = pImage->pNext; + nImage--; + } + return pImage; +} + +/** + * Creates a new region list from the given one converting to match the flags if necessary. + * + * @returns VBox status code. + * @param pRegionList The region list to convert from. + * @param fFlags The flags for the new region list. + * @param ppRegionList Where to store the new region list on success. + */ +static int vdRegionListConv(PCVDREGIONLIST pRegionList, uint32_t fFlags, PPVDREGIONLIST ppRegionList) +{ + int rc = VINF_SUCCESS; + PVDREGIONLIST pRegionListNew = (PVDREGIONLIST)RTMemDup(pRegionList, + RT_UOFFSETOF_DYN(VDREGIONLIST, aRegions[pRegionList->cRegions])); + if (RT_LIKELY(pRegionListNew)) + { + /* Do we have to convert anything? */ + if (pRegionList->fFlags != fFlags) + { + uint64_t offRegionNext = 0; + + pRegionListNew->fFlags = fFlags; + for (unsigned i = 0; i < pRegionListNew->cRegions; i++) + { + PVDREGIONDESC pRegion = &pRegionListNew->aRegions[i]; + + if ( (fFlags & VD_REGION_LIST_F_LOC_SIZE_BLOCKS) + && !(pRegionList->fFlags & VD_REGION_LIST_F_LOC_SIZE_BLOCKS)) + { + Assert(!(pRegion->cRegionBlocksOrBytes % pRegion->cbBlock)); + + /* Convert from bytes to logical blocks. */ + pRegion->offRegion = offRegionNext; + pRegion->cRegionBlocksOrBytes = pRegion->cRegionBlocksOrBytes / pRegion->cbBlock; + offRegionNext += pRegion->cRegionBlocksOrBytes; + } + else + { + /* Convert from logical blocks to bytes. */ + pRegion->offRegion = offRegionNext; + pRegion->cRegionBlocksOrBytes = pRegion->cRegionBlocksOrBytes * pRegion->cbBlock; + offRegionNext += pRegion->cRegionBlocksOrBytes; + } + } + } + + *ppRegionList = pRegionListNew; + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + +/** + * Returns the virtual size of the image in bytes. + * + * @returns Size of the given image in bytes. + * @param pImage The image to get the size from. + */ +static uint64_t vdImageGetSize(PVDIMAGE pImage) +{ + uint64_t cbImage = 0; + + if (pImage->cbImage == VD_IMAGE_SIZE_UNINITIALIZED) + { + PCVDREGIONLIST pRegionList = NULL; + int rc = pImage->Backend->pfnQueryRegions(pImage->pBackendData, &pRegionList); + if (RT_SUCCESS(rc)) + { + if (pRegionList->fFlags & VD_REGION_LIST_F_LOC_SIZE_BLOCKS) + { + PVDREGIONLIST pRegionListConv = NULL; + rc = vdRegionListConv(pRegionList, 0, &pRegionListConv); + if (RT_SUCCESS(rc)) + { + for (uint32_t i = 0; i < pRegionListConv->cRegions; i++) + cbImage += pRegionListConv->aRegions[i].cRegionBlocksOrBytes; + + VDRegionListFree(pRegionListConv); + } + } + else + for (uint32_t i = 0; i < pRegionList->cRegions; i++) + cbImage += pRegionList->aRegions[i].cRegionBlocksOrBytes; + + AssertPtr(pImage->Backend->pfnRegionListRelease); + pImage->Backend->pfnRegionListRelease(pImage->pBackendData, pRegionList); + pImage->cbImage = cbImage; /* Cache the value. */ + } + } + else + cbImage = pImage->cbImage; + + return cbImage; +} + +/** + * Applies the filter chain to the given write request. + * + * @returns VBox status code. + * @param pDisk The HDD container. + * @param uOffset The start offset of the write. + * @param cbWrite Number of bytes to write. + * @param pIoCtx The I/O context associated with the request. + */ +static int vdFilterChainApplyWrite(PVDISK pDisk, uint64_t uOffset, size_t cbWrite, + PVDIOCTX pIoCtx) +{ + int rc = VINF_SUCCESS; + + VD_IS_LOCKED(pDisk); + + PVDFILTER pFilter; + RTListForEach(&pDisk->ListFilterChainWrite, pFilter, VDFILTER, ListNodeChainWrite) + { + rc = pFilter->pBackend->pfnFilterWrite(pFilter->pvBackendData, uOffset, cbWrite, pIoCtx); + if (RT_FAILURE(rc)) + break; + /* Reset S/G buffer for the next filter. */ + RTSgBufReset(&pIoCtx->Req.Io.SgBuf); + } + + return rc; +} + +/** + * Applies the filter chain to the given read request. + * + * @returns VBox status code. + * @param pDisk The HDD container. + * @param uOffset The start offset of the read. + * @param cbRead Number of bytes read. + * @param pIoCtx The I/O context associated with the request. + */ +static int vdFilterChainApplyRead(PVDISK pDisk, uint64_t uOffset, size_t cbRead, + PVDIOCTX pIoCtx) +{ + int rc = VINF_SUCCESS; + + VD_IS_LOCKED(pDisk); + + /* Reset buffer before starting. */ + RTSgBufReset(&pIoCtx->Req.Io.SgBuf); + + PVDFILTER pFilter; + RTListForEach(&pDisk->ListFilterChainRead, pFilter, VDFILTER, ListNodeChainRead) + { + rc = pFilter->pBackend->pfnFilterRead(pFilter->pvBackendData, uOffset, cbRead, pIoCtx); + if (RT_FAILURE(rc)) + break; + /* Reset S/G buffer for the next filter. */ + RTSgBufReset(&pIoCtx->Req.Io.SgBuf); + } + + return rc; +} + +DECLINLINE(void) vdIoCtxRootComplete(PVDISK pDisk, PVDIOCTX pIoCtx) +{ + if ( RT_SUCCESS(pIoCtx->rcReq) + && pIoCtx->enmTxDir == VDIOCTXTXDIR_READ) + pIoCtx->rcReq = vdFilterChainApplyRead(pDisk, pIoCtx->Req.Io.uOffsetXferOrig, + pIoCtx->Req.Io.cbXferOrig, pIoCtx); + + pIoCtx->Type.Root.pfnComplete(pIoCtx->Type.Root.pvUser1, + pIoCtx->Type.Root.pvUser2, + pIoCtx->rcReq); +} + +/** + * Initialize the structure members of a given I/O context. + */ +DECLINLINE(void) vdIoCtxInit(PVDIOCTX pIoCtx, PVDISK pDisk, VDIOCTXTXDIR enmTxDir, + uint64_t uOffset, size_t cbTransfer, PVDIMAGE pImageStart, + PCRTSGBUF pcSgBuf, void *pvAllocation, + PFNVDIOCTXTRANSFER pfnIoCtxTransfer, uint32_t fFlags) +{ + pIoCtx->pDisk = pDisk; + pIoCtx->enmTxDir = enmTxDir; + pIoCtx->Req.Io.cbTransferLeft = (uint32_t)cbTransfer; Assert((uint32_t)cbTransfer == cbTransfer); + pIoCtx->Req.Io.uOffset = uOffset; + pIoCtx->Req.Io.cbTransfer = cbTransfer; + pIoCtx->Req.Io.pImageStart = pImageStart; + pIoCtx->Req.Io.pImageCur = pImageStart; + pIoCtx->Req.Io.cbBufClear = 0; + pIoCtx->Req.Io.pImageParentOverride = NULL; + pIoCtx->Req.Io.uOffsetXferOrig = uOffset; + pIoCtx->Req.Io.cbXferOrig = cbTransfer; + 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->pIoCtxParent = NULL; + + /* There is no S/G list for a flush request. */ + if ( enmTxDir != VDIOCTXTXDIR_FLUSH + && enmTxDir != VDIOCTXTXDIR_DISCARD) + RTSgBufClone(&pIoCtx->Req.Io.SgBuf, pcSgBuf); + else + memset(&pIoCtx->Req.Io.SgBuf, 0, sizeof(RTSGBUF)); +} + +/** + * Internal: Tries to read the desired range from the given cache. + * + * @returns VBox status code. + * @retval VERR_VD_BLOCK_FREE if the block is not in the cache. + * pcbRead will be set to the number of bytes not in the cache. + * Everything thereafter might be in the cache. + * @param pCache The cache to read from. + * @param uOffset Offset of the virtual disk to read. + * @param cbRead How much to read. + * @param pIoCtx The I/O context to read into. + * @param pcbRead Where to store the number of bytes actually read. + * On success this indicates the number of bytes read from the cache. + * If VERR_VD_BLOCK_FREE is returned this gives the number of bytes + * which are not in the cache. + * In both cases everything beyond this value + * might or might not be in the cache. + */ +static int vdCacheReadHelper(PVDCACHE pCache, uint64_t uOffset, + size_t cbRead, PVDIOCTX pIoCtx, size_t *pcbRead) +{ + int rc = VINF_SUCCESS; + + LogFlowFunc(("pCache=%#p uOffset=%llu pIoCtx=%p cbRead=%zu pcbRead=%#p\n", + pCache, uOffset, pIoCtx, cbRead, pcbRead)); + + AssertPtr(pCache); + AssertPtr(pcbRead); + + rc = pCache->Backend->pfnRead(pCache->pBackendData, uOffset, cbRead, + pIoCtx, pcbRead); + + LogFlowFunc(("returns rc=%Rrc pcbRead=%zu\n", rc, *pcbRead)); + return rc; +} + +/** + * Internal: Writes data for the given block into the cache. + * + * @returns VBox status code. + * @param pCache The cache to write to. + * @param uOffset Offset of the virtual disk to write to the cache. + * @param cbWrite How much to write. + * @param pIoCtx The I/O context to write from. + * @param pcbWritten How much data could be written, optional. + */ +static int vdCacheWriteHelper(PVDCACHE pCache, uint64_t uOffset, size_t cbWrite, + PVDIOCTX pIoCtx, size_t *pcbWritten) +{ + int rc = VINF_SUCCESS; + + LogFlowFunc(("pCache=%#p uOffset=%llu pIoCtx=%p cbWrite=%zu pcbWritten=%#p\n", + pCache, uOffset, pIoCtx, cbWrite, pcbWritten)); + + AssertPtr(pCache); + AssertPtr(pIoCtx); + Assert(cbWrite > 0); + + if (pcbWritten) + rc = pCache->Backend->pfnWrite(pCache->pBackendData, uOffset, cbWrite, + pIoCtx, pcbWritten); + else + { + size_t cbWritten = 0; + + do + { + rc = pCache->Backend->pfnWrite(pCache->pBackendData, uOffset, cbWrite, + pIoCtx, &cbWritten); + uOffset += cbWritten; + cbWrite -= cbWritten; + } while ( cbWrite + && ( RT_SUCCESS(rc) + || rc == VERR_VD_ASYNC_IO_IN_PROGRESS)); + } + + LogFlowFunc(("returns rc=%Rrc pcbWritten=%zu\n", + rc, pcbWritten ? *pcbWritten : cbWrite)); + return rc; +} + +/** + * 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(PVDISK 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 = (uint32_t)(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); NOREF(pBlockRemove); + 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(PVDISK 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(PVDISK 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 + (int32_t)(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(PVDISK 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(PVDISK 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(void) vdIoCtxDiscardInit(PVDIOCTX pIoCtx, PVDISK pDisk, PCRTRANGE paRanges, + unsigned cRanges, PFNVDASYNCTRANSFERCOMPLETE pfnComplete, + void *pvUser1, void *pvUser2, void *pvAllocation, + PFNVDIOCTXTRANSFER pfnIoCtxTransfer, uint32_t fFlags) +{ + 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; +} + +DECLINLINE(PVDIOCTX) vdIoCtxDiscardAlloc(PVDISK 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)) + { + vdIoCtxDiscardInit(pIoCtx, pDisk, paRanges, cRanges, pfnComplete, pvUser1, + pvUser2, pvAllocation, pfnIoCtxTransfer, fFlags); + } + + LogFlow(("Allocated discard I/O context %#p\n", pIoCtx)); + return pIoCtx; +} + +DECLINLINE(PVDIOCTX) vdIoCtxChildAlloc(PVDISK 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, pIoCtxParent->fFlags & ~VDIOCTX_FLAGS_DONT_FREE); + + 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(PVDISK pDisk, PVDIOCTX pIoCtx) +{ + Log(("Freeing I/O context %#p\n", pIoCtx)); + + if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_DONT_FREE)) + { + if (pIoCtx->pvAllocation) + RTMemFree(pIoCtx->pvAllocation); +#ifdef DEBUG + memset(&pIoCtx->pDisk, 0xff, sizeof(void *)); +#endif + RTMemCacheFree(pDisk->hMemCacheIoCtx, pIoCtx); + } +} + +DECLINLINE(void) vdIoTaskFree(PVDISK pDisk, PVDIOTASK pIoTask) +{ +#ifdef DEBUG + memset(pIoTask, 0xff, sizeof(VDIOTASK)); +#endif + 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 = (uint32_t)pIoCtx->Type.Child.cbTransferLeftSaved; + Assert((uint32_t)pIoCtx->Type.Child.cbTransferLeftSaved == pIoCtx->Type.Child.cbTransferLeftSaved); +} + +DECLINLINE(PVDMETAXFER) vdMetaXferAlloc(PVDIOSTORAGE pIoStorage, uint64_t uOffset, size_t cb) +{ + PVDMETAXFER pMetaXfer = (PVDMETAXFER)RTMemAlloc(RT_UOFFSETOF_DYN(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; + pMetaXfer->pbDataShw = NULL; + RTListInit(&pMetaXfer->ListIoCtxWaiting); + RTListInit(&pMetaXfer->ListIoCtxShwWrites); + } + 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(PVDISK pDisk, PVDIOCTX pIoCtx) +{ + LogFlowFunc(("Deferring I/O context 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); +} + +#if 0 /* unused */ +static int vdIoCtxCmp(PVDIOCTX pIoCtx1, PVDIOCTX pIoCtx2, size_t cbData) +{ + return RTSgBufCmp(&pIoCtx1->Req.Io.SgBuf, &pIoCtx2->Req.Io.SgBuf, cbData); +} +#endif + +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); +} + +/** + * Returns whether the given I/O context has completed. + * + * @returns Flag whether the I/O context is complete. + * @param pIoCtx The I/O context to check. + */ +DECLINLINE(bool) vdIoCtxIsComplete(PVDIOCTX pIoCtx) +{ + if ( !pIoCtx->cMetaTransfersPending + && !pIoCtx->cDataTransfersPending + && !pIoCtx->pfnIoCtxTransfer) + return true; + + /* + * 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) + return true; + + return false; +} + +/** + * Returns whether the given I/O context is blocked due to a metadata transfer + * or because the backend blocked it. + * + * @returns Flag whether the I/O context is blocked. + * @param pIoCtx The I/O context to check. + */ +DECLINLINE(bool) vdIoCtxIsBlocked(PVDIOCTX pIoCtx) +{ + /* Don't change anything if there is a metadata transfer pending or we are blocked. */ + if ( pIoCtx->cMetaTransfersPending + || (pIoCtx->fFlags & VDIOCTX_FLAGS_BLOCKED)) + return true; + + return false; +} + +/** + * 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 (!vdIoCtxIsComplete(pIoCtx)) + { + if (!vdIoCtxIsBlocked(pIoCtx)) + { + 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 + && !(pIoCtx->fFlags & VDIOCTX_FLAGS_BLOCKED)) + 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; + } + } + else + rc = VERR_VD_ASYNC_IO_IN_PROGRESS; + } + else + rc = VINF_VD_ASYNC_IO_FINISHED; + + 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, only valid if pIoCtxRc is not NULL, treat as void + * function otherwise. + * @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(PVDISK pDisk, PVDIOCTX pIoCtxRc) +{ + int rc = VERR_VD_ASYNC_IO_IN_PROGRESS; + + 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) + { + if ( rcTmp == VINF_VD_ASYNC_IO_FINISHED + && RT_SUCCESS(pTmp->rcReq) + && pTmp->enmTxDir == VDIOCTXTXDIR_READ) + { + int rc2 = vdFilterChainApplyRead(pDisk, pTmp->Req.Io.uOffsetXferOrig, + pTmp->Req.Io.cbXferOrig, pTmp); + if (RT_FAILURE(rc2)) + rcTmp = rc2; + } + + /* The given I/O context was processed, pass the return code to the caller. */ + if ( rcTmp == VINF_VD_ASYNC_IO_FINISHED + && (pTmp->fFlags & VDIOCTX_FLAGS_SYNC)) + rc = pTmp->rcReq; + else + 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); + + bool fFreeCtx = RT_BOOL(!(pTmp->fFlags & VDIOCTX_FLAGS_DONT_FREE)); + vdIoCtxRootComplete(pDisk, pTmp); + + if (fFreeCtx) + 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(PVDISK 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); + + bool fFreeCtx = RT_BOOL(!(pTmp->fFlags & VDIOCTX_FLAGS_DONT_FREE)); + vdIoCtxRootComplete(pDisk, pTmp); + if (fFreeCtx) + 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; + PVDISK pDisk = pIoCtx->pDisk; + + Log(("Defer 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. + * @param hEventComplete Event sempahore to wait on for completion. + */ +static int vdIoCtxProcessSync(PVDIOCTX pIoCtx, RTSEMEVENT hEventComplete) +{ + int rc = VINF_SUCCESS; + PVDISK pDisk = pIoCtx->pDisk; + + LogFlowFunc(("pIoCtx=%p\n", pIoCtx)); + + AssertMsg(pIoCtx->fFlags & (VDIOCTX_FLAGS_SYNC | VDIOCTX_FLAGS_DONT_FREE), + ("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(hEventComplete, RT_INDEFINITE_WAIT); + AssertRC(rc); + } + + rc = pIoCtx->rcReq; + vdIoCtxFree(pDisk, pIoCtx); + + return rc; +} + +DECLINLINE(bool) vdIoCtxIsDiskLockOwner(PVDISK pDisk, PVDIOCTX pIoCtx) +{ + return pDisk->pIoCtxLockOwner == pIoCtx; +} + +static int vdIoCtxLockDisk(PVDISK 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(PVDISK pDisk, PVDIOCTX pIoCtx, bool fProcessBlockedReqs) +{ + RT_NOREF1(pIoCtx); + 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(PVDISK pDisk, PVDIMAGE pImage, PVDIMAGE pImageParentOverride, + uint64_t uOffset, size_t cbRead, PVDIOCTX pIoCtx, size_t *pcbThisRead) +{ + RT_NOREF1(pDisk); + int rc = VINF_SUCCESS; + size_t cbThisRead = cbRead; + + AssertPtr(pcbThisRead); + + *pcbThisRead = 0; + + /* + * 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, pIoCtx, + &cbThisRead); + + if (rc == VERR_VD_BLOCK_FREE) + { + 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, pIoCtx, + &cbThisRead); + } + } + + if (RT_SUCCESS(rc) || rc == VERR_VD_BLOCK_FREE) + *pcbThisRead = cbThisRead; + + return rc; +} + +/** + * internal: read the specified amount of data in whatever blocks the backend + * will give us - async version. + */ +static DECLCALLBACK(int) vdReadHelperAsync(PVDIOCTX pIoCtx) +{ + int rc; + PVDISK 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; + + /* + * Check whether there is a full block write in progress which was not allocated. + * Defer I/O if the range interferes but only if it does not belong to the + * write doing the allocation. + */ + if ( pDisk->pIoCtxLockOwner != NIL_VDIOCTX + && uOffset >= pDisk->uOffsetStartLocked + && uOffset < pDisk->uOffsetEndLocked + && ( !pIoCtx->pIoCtxParent + || pIoCtx->pIoCtxParent != pDisk->pIoCtxLockOwner)) + { + Log(("Interferring read while allocating a new block => deferring read\n")); + vdIoCtxDefer(pDisk, pIoCtx); + return VERR_VD_ASYNC_IO_IN_PROGRESS; + } + + /* 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_UPDATE_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, (uint32_t)cbThisRead); Assert(cbThisRead == (uint32_t)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) + 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_IOCTX_HALT) + { + uOffset += cbThisRead; + cbToRead -= cbThisRead; + pIoCtx->fFlags |= VDIOCTX_FLAGS_BLOCKED; + } + else if ( RT_SUCCESS(rc) + || rc == VERR_VD_ASYNC_IO_IN_PROGRESS) + { + /* First not free block, fill the space before with 0. */ + if ( pIoCtx->Req.Io.cbBufClear + && !(pIoCtx->fFlags & VDIOCTX_FLAGS_ZERO_FREE_BLOCKS)) + { + RTSGBUF SgBuf; + RTSgBufClone(&SgBuf, &pIoCtx->Req.Io.SgBuf); + RTSgBufReset(&SgBuf); + RTSgBufSet(&SgBuf, 0, pIoCtx->Req.Io.cbBufClear); + pIoCtx->Req.Io.cbBufClear = 0; + pIoCtx->fFlags |= VDIOCTX_FLAGS_ZERO_FREE_BLOCKS; + } + rc = VINF_SUCCESS; + } + + 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)) + ? VERR_VD_BLOCK_FREE + : rc; +} + +/** + * internal: parent image read wrapper for compacting. + */ +static DECLCALLBACK(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 | VDIOCTX_FLAGS_ZERO_FREE_BLOCKS); + int rc = vdReadHelperAsync(&IoCtx); + ASMAtomicXchgBool(&pParentState->pDisk->fLocked, false); + return rc; +} + +/** + * Extended version of vdReadHelper(), implementing certain optimizations + * for image cloning. + * + * @returns VBox status code. + * @param pDisk The disk to read from. + * @param pImage The image to start reading from. + * @param pImageParentOverride The parent image to read from + * if the starting image returns a free block. + * If NULL is passed the real parent of the image + * in the chain is used. + * @param uOffset Offset in the disk to start reading from. + * @param pvBuf Where to store the read data. + * @param cbRead How much to read. + * @param fZeroFreeBlocks Flag whether free blocks should be zeroed. + * If false and no image has data for sepcified + * range VERR_VD_BLOCK_FREE is returned. + * Note that unallocated blocks are still zeroed + * if at least one image has valid data for a part + * of the range. + * @param fUpdateCache Flag whether to update the attached cache if + * available. + * @param cImagesRead Number of images in the chain to read until + * the read is cut off. A value of 0 disables the cut off. + */ +static int vdReadHelperEx(PVDISK pDisk, PVDIMAGE pImage, PVDIMAGE pImageParentOverride, + uint64_t uOffset, void *pvBuf, size_t cbRead, + bool fZeroFreeBlocks, bool fUpdateCache, unsigned cImagesRead) +{ + int rc = VINF_SUCCESS; + uint32_t fFlags = VDIOCTX_FLAGS_SYNC | VDIOCTX_FLAGS_DONT_FREE; + RTSGSEG Segment; + RTSGBUF SgBuf; + VDIOCTX IoCtx; + RTSEMEVENT hEventComplete = NIL_RTSEMEVENT; + + rc = RTSemEventCreate(&hEventComplete); + if (RT_FAILURE(rc)) + return rc; + + if (fZeroFreeBlocks) + fFlags |= VDIOCTX_FLAGS_ZERO_FREE_BLOCKS; + if (fUpdateCache) + fFlags |= VDIOCTX_FLAGS_READ_UPDATE_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 = hEventComplete; + rc = vdIoCtxProcessSync(&IoCtx, hEventComplete); + RTSemEventDestroy(hEventComplete); + return rc; +} + +/** + * internal: read the specified amount of data in whatever blocks the backend + * will give us. + */ +static int vdReadHelper(PVDISK pDisk, PVDIMAGE pImage, uint64_t uOffset, + void *pvBuf, size_t cbRead, bool fUpdateCache) +{ + return vdReadHelperEx(pDisk, pImage, NULL, uOffset, pvBuf, cbRead, + true /* fZeroFreeBlocks */, fUpdateCache, 0); +} + +/** + * internal: mark the disk as not modified. + */ +static void vdResetModifiedFlag(PVDISK pDisk) +{ + if (pDisk->uModified & VD_IMAGE_MODIFIED_FLAG) + { + /* generate new last-modified uuid */ + if (!(pDisk->uModified & VD_IMAGE_MODIFIED_DISABLE_UUID_UPDATE)) + { + RTUUID Uuid; + + RTUuidCreate(&Uuid); + pDisk->pLast->Backend->pfnSetModificationUuid(pDisk->pLast->pBackendData, + &Uuid); + + if (pDisk->pCache) + pDisk->pCache->Backend->pfnSetModificationUuid(pDisk->pCache->pBackendData, + &Uuid); + } + + pDisk->uModified &= ~VD_IMAGE_MODIFIED_FLAG; + } +} + +/** + * internal: mark the disk as modified. + */ +static void vdSetModifiedFlag(PVDISK pDisk) +{ + pDisk->uModified |= VD_IMAGE_MODIFIED_FLAG; + if (pDisk->uModified & VD_IMAGE_MODIFIED_FIRST) + { + pDisk->uModified &= ~VD_IMAGE_MODIFIED_FIRST; + + /* First modify, so create a UUID and ensure it's written to disk. */ + vdResetModifiedFlag(pDisk); + + if (!(pDisk->uModified & VD_IMAGE_MODIFIED_DISABLE_UUID_UPDATE)) + { + VDIOCTX IoCtx; + vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_FLUSH, 0, 0, NULL, + NULL, NULL, NULL, VDIOCTX_FLAGS_SYNC); + pDisk->pLast->Backend->pfnFlush(pDisk->pLast->pBackendData, &IoCtx); + } + } +} + +/** + * internal: write buffer to the image, taking care of block boundaries and + * write optimizations. + */ +static int vdWriteHelperEx(PVDISK pDisk, PVDIMAGE pImage, + PVDIMAGE pImageParentOverride, uint64_t uOffset, + const void *pvBuf, size_t cbWrite, + uint32_t fFlags, unsigned cImagesRead) +{ + int rc = VINF_SUCCESS; + RTSGSEG Segment; + RTSGBUF SgBuf; + VDIOCTX IoCtx; + RTSEMEVENT hEventComplete = NIL_RTSEMEVENT; + + rc = RTSemEventCreate(&hEventComplete); + if (RT_FAILURE(rc)) + return rc; + + fFlags |= VDIOCTX_FLAGS_SYNC | VDIOCTX_FLAGS_DONT_FREE; + + 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 = hEventComplete; + if (RT_SUCCESS(rc)) + rc = vdIoCtxProcessSync(&IoCtx, hEventComplete); + + RTSemEventDestroy(hEventComplete); + return rc; +} + +/** + * internal: write buffer to the image, taking care of block boundaries and + * write optimizations. + */ +static int vdWriteHelper(PVDISK pDisk, PVDIMAGE pImage, uint64_t uOffset, + const void *pvBuf, size_t cbWrite, uint32_t fFlags) +{ + return vdWriteHelperEx(pDisk, pImage, NULL, uOffset, pvBuf, cbWrite, + fFlags, 0); +} + +/** + * Internal: Copies the content of one disk to another one applying optimizations + * to speed up the copy process if possible. + */ +static int vdCopyHelper(PVDISK pDiskFrom, PVDIMAGE pImageFrom, PVDISK pDiskTo, + uint64_t cbSize, unsigned cImagesFromRead, unsigned cImagesToRead, + bool fSuppressRedundantIo, PVDINTERFACEPROGRESS pIfProgress, + PVDINTERFACEPROGRESS pDstIfProgress) +{ + int rc = VINF_SUCCESS; + int rc2; + uint64_t uOffset = 0; + uint64_t cbRemaining = cbSize; + void *pvBuf = NULL; + bool fLockReadFrom = false; + bool fLockWriteTo = false; + bool fBlockwiseCopy = false; + unsigned uProgressOld = 0; + + LogFlowFunc(("pDiskFrom=%#p pImageFrom=%#p pDiskTo=%#p cbSize=%llu cImagesFromRead=%u cImagesToRead=%u fSuppressRedundantIo=%RTbool pIfProgress=%#p pDstIfProgress=%#p\n", + pDiskFrom, pImageFrom, pDiskTo, cbSize, cImagesFromRead, cImagesToRead, fSuppressRedundantIo, pDstIfProgress, pDstIfProgress)); + + if ( (fSuppressRedundantIo || (cImagesFromRead > 0)) + && RTListIsEmpty(&pDiskFrom->ListFilterChainRead)) + fBlockwiseCopy = true; + + /* Allocate tmp buffer. */ + pvBuf = RTMemTmpAlloc(VD_MERGE_BUFFER_SIZE); + if (!pvBuf) + return rc; + + do + { + size_t cbThisRead = RT_MIN(VD_MERGE_BUFFER_SIZE, cbRemaining); + + /* Note that we don't attempt to synchronize cross-disk accesses. + * It wouldn't be very difficult to do, just the lock order would + * need to be defined somehow to prevent deadlocks. Postpone such + * magic as there is no use case for this. */ + + rc2 = vdThreadStartRead(pDiskFrom); + AssertRC(rc2); + fLockReadFrom = true; + + if (fBlockwiseCopy) + { + RTSGSEG SegmentBuf; + RTSGBUF SgBuf; + VDIOCTX IoCtx; + + SegmentBuf.pvSeg = pvBuf; + SegmentBuf.cbSeg = VD_MERGE_BUFFER_SIZE; + RTSgBufInit(&SgBuf, &SegmentBuf, 1); + vdIoCtxInit(&IoCtx, pDiskFrom, VDIOCTXTXDIR_READ, 0, 0, NULL, + &SgBuf, NULL, NULL, VDIOCTX_FLAGS_SYNC); + + /* Read the source data. */ + rc = pImageFrom->Backend->pfnRead(pImageFrom->pBackendData, + uOffset, cbThisRead, &IoCtx, + &cbThisRead); + + if ( rc == VERR_VD_BLOCK_FREE + && cImagesFromRead != 1) + { + unsigned cImagesToProcess = cImagesFromRead; + + for (PVDIMAGE pCurrImage = pImageFrom->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--; + } + } + } + else + rc = vdReadHelper(pDiskFrom, pImageFrom, uOffset, pvBuf, cbThisRead, + false /* fUpdateCache */); + + if (RT_FAILURE(rc) && rc != VERR_VD_BLOCK_FREE) + break; + + rc2 = vdThreadFinishRead(pDiskFrom); + AssertRC(rc2); + fLockReadFrom = false; + + if (rc != VERR_VD_BLOCK_FREE) + { + rc2 = vdThreadStartWrite(pDiskTo); + AssertRC(rc2); + fLockWriteTo = true; + + /* Only do collapsed I/O if we are copying the data blockwise. */ + rc = vdWriteHelperEx(pDiskTo, pDiskTo->pLast, NULL, uOffset, pvBuf, + cbThisRead, VDIOCTX_FLAGS_DONT_SET_MODIFIED_FLAG /* fFlags */, + fBlockwiseCopy ? cImagesToRead : 0); + if (RT_FAILURE(rc)) + break; + + rc2 = vdThreadFinishWrite(pDiskTo); + AssertRC(rc2); + fLockWriteTo = false; + } + else /* Don't propagate the error to the outside */ + rc = VINF_SUCCESS; + + uOffset += cbThisRead; + cbRemaining -= cbThisRead; + + unsigned uProgressNew = uOffset * 99 / cbSize; + if (uProgressNew != uProgressOld) + { + uProgressOld = uProgressNew; + + if (pIfProgress && pIfProgress->pfnProgress) + { + rc = pIfProgress->pfnProgress(pIfProgress->Core.pvUser, + uProgressOld); + if (RT_FAILURE(rc)) + break; + } + if (pDstIfProgress && pDstIfProgress->pfnProgress) + { + rc = pDstIfProgress->pfnProgress(pDstIfProgress->Core.pvUser, + uProgressOld); + if (RT_FAILURE(rc)) + break; + } + } + } while (uOffset < cbSize); + + RTMemFree(pvBuf); + + if (fLockReadFrom) + { + rc2 = vdThreadFinishRead(pDiskFrom); + AssertRC(rc2); + } + + if (fLockWriteTo) + { + rc2 = vdThreadFinishWrite(pDiskTo); + AssertRC(rc2); + } + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + +/** + * Flush helper async version. + */ +static DECLCALLBACK(int) vdSetModifiedHelperAsync(PVDIOCTX pIoCtx) +{ + int rc = VINF_SUCCESS; + PVDIMAGE pImage = pIoCtx->Req.Io.pImageCur; + + rc = pImage->Backend->pfnFlush(pImage->pBackendData, pIoCtx); + if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS) + rc = VINF_SUCCESS; + + return rc; +} + +/** + * internal: mark the disk as modified - async version. + */ +static int vdSetModifiedFlagAsync(PVDISK pDisk, PVDIOCTX pIoCtx) +{ + int rc = VINF_SUCCESS; + + VD_IS_LOCKED(pDisk); + + pDisk->uModified |= VD_IMAGE_MODIFIED_FLAG; + if (pDisk->uModified & VD_IMAGE_MODIFIED_FIRST) + { + rc = vdIoCtxLockDisk(pDisk, pIoCtx); + if (RT_SUCCESS(rc)) + { + pDisk->uModified &= ~VD_IMAGE_MODIFIED_FIRST; + + /* First modify, so create a UUID and ensure it's written to disk. */ + vdResetModifiedFlag(pDisk); + + if (!(pDisk->uModified & VD_IMAGE_MODIFIED_DISABLE_UUID_UPDATE)) + { + PVDIOCTX pIoCtxFlush = vdIoCtxChildAlloc(pDisk, VDIOCTXTXDIR_FLUSH, + 0, 0, pDisk->pLast, + NULL, pIoCtx, 0, 0, NULL, + vdSetModifiedHelperAsync); + + if (pIoCtxFlush) + { + rc = vdIoCtxProcessLocked(pIoCtxFlush); + if (rc == VINF_VD_ASYNC_IO_FINISHED) + { + vdIoCtxUnlockDisk(pDisk, pIoCtx, false /* fProcessDeferredReqs */); + vdIoCtxFree(pDisk, pIoCtxFlush); + } + else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS) + { + ASMAtomicIncU32(&pIoCtx->cDataTransfersPending); + pIoCtx->fFlags |= VDIOCTX_FLAGS_BLOCKED; + } + else /* Another error */ + vdIoCtxFree(pDisk, pIoCtxFlush); + } + else + rc = VERR_NO_MEMORY; + } + } + } + + return rc; +} + +static DECLCALLBACK(int) vdWriteHelperCommitAsync(PVDIOCTX pIoCtx) +{ + int rc = VINF_SUCCESS; + PVDIMAGE pImage = pIoCtx->Req.Io.pImageStart; + size_t cbPreRead = pIoCtx->Type.Child.cbPreRead; + size_t cbPostRead = pIoCtx->Type.Child.cbPostRead; + size_t cbThisWrite = pIoCtx->Type.Child.cbTransferParent; + + LogFlowFunc(("pIoCtx=%#p\n", pIoCtx)); + rc = pImage->Backend->pfnWrite(pImage->pBackendData, + 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); + Assert(rc == VERR_VD_NOT_ENOUGH_METADATA || cbPostRead == 0); + if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS) + rc = VINF_SUCCESS; + else if (rc == VERR_VD_IOCTX_HALT) + { + pIoCtx->fFlags |= VDIOCTX_FLAGS_BLOCKED; + rc = VINF_SUCCESS; + } + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) vdWriteHelperOptimizedCmpAndWriteAsync(PVDIOCTX pIoCtx) +{ + int rc = VINF_SUCCESS; + size_t cbThisWrite = 0; + size_t cbPreRead = pIoCtx->Type.Child.cbPreRead; + size_t cbPostRead = pIoCtx->Type.Child.cbPostRead; + size_t cbWriteCopy = pIoCtx->Type.Child.Write.Optimized.cbWriteCopy; + size_t cbFill = pIoCtx->Type.Child.Write.Optimized.cbFill; + size_t cbReadImage = pIoCtx->Type.Child.Write.Optimized.cbReadImage; + PVDIOCTX pIoCtxParent = pIoCtx->pIoCtxParent; + + LogFlowFunc(("pIoCtx=%#p\n", pIoCtx)); + + AssertPtr(pIoCtxParent); + Assert(!pIoCtxParent->pIoCtxParent); + Assert(!pIoCtx->Req.Io.cbTransferLeft && !pIoCtx->cMetaTransfersPending); + + vdIoCtxChildReset(pIoCtx); + cbThisWrite = pIoCtx->Type.Child.cbTransferParent; + RTSgBufAdvance(&pIoCtx->Req.Io.SgBuf, cbPreRead); + + /* Check if the write would modify anything in this block. */ + if (!RTSgBufCmp(&pIoCtx->Req.Io.SgBuf, &pIoCtxParent->Req.Io.SgBuf, cbThisWrite)) + { + RTSGBUF SgBufSrcTmp; + + RTSgBufClone(&SgBufSrcTmp, &pIoCtxParent->Req.Io.SgBuf); + RTSgBufAdvance(&SgBufSrcTmp, cbThisWrite); + RTSgBufAdvance(&pIoCtx->Req.Io.SgBuf, cbThisWrite); + + if (!cbWriteCopy || !RTSgBufCmp(&pIoCtx->Req.Io.SgBuf, &SgBufSrcTmp, cbWriteCopy)) + { + /* Block is completely unchanged, so no need to write anything. */ + LogFlowFunc(("Block didn't changed\n")); + ASMAtomicWriteU32(&pIoCtx->Req.Io.cbTransferLeft, 0); + RTSgBufAdvance(&pIoCtxParent->Req.Io.SgBuf, cbThisWrite); + return VINF_VD_ASYNC_IO_FINISHED; + } + } + + /* Copy the data to the right place in the buffer. */ + RTSgBufReset(&pIoCtx->Req.Io.SgBuf); + RTSgBufAdvance(&pIoCtx->Req.Io.SgBuf, cbPreRead); + vdIoCtxCopy(pIoCtx, pIoCtxParent, cbThisWrite); + + /* Handle the data that goes after the write to fill the block. */ + if (cbPostRead) + { + /* 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); + } + } + + /* Write the full block to the virtual disk. */ + RTSgBufReset(&pIoCtx->Req.Io.SgBuf); + pIoCtx->pfnIoCtxTransferNext = vdWriteHelperCommitAsync; + + return rc; +} + +static DECLCALLBACK(int) vdWriteHelperOptimizedPreReadAsync(PVDIOCTX pIoCtx) +{ + int rc = VINF_SUCCESS; + + LogFlowFunc(("pIoCtx=%#p\n", pIoCtx)); + + pIoCtx->fFlags |= VDIOCTX_FLAGS_ZERO_FREE_BLOCKS; + + if ( pIoCtx->Req.Io.cbTransferLeft + && !pIoCtx->cDataTransfersPending) + rc = vdReadHelperAsync(pIoCtx); + + if ( ( RT_SUCCESS(rc) + || (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)) + && ( pIoCtx->Req.Io.cbTransferLeft + || pIoCtx->cMetaTransfersPending)) + rc = VERR_VD_ASYNC_IO_IN_PROGRESS; + else + pIoCtx->pfnIoCtxTransferNext = vdWriteHelperOptimizedCmpAndWriteAsync; + + 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 - async version. + */ +static DECLCALLBACK(int) vdWriteHelperOptimizedAsync(PVDIOCTX pIoCtx) +{ + PVDISK 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); + + 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; + } + + pIoCtx->Type.Child.Write.Optimized.cbFill = cbFill; + pIoCtx->Type.Child.Write.Optimized.cbWriteCopy = cbWriteCopy; + pIoCtx->Type.Child.Write.Optimized.cbReadImage = cbReadImage; + + /* Read the entire data of the block so that we can compare whether it will + * be modified by the write or not. */ + size_t cbTmp = cbPreRead + cbThisWrite + cbPostRead - cbFill; Assert(cbTmp == (uint32_t)cbTmp); + pIoCtx->Req.Io.cbTransferLeft = (uint32_t)cbTmp; + pIoCtx->Req.Io.cbTransfer = pIoCtx->Req.Io.cbTransferLeft; + pIoCtx->Req.Io.uOffset -= cbPreRead; + + /* Next step */ + pIoCtx->pfnIoCtxTransferNext = vdWriteHelperOptimizedPreReadAsync; + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) vdWriteHelperStandardReadImageAsync(PVDIOCTX pIoCtx) +{ + int rc = VINF_SUCCESS; + + LogFlowFunc(("pIoCtx=%#p\n", pIoCtx)); + + pIoCtx->fFlags |= VDIOCTX_FLAGS_ZERO_FREE_BLOCKS; + + if ( pIoCtx->Req.Io.cbTransferLeft + && !pIoCtx->cDataTransfersPending) + rc = vdReadHelperAsync(pIoCtx); + + if ( RT_SUCCESS(rc) + && ( pIoCtx->Req.Io.cbTransferLeft + || pIoCtx->cMetaTransfersPending)) + rc = VERR_VD_ASYNC_IO_IN_PROGRESS; + else + { + size_t cbFill = pIoCtx->Type.Child.Write.Optimized.cbFill; + + /* Zero out the remainder of this block. Will never be visible, as this + * is beyond the limit of the image. */ + if (cbFill) + vdIoCtxSet(pIoCtx, '\0', cbFill); + + /* Write the full block to the virtual disk. */ + RTSgBufReset(&pIoCtx->Req.Io.SgBuf); + + vdIoCtxChildReset(pIoCtx); + pIoCtx->pfnIoCtxTransferNext = vdWriteHelperCommitAsync; + } + + return rc; +} + +static DECLCALLBACK(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); + } + + if (cbReadImage) + { + /* Read remaining data. */ + pIoCtx->pfnIoCtxTransferNext = vdWriteHelperStandardReadImageAsync; + + /* Read the data that goes before the write to fill the block. */ + pIoCtx->Req.Io.cbTransferLeft = (uint32_t)cbReadImage; Assert(cbReadImage == (uint32_t)cbReadImage); + pIoCtx->Req.Io.cbTransfer = pIoCtx->Req.Io.cbTransferLeft; + pIoCtx->Req.Io.uOffset += cbWriteCopy; + } + else + { + /* Zero out the remainder of this block. Will never be visible, as this + * is beyond the limit of the image. */ + if (cbFill) + vdIoCtxSet(pIoCtx, '\0', cbFill); + + /* Write the full block to the virtual disk. */ + RTSgBufReset(&pIoCtx->Req.Io.SgBuf); + vdIoCtxChildReset(pIoCtx); + pIoCtx->pfnIoCtxTransferNext = vdWriteHelperCommitAsync; + } + } + else + { + /* Write the full block to the virtual disk. */ + RTSgBufReset(&pIoCtx->Req.Io.SgBuf); + vdIoCtxChildReset(pIoCtx); + pIoCtx->pfnIoCtxTransferNext = vdWriteHelperCommitAsync; + } + + return rc; +} + +static DECLCALLBACK(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 + && !pIoCtx->cDataTransfersPending) + 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 DECLCALLBACK(int) vdWriteHelperStandardAsync(PVDIOCTX pIoCtx) +{ + PVDISK 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. */ + 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. */ + 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 = (uint32_t)cbPreRead; Assert(cbPreRead == (uint32_t)cbPreRead); + pIoCtx->Req.Io.cbTransfer = pIoCtx->Req.Io.cbTransferLeft; + pIoCtx->Req.Io.uOffset -= cbPreRead; + } + else + pIoCtx->pfnIoCtxTransferNext = vdWriteHelperStandardAssemble; + + return VINF_SUCCESS; +} + +/** + * internal: write buffer to the image, taking care of block boundaries and + * write optimizations - async version. + */ +static DECLCALLBACK(int) vdWriteHelperAsync(PVDIOCTX pIoCtx) +{ + int rc; + size_t cbWrite = pIoCtx->Req.Io.cbTransfer; + uint64_t uOffset = pIoCtx->Req.Io.uOffset; + PVDIMAGE pImage = pIoCtx->Req.Io.pImageCur; + PVDISK pDisk = pIoCtx->pDisk; + unsigned fWrite; + size_t cbThisWrite; + size_t cbPreRead, cbPostRead; + + /* Apply write filter chain here if it was not done already. */ + if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_WRITE_FILTER_APPLIED)) + { + rc = vdFilterChainApplyWrite(pDisk, uOffset, cbWrite, pIoCtx); + if (RT_FAILURE(rc)) + return rc; + pIoCtx->fFlags |= VDIOCTX_FLAGS_WRITE_FILTER_APPLIED; + } + + if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_DONT_SET_MODIFIED_FLAG)) + { + rc = vdSetModifiedFlagAsync(pDisk, pIoCtx); + if (RT_FAILURE(rc)) /* Includes I/O in progress. */ + return rc; + } + + rc = vdDiscardSetRangeAllocated(pDisk, uOffset, cbWrite); + if (RT_FAILURE(rc)) + return rc; + + /* 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 = cbWrite; + + /* + * Check whether there is a full block write in progress which was not allocated. + * Defer I/O if the range interferes. + */ + if ( pDisk->pIoCtxLockOwner != NIL_VDIOCTX + && uOffset >= pDisk->uOffsetStartLocked + && uOffset < pDisk->uOffsetEndLocked) + { + Log(("Interferring write while allocating a new block => deferring write\n")); + vdIoCtxDefer(pDisk, pIoCtx); + rc = VERR_VD_ASYNC_IO_IN_PROGRESS; + break; + } + + fWrite = (pImage->uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME) + ? 0 : VD_WRITE_NO_ALLOC; + rc = pImage->Backend->pfnWrite(pImage->pBackendData, uOffset, cbThisWrite, + pIoCtx, &cbThisWrite, &cbPreRead, &cbPostRead, + fWrite); + if (rc == VERR_VD_BLOCK_FREE) + { + /* Lock the disk .*/ + rc = vdIoCtxLockDisk(pDisk, pIoCtx); + if (RT_SUCCESS(rc)) + { + /* + * Allocate segment and buffer in one go. + * A bit hackish but avoids the need to allocate memory twice. + */ + PRTSGBUF pTmp = (PRTSGBUF)RTMemAlloc(cbPreRead + cbThisWrite + cbPostRead + sizeof(RTSGSEG) + sizeof(RTSGBUF)); + AssertBreakStmt(pTmp, rc = VERR_NO_MEMORY); + PRTSGSEG pSeg = (PRTSGSEG)(pTmp + 1); + + pSeg->pvSeg = pSeg + 1; + pSeg->cbSeg = cbPreRead + cbThisWrite + cbPostRead; + RTSgBufInit(pTmp, pSeg, 1); + + PVDIOCTX pIoCtxWrite = vdIoCtxChildAlloc(pDisk, VDIOCTXTXDIR_WRITE, + uOffset, pSeg->cbSeg, pImage, + pTmp, + pIoCtx, cbThisWrite, + cbWrite, + pTmp, + (pImage->uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME) + ? vdWriteHelperStandardAsync + : vdWriteHelperOptimizedAsync); + if (!VALID_PTR(pIoCtxWrite)) + { + RTMemTmpFree(pTmp); + rc = VERR_NO_MEMORY; + break; + } + + LogFlowFunc(("Disk is growing because of pIoCtx=%#p pIoCtxWrite=%#p\n", + pIoCtx, pIoCtxWrite)); + + /* Save the current range for the growing operation to check for intersecting requests later. */ + pDisk->uOffsetStartLocked = uOffset - cbPreRead; + pDisk->uOffsetEndLocked = uOffset + cbThisWrite + cbPostRead; + + pIoCtxWrite->Type.Child.cbPreRead = cbPreRead; + pIoCtxWrite->Type.Child.cbPostRead = cbPostRead; + pIoCtxWrite->Req.Io.pImageParentOverride = pIoCtx->Req.Io.pImageParentOverride; + + /* Process the write request */ + rc = vdIoCtxProcessLocked(pIoCtxWrite); + + if (RT_FAILURE(rc) && (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)) + { + vdIoCtxUnlockDisk(pDisk, pIoCtx, false /* fProcessDeferredReqs*/ ); + vdIoCtxFree(pDisk, pIoCtxWrite); + break; + } + else if ( rc == VINF_VD_ASYNC_IO_FINISHED + && ASMAtomicCmpXchgBool(&pIoCtxWrite->fComplete, true, false)) + { + LogFlow(("Child write request completed\n")); + Assert(pIoCtx->Req.Io.cbTransferLeft >= cbThisWrite); + Assert(cbThisWrite == (uint32_t)cbThisWrite); + rc = pIoCtxWrite->rcReq; + ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbThisWrite); + vdIoCtxUnlockDisk(pDisk, pIoCtx, false /* fProcessDeferredReqs*/ ); + vdIoCtxFree(pDisk, pIoCtxWrite); + } + else + { + LogFlow(("Child write pending\n")); + ASMAtomicIncU32(&pIoCtx->cDataTransfersPending); + pIoCtx->fFlags |= VDIOCTX_FLAGS_BLOCKED; + rc = VERR_VD_ASYNC_IO_IN_PROGRESS; + cbWrite -= cbThisWrite; + uOffset += cbThisWrite; + break; + } + } + else + { + rc = VERR_VD_ASYNC_IO_IN_PROGRESS; + break; + } + } + + if (rc == VERR_VD_IOCTX_HALT) + { + cbWrite -= cbThisWrite; + uOffset += cbThisWrite; + pIoCtx->fFlags |= VDIOCTX_FLAGS_BLOCKED; + break; + } + else if (rc == VERR_VD_NOT_ENOUGH_METADATA) + break; + + cbWrite -= cbThisWrite; + uOffset += cbThisWrite; + } 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 + || rc == VERR_VD_IOCTX_HALT) + { + /* + * Tell the caller that we don't need to go back here because all + * writes are initiated. + */ + if ( !cbWrite + && rc != VERR_VD_IOCTX_HALT) + rc = VINF_SUCCESS; + + pIoCtx->Req.Io.uOffset = uOffset; + pIoCtx->Req.Io.cbTransfer = cbWrite; + } + + return rc; +} + +/** + * Flush helper async version. + */ +static DECLCALLBACK(int) vdFlushHelperAsync(PVDIOCTX pIoCtx) +{ + int rc = VINF_SUCCESS; + PVDISK pDisk = pIoCtx->pDisk; + PVDIMAGE pImage = pIoCtx->Req.Io.pImageCur; + + rc = vdIoCtxLockDisk(pDisk, pIoCtx); + if (RT_SUCCESS(rc)) + { + /* Mark the whole disk as locked. */ + pDisk->uOffsetStartLocked = 0; + pDisk->uOffsetEndLocked = UINT64_C(0xffffffffffffffff); + + vdResetModifiedFlag(pDisk); + rc = pImage->Backend->pfnFlush(pImage->pBackendData, pIoCtx); + if ( ( RT_SUCCESS(rc) + || rc == VERR_VD_ASYNC_IO_IN_PROGRESS + || rc == VERR_VD_IOCTX_HALT) + && pDisk->pCache) + { + rc = pDisk->pCache->Backend->pfnFlush(pDisk->pCache->pBackendData, pIoCtx); + if ( RT_SUCCESS(rc) + || ( rc != VERR_VD_ASYNC_IO_IN_PROGRESS + && rc != VERR_VD_IOCTX_HALT)) + vdIoCtxUnlockDisk(pDisk, pIoCtx, true /* fProcessBlockedReqs */); + else if (rc != VERR_VD_IOCTX_HALT) + rc = VINF_SUCCESS; + } + else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS) + rc = VINF_SUCCESS; + else if (rc != VERR_VD_IOCTX_HALT)/* Some other error. */ + vdIoCtxUnlockDisk(pDisk, pIoCtx, true /* fProcessBlockedReqs */); + } + + return rc; +} + +/** + * Async discard helper - discards a whole block which is recorded in the block + * tree. + * + * @returns VBox status code. + * @param pIoCtx The I/O context to operate on. + */ +static DECLCALLBACK(int) vdDiscardWholeBlockAsync(PVDIOCTX pIoCtx) +{ + int rc = VINF_SUCCESS; + PVDISK pDisk = pIoCtx->pDisk; + PVDDISCARDSTATE pDiscard = pDisk->pDiscard; + PVDDISCARDBLOCK pBlock = pIoCtx->Req.Discard.pBlock; + size_t cbPreAllocated, cbPostAllocated, cbActuallyDiscarded; + + LogFlowFunc(("pIoCtx=%#p\n", pIoCtx)); + + AssertPtr(pBlock); + + rc = pDisk->pLast->Backend->pfnDiscard(pDisk->pLast->pBackendData, pIoCtx, + pBlock->Core.Key, pBlock->cbDiscard, + &cbPreAllocated, &cbPostAllocated, + &cbActuallyDiscarded, NULL, 0); + Assert(rc != VERR_VD_DISCARD_ALIGNMENT_NOT_MET); + Assert(!cbPreAllocated); + Assert(!cbPostAllocated); + Assert(cbActuallyDiscarded == pBlock->cbDiscard || RT_FAILURE(rc)); + + /* Remove the block on success. */ + if ( RT_SUCCESS(rc) + || rc == VERR_VD_ASYNC_IO_IN_PROGRESS) + { + PVDDISCARDBLOCK pBlockRemove = (PVDDISCARDBLOCK)RTAvlrU64RangeRemove(pDiscard->pTreeBlocks, pBlock->Core.Key); + Assert(pBlockRemove == pBlock); RT_NOREF1(pBlockRemove); + + pDiscard->cbDiscarding -= pBlock->cbDiscard; + RTListNodeRemove(&pBlock->NodeLru); + RTMemFree(pBlock->pbmAllocated); + RTMemFree(pBlock); + pIoCtx->Req.Discard.pBlock = NULL;/* Safety precaution. */ + pIoCtx->pfnIoCtxTransferNext = vdDiscardHelperAsync; /* Next part. */ + rc = VINF_SUCCESS; + } + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + +/** + * Removes the least recently used blocks from the waiting list until + * the new value is reached - version for async I/O. + * + * @returns VBox status code. + * @param pDisk VD disk container. + * @param pIoCtx The I/O context associated with this discard operation. + * @param cbDiscardingNew How many bytes should be waiting on success. + * The number of bytes waiting can be less. + */ +static int vdDiscardRemoveBlocksAsync(PVDISK pDisk, PVDIOCTX pIoCtx, size_t cbDiscardingNew) +{ + int rc = VINF_SUCCESS; + PVDDISCARDSTATE pDiscard = pDisk->pDiscard; + + 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 = (uint32_t)(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; + + rc = pDisk->pLast->Backend->pfnDiscard(pDisk->pLast->pBackendData, pIoCtx, + offStart, cbThis, NULL, NULL, &cbThis, + NULL, VD_DISCARD_MARK_UNUSED); + if ( RT_FAILURE(rc) + && rc != VERR_VD_ASYNC_IO_IN_PROGRESS) + break; + + fAllocated = true; + } + + idxStart = idxEnd; + offStart += cbThis; + cbLeft -= cbThis; + } + + if ( RT_FAILURE(rc) + && rc != VERR_VD_ASYNC_IO_IN_PROGRESS) + break; + + PVDDISCARDBLOCK pBlockRemove = (PVDDISCARDBLOCK)RTAvlrU64RangeRemove(pDiscard->pTreeBlocks, pBlock->Core.Key); + Assert(pBlockRemove == pBlock); NOREF(pBlockRemove); + RTListNodeRemove(&pBlock->NodeLru); + + pDiscard->cbDiscarding -= pBlock->cbDiscard; + RTMemFree(pBlock->pbmAllocated); + RTMemFree(pBlock); + } + + if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS) + rc = VINF_SUCCESS; + + Assert(RT_FAILURE(rc) || pDiscard->cbDiscarding <= cbDiscardingNew); + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + +/** + * Async discard helper - discards the current range if there is no matching + * block in the tree. + * + * @returns VBox status code. + * @param pIoCtx The I/O context to operate on. + */ +static DECLCALLBACK(int) vdDiscardCurrentRangeAsync(PVDIOCTX pIoCtx) +{ + PVDISK pDisk = pIoCtx->pDisk; + PVDDISCARDSTATE pDiscard = pDisk->pDiscard; + uint64_t offStart = pIoCtx->Req.Discard.offCur; + size_t cbThisDiscard = pIoCtx->Req.Discard.cbThisDiscard; + void *pbmAllocated = NULL; + size_t cbPreAllocated, cbPostAllocated; + int rc = VINF_SUCCESS; + + LogFlowFunc(("pIoCtx=%#p\n", pIoCtx)); + + /* No block found, try to discard using the backend first. */ + rc = pDisk->pLast->Backend->pfnDiscard(pDisk->pLast->pBackendData, pIoCtx, + offStart, cbThisDiscard, &cbPreAllocated, + &cbPostAllocated, &cbThisDiscard, + &pbmAllocated, 0); + if (rc == VERR_VD_DISCARD_ALIGNMENT_NOT_MET) + { + /* Create new discard block. */ + PVDDISCARDBLOCK pBlock = (PVDDISCARDBLOCK)RTMemAllocZ(sizeof(VDDISCARDBLOCK)); + if (pBlock) + { + pBlock->Core.Key = offStart - cbPreAllocated; + pBlock->Core.KeyLast = offStart + cbThisDiscard + cbPostAllocated - 1; + pBlock->cbDiscard = cbPreAllocated + cbThisDiscard + cbPostAllocated; + pBlock->pbmAllocated = pbmAllocated; + bool fInserted = RTAvlrU64Insert(pDiscard->pTreeBlocks, &pBlock->Core); + Assert(fInserted); NOREF(fInserted); + + RTListPrepend(&pDiscard->ListLru, &pBlock->NodeLru); + pDiscard->cbDiscarding += pBlock->cbDiscard; + + Assert(pIoCtx->Req.Discard.cbDiscardLeft >= cbThisDiscard); + pIoCtx->Req.Discard.cbDiscardLeft -= cbThisDiscard; + pIoCtx->Req.Discard.offCur += cbThisDiscard; + pIoCtx->Req.Discard.cbThisDiscard = cbThisDiscard; + + if (pDiscard->cbDiscarding > VD_DISCARD_REMOVE_THRESHOLD) + rc = vdDiscardRemoveBlocksAsync(pDisk, pIoCtx, VD_DISCARD_REMOVE_THRESHOLD); + else + rc = VINF_SUCCESS; + + if (RT_SUCCESS(rc)) + pIoCtx->pfnIoCtxTransferNext = vdDiscardHelperAsync; /* Next part. */ + } + else + { + RTMemFree(pbmAllocated); + rc = VERR_NO_MEMORY; + } + } + else if ( RT_SUCCESS(rc) + || rc == VERR_VD_ASYNC_IO_IN_PROGRESS) /* Save state and andvance to next range. */ + { + Assert(pIoCtx->Req.Discard.cbDiscardLeft >= cbThisDiscard); + pIoCtx->Req.Discard.cbDiscardLeft -= cbThisDiscard; + pIoCtx->Req.Discard.offCur += cbThisDiscard; + pIoCtx->Req.Discard.cbThisDiscard = cbThisDiscard; + pIoCtx->pfnIoCtxTransferNext = vdDiscardHelperAsync; + rc = VINF_SUCCESS; + } + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + +/** + * Async discard helper - entry point. + * + * @returns VBox status code. + * @param pIoCtx The I/O context to operate on. + */ +static DECLCALLBACK(int) vdDiscardHelperAsync(PVDIOCTX pIoCtx) +{ + int rc = VINF_SUCCESS; + PVDISK pDisk = pIoCtx->pDisk; + PCRTRANGE paRanges = pIoCtx->Req.Discard.paRanges; + unsigned cRanges = pIoCtx->Req.Discard.cRanges; + PVDDISCARDSTATE pDiscard = pDisk->pDiscard; + + LogFlowFunc(("pIoCtx=%#p\n", pIoCtx)); + + /* Check if the I/O context processed all ranges. */ + if ( pIoCtx->Req.Discard.idxRange == cRanges + && !pIoCtx->Req.Discard.cbDiscardLeft) + { + LogFlowFunc(("All ranges discarded, completing\n")); + vdIoCtxUnlockDisk(pDisk, pIoCtx, true /* fProcessDeferredReqs*/); + return VINF_SUCCESS; + } + + if (pDisk->pIoCtxLockOwner != pIoCtx) + rc = vdIoCtxLockDisk(pDisk, pIoCtx); + + if (RT_SUCCESS(rc)) + { + uint64_t offStart = pIoCtx->Req.Discard.offCur; + size_t cbDiscardLeft = pIoCtx->Req.Discard.cbDiscardLeft; + size_t cbThisDiscard; + + pDisk->uOffsetStartLocked = offStart; + pDisk->uOffsetEndLocked = offStart + cbDiscardLeft; + + if (RT_UNLIKELY(!pDiscard)) + { + pDiscard = vdDiscardStateCreate(); + if (!pDiscard) + return VERR_NO_MEMORY; + + pDisk->pDiscard = pDiscard; + } + + if (!pIoCtx->Req.Discard.cbDiscardLeft) + { + offStart = paRanges[pIoCtx->Req.Discard.idxRange].offStart; + cbDiscardLeft = paRanges[pIoCtx->Req.Discard.idxRange].cbRange; + LogFlowFunc(("New range descriptor loaded (%u) offStart=%llu cbDiscard=%zu\n", + pIoCtx->Req.Discard.idxRange, offStart, cbDiscardLeft)); + pIoCtx->Req.Discard.idxRange++; + } + + /* Look for a matching block in the AVL tree first. */ + PVDDISCARDBLOCK pBlock = (PVDDISCARDBLOCK)RTAvlrU64GetBestFit(pDiscard->pTreeBlocks, offStart, false); + if (!pBlock || pBlock->Core.KeyLast < offStart) + { + PVDDISCARDBLOCK pBlockAbove = (PVDDISCARDBLOCK)RTAvlrU64GetBestFit(pDiscard->pTreeBlocks, offStart, true); + + /* Clip range to remain in the current block. */ + if (pBlockAbove) + cbThisDiscard = RT_MIN(cbDiscardLeft, pBlockAbove->Core.KeyLast - offStart + 1); + else + cbThisDiscard = cbDiscardLeft; + + Assert(!(cbThisDiscard % 512)); + pIoCtx->Req.Discard.pBlock = NULL; + pIoCtx->pfnIoCtxTransferNext = vdDiscardCurrentRangeAsync; + } + else + { + /* Range lies partly in the block, update allocation bitmap. */ + int32_t idxStart, idxEnd; + + cbThisDiscard = RT_MIN(cbDiscardLeft, pBlock->Core.KeyLast - offStart + 1); + + AssertPtr(pBlock); + + Assert(!(cbThisDiscard % 512)); + Assert(!((offStart - pBlock->Core.Key) % 512)); + + idxStart = (offStart - pBlock->Core.Key) / 512; + idxEnd = idxStart + (int32_t)(cbThisDiscard / 512); + + ASMBitClearRange(pBlock->pbmAllocated, idxStart, idxEnd); + + cbDiscardLeft -= cbThisDiscard; + offStart += cbThisDiscard; + + /* Call the backend to discard the block if it is completely unallocated now. */ + if (ASMBitFirstSet((volatile void *)pBlock->pbmAllocated, (uint32_t)(pBlock->cbDiscard / 512)) == -1) + { + pIoCtx->Req.Discard.pBlock = pBlock; + pIoCtx->pfnIoCtxTransferNext = vdDiscardWholeBlockAsync; + rc = VINF_SUCCESS; + } + else + { + RTListNodeRemove(&pBlock->NodeLru); + RTListPrepend(&pDiscard->ListLru, &pBlock->NodeLru); + + /* Start with next range. */ + pIoCtx->pfnIoCtxTransferNext = vdDiscardHelperAsync; + rc = VINF_SUCCESS; + } + } + + /* Save state in the context. */ + pIoCtx->Req.Discard.offCur = offStart; + pIoCtx->Req.Discard.cbDiscardLeft = cbDiscardLeft; + pIoCtx->Req.Discard.cbThisDiscard = cbThisDiscard; + } + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + +/** + * VD async I/O interface open callback. + */ +static DECLCALLBACK(int) vdIOOpenFallback(void *pvUser, const char *pszLocation, + uint32_t fOpen, PFNVDCOMPLETED pfnCompleted, + void **ppStorage) +{ + RT_NOREF1(pvUser); + PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)RTMemAllocZ(sizeof(VDIIOFALLBACKSTORAGE)); + + if (!pStorage) + return VERR_NO_MEMORY; + + pStorage->pfnCompleted = pfnCompleted; + + /* Open the file. */ + int rc = RTFileOpen(&pStorage->File, pszLocation, fOpen); + if (RT_SUCCESS(rc)) + { + *ppStorage = pStorage; + return VINF_SUCCESS; + } + + RTMemFree(pStorage); + return rc; +} + +/** + * VD async I/O interface close callback. + */ +static DECLCALLBACK(int) vdIOCloseFallback(void *pvUser, void *pvStorage) +{ + RT_NOREF1(pvUser); + PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)pvStorage; + + RTFileClose(pStorage->File); + RTMemFree(pStorage); + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) vdIODeleteFallback(void *pvUser, const char *pcszFilename) +{ + RT_NOREF1(pvUser); + return RTFileDelete(pcszFilename); +} + +static DECLCALLBACK(int) vdIOMoveFallback(void *pvUser, const char *pcszSrc, const char *pcszDst, unsigned fMove) +{ + RT_NOREF1(pvUser); + return RTFileMove(pcszSrc, pcszDst, fMove); +} + +static DECLCALLBACK(int) vdIOGetFreeSpaceFallback(void *pvUser, const char *pcszFilename, int64_t *pcbFreeSpace) +{ + RT_NOREF1(pvUser); + return RTFsQuerySizes(pcszFilename, NULL, pcbFreeSpace, NULL, NULL); +} + +static DECLCALLBACK(int) vdIOGetModificationTimeFallback(void *pvUser, const char *pcszFilename, PRTTIMESPEC pModificationTime) +{ + RT_NOREF1(pvUser); + RTFSOBJINFO info; + int rc = RTPathQueryInfo(pcszFilename, &info, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + *pModificationTime = info.ModificationTime; + return rc; +} + +/** + * VD async I/O interface callback for retrieving the file size. + */ +static DECLCALLBACK(int) vdIOGetSizeFallback(void *pvUser, void *pvStorage, uint64_t *pcbSize) +{ + RT_NOREF1(pvUser); + PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)pvStorage; + + return RTFileQuerySize(pStorage->File, pcbSize); +} + +/** + * VD async I/O interface callback for setting the file size. + */ +static DECLCALLBACK(int) vdIOSetSizeFallback(void *pvUser, void *pvStorage, uint64_t cbSize) +{ + RT_NOREF1(pvUser); + PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)pvStorage; + + return RTFileSetSize(pStorage->File, cbSize); +} + +/** + * VD async I/O interface callback for setting the file allocation size. + */ +static DECLCALLBACK(int) vdIOSetAllocationSizeFallback(void *pvUser, void *pvStorage, uint64_t cbSize, + uint32_t fFlags) +{ + RT_NOREF2(pvUser, fFlags); + PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)pvStorage; + + return RTFileSetAllocationSize(pStorage->File, cbSize, RTFILE_ALLOC_SIZE_F_DEFAULT); +} + +/** + * VD async I/O interface callback for a synchronous write to the file. + */ +static DECLCALLBACK(int) vdIOWriteSyncFallback(void *pvUser, void *pvStorage, uint64_t uOffset, + const void *pvBuf, size_t cbWrite, size_t *pcbWritten) +{ + RT_NOREF1(pvUser); + PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)pvStorage; + + return RTFileWriteAt(pStorage->File, uOffset, pvBuf, cbWrite, pcbWritten); +} + +/** + * VD async I/O interface callback for a synchronous read from the file. + */ +static DECLCALLBACK(int) vdIOReadSyncFallback(void *pvUser, void *pvStorage, uint64_t uOffset, + void *pvBuf, size_t cbRead, size_t *pcbRead) +{ + RT_NOREF1(pvUser); + PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)pvStorage; + + return RTFileReadAt(pStorage->File, uOffset, pvBuf, cbRead, pcbRead); +} + +/** + * VD async I/O interface callback for a synchronous flush of the file data. + */ +static DECLCALLBACK(int) vdIOFlushSyncFallback(void *pvUser, void *pvStorage) +{ + RT_NOREF1(pvUser); + PVDIIOFALLBACKSTORAGE pStorage = (PVDIIOFALLBACKSTORAGE)pvStorage; + + return RTFileFlush(pStorage->File); +} + +/** + * VD async I/O interface callback for a asynchronous read from the file. + */ +static DECLCALLBACK(int) vdIOReadAsyncFallback(void *pvUser, void *pStorage, uint64_t uOffset, + PCRTSGSEG paSegments, size_t cSegments, + size_t cbRead, void *pvCompletion, + void **ppTask) +{ + RT_NOREF8(pvUser, pStorage, uOffset, paSegments, cSegments, cbRead, pvCompletion, ppTask); + AssertFailed(); + return VERR_NOT_IMPLEMENTED; +} + +/** + * VD async I/O interface callback for a asynchronous write to the file. + */ +static DECLCALLBACK(int) vdIOWriteAsyncFallback(void *pvUser, void *pStorage, uint64_t uOffset, + PCRTSGSEG paSegments, size_t cSegments, + size_t cbWrite, void *pvCompletion, + void **ppTask) +{ + RT_NOREF8(pvUser, pStorage, uOffset, paSegments, cSegments, cbWrite, pvCompletion, ppTask); + AssertFailed(); + return VERR_NOT_IMPLEMENTED; +} + +/** + * VD async I/O interface callback for a asynchronous flush of the file data. + */ +static DECLCALLBACK(int) vdIOFlushAsyncFallback(void *pvUser, void *pStorage, + void *pvCompletion, void **ppTask) +{ + RT_NOREF4(pvUser, pStorage, pvCompletion, ppTask); + AssertFailed(); + return VERR_NOT_IMPLEMENTED; +} + +/** + * Internal - Continues an I/O context after + * it was halted because of an active transfer. + */ +static int vdIoCtxContinue(PVDIOCTX pIoCtx, int rcReq) +{ + PVDISK pDisk = pIoCtx->pDisk; + int rc = VINF_SUCCESS; + + VD_IS_LOCKED(pDisk); + + if (RT_FAILURE(rcReq)) + ASMAtomicCmpXchgS32(&pIoCtx->rcReq, rcReq, VINF_SUCCESS); + + if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_BLOCKED)) + { + /* Continue the transfer */ + rc = vdIoCtxProcessLocked(pIoCtx); + + if ( rc == VINF_VD_ASYNC_IO_FINISHED + && ASMAtomicCmpXchgBool(&pIoCtx->fComplete, true, false)) + { + LogFlowFunc(("I/O context completed pIoCtx=%#p\n", pIoCtx)); + bool fFreeCtx = RT_BOOL(!(pIoCtx->fFlags & VDIOCTX_FLAGS_DONT_FREE)); + if (pIoCtx->pIoCtxParent) + { + PVDIOCTX pIoCtxParent = pIoCtx->pIoCtxParent; + + Assert(!pIoCtxParent->pIoCtxParent); + if (RT_FAILURE(pIoCtx->rcReq)) + ASMAtomicCmpXchgS32(&pIoCtxParent->rcReq, pIoCtx->rcReq, VINF_SUCCESS); + + ASMAtomicDecU32(&pIoCtxParent->cDataTransfersPending); + + if (pIoCtx->enmTxDir == VDIOCTXTXDIR_WRITE) + { + LogFlowFunc(("I/O context transferred %u bytes for the parent pIoCtxParent=%p\n", + pIoCtx->Type.Child.cbTransferParent, pIoCtxParent)); + + /* Update the parent state. */ + Assert(pIoCtxParent->Req.Io.cbTransferLeft >= pIoCtx->Type.Child.cbTransferParent); + ASMAtomicSubU32(&pIoCtxParent->Req.Io.cbTransferLeft, (uint32_t)pIoCtx->Type.Child.cbTransferParent); + } + else + Assert(pIoCtx->enmTxDir == VDIOCTXTXDIR_FLUSH); + + /* + * A completed child write means that we finished growing the image. + * We have to process any pending writes now. + */ + vdIoCtxUnlockDisk(pDisk, pIoCtxParent, false /* fProcessDeferredReqs */); + + /* Unblock the parent */ + pIoCtxParent->fFlags &= ~VDIOCTX_FLAGS_BLOCKED; + + rc = vdIoCtxProcessLocked(pIoCtxParent); + + if ( rc == VINF_VD_ASYNC_IO_FINISHED + && ASMAtomicCmpXchgBool(&pIoCtxParent->fComplete, true, false)) + { + LogFlowFunc(("Parent I/O context completed pIoCtxParent=%#p rcReq=%Rrc\n", pIoCtxParent, pIoCtxParent->rcReq)); + bool fFreeParentCtx = RT_BOOL(!(pIoCtxParent->fFlags & VDIOCTX_FLAGS_DONT_FREE)); + vdIoCtxRootComplete(pDisk, pIoCtxParent); + vdThreadFinishWrite(pDisk); + + if (fFreeParentCtx) + vdIoCtxFree(pDisk, pIoCtxParent); + vdDiskProcessBlockedIoCtx(pDisk); + } + else if (!vdIoCtxIsDiskLockOwner(pDisk, pIoCtx)) + { + /* Process any pending writes if the current request didn't caused another growing. */ + vdDiskProcessBlockedIoCtx(pDisk); + } + } + else + { + if (pIoCtx->enmTxDir == VDIOCTXTXDIR_FLUSH) + { + vdIoCtxUnlockDisk(pDisk, pIoCtx, true /* fProcessDerredReqs */); + vdThreadFinishWrite(pDisk); + } + else if ( pIoCtx->enmTxDir == VDIOCTXTXDIR_WRITE + || pIoCtx->enmTxDir == VDIOCTXTXDIR_DISCARD) + vdThreadFinishWrite(pDisk); + else + { + Assert(pIoCtx->enmTxDir == VDIOCTXTXDIR_READ); + vdThreadFinishRead(pDisk); + } + + LogFlowFunc(("I/O context completed pIoCtx=%#p rcReq=%Rrc\n", pIoCtx, pIoCtx->rcReq)); + vdIoCtxRootComplete(pDisk, pIoCtx); + } + + if (fFreeCtx) + vdIoCtxFree(pDisk, pIoCtx); + } + } + + return VINF_SUCCESS; +} + +/** + * 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; + PVDISK pDisk = pIoCtx->pDisk; + + LogFlowFunc(("pIoStorage=%#p pIoCtx=%#p pfnComplete=%#p pvUser=%#p cbTransfer=%zu rcReq=%Rrc\n", + pIoStorage, pIoCtx, pfnComplete, pvUser, cbTransfer, rcReq)); + + VD_IS_LOCKED(pDisk); + + Assert(pIoCtx->Req.Io.cbTransferLeft >= cbTransfer); + ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbTransfer); Assert(cbTransfer == (uint32_t)cbTransfer); + ASMAtomicDecU32(&pIoCtx->cDataTransfersPending); + + if (pfnComplete) + rc = pfnComplete(pIoStorage->pVDIo->pBackendData, 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; +} + +static void vdIoCtxContinueDeferredList(PVDIOSTORAGE pIoStorage, PRTLISTANCHOR pListWaiting, + PFNVDXFERCOMPLETED pfnComplete, void *pvUser, int rcReq) +{ + LogFlowFunc(("pIoStorage=%#p pListWaiting=%#p pfnComplete=%#p pvUser=%#p rcReq=%Rrc\n", + pIoStorage, pListWaiting, pfnComplete, pvUser, rcReq)); + + /* Go through the waiting list and continue the I/O contexts. */ + while (!RTListIsEmpty(pListWaiting)) + { + int rc = VINF_SUCCESS; + PVDIOCTXDEFERRED pDeferred = RTListGetFirst(pListWaiting, VDIOCTXDEFERRED, NodeDeferred); + PVDIOCTX pIoCtx = pDeferred->pIoCtx; + RTListNodeRemove(&pDeferred->NodeDeferred); + + RTMemFree(pDeferred); + ASMAtomicDecU32(&pIoCtx->cMetaTransfersPending); + + if (pfnComplete) + rc = pfnComplete(pIoStorage->pVDIo->pBackendData, 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); + } +} + +/** + * Internal - Called when a meta transfer completed. + */ +static int vdMetaXferCompleted(PVDIOSTORAGE pIoStorage, PFNVDXFERCOMPLETED pfnComplete, void *pvUser, + PVDMETAXFER pMetaXfer, int rcReq) +{ + PVDISK pDisk = pIoStorage->pVDIo->pDisk; + RTLISTANCHOR ListIoCtxWaiting; + bool fFlush; + + LogFlowFunc(("pIoStorage=%#p pfnComplete=%#p pvUser=%#p pMetaXfer=%#p rcReq=%Rrc\n", + pIoStorage, pfnComplete, pvUser, pMetaXfer, rcReq)); + + VD_IS_LOCKED(pDisk); + + fFlush = VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_FLUSH; + + if (!fFlush) + { + RTListMove(&ListIoCtxWaiting, &pMetaXfer->ListIoCtxWaiting); + + if (RT_FAILURE(rcReq)) + { + /* Remove from the AVL tree. */ + LogFlow(("Removing meta xfer=%#p\n", pMetaXfer)); + bool fRemoved = RTAvlrFileOffsetRemove(pIoStorage->pTreeMetaXfers, pMetaXfer->Core.Key) != NULL; + Assert(fRemoved); NOREF(fRemoved); + /* If this was a write check if there is a shadow buffer with updated data. */ + if (pMetaXfer->pbDataShw) + { + Assert(VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_WRITE); + Assert(!RTListIsEmpty(&pMetaXfer->ListIoCtxShwWrites)); + RTListConcatenate(&ListIoCtxWaiting, &pMetaXfer->ListIoCtxShwWrites); + RTMemFree(pMetaXfer->pbDataShw); + pMetaXfer->pbDataShw = NULL; + } + RTMemFree(pMetaXfer); + } + else + { + /* Increase the reference counter to make sure it doesn't go away before the last context is processed. */ + pMetaXfer->cRefs++; + } + } + else + RTListMove(&ListIoCtxWaiting, &pMetaXfer->ListIoCtxWaiting); + + VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_NONE); + vdIoCtxContinueDeferredList(pIoStorage, &ListIoCtxWaiting, pfnComplete, pvUser, rcReq); + + /* + * If there is a shadow buffer and the previous write was successful update with the + * new data and trigger a new write. + */ + if ( pMetaXfer->pbDataShw + && RT_SUCCESS(rcReq) + && VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_NONE) + { + LogFlowFunc(("pMetaXfer=%#p Updating from shadow buffer and triggering new write\n", pMetaXfer)); + memcpy(pMetaXfer->abData, pMetaXfer->pbDataShw, pMetaXfer->cbMeta); + RTMemFree(pMetaXfer->pbDataShw); + pMetaXfer->pbDataShw = NULL; + Assert(!RTListIsEmpty(&pMetaXfer->ListIoCtxShwWrites)); + + /* Setup a new I/O write. */ + PVDIOTASK pIoTask = vdIoTaskMetaAlloc(pIoStorage, pfnComplete, pvUser, pMetaXfer); + if (RT_LIKELY(pIoTask)) + { + void *pvTask = NULL; + RTSGSEG Seg; + + Seg.cbSeg = pMetaXfer->cbMeta; + Seg.pvSeg = pMetaXfer->abData; + + VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_WRITE); + rcReq = pIoStorage->pVDIo->pInterfaceIo->pfnWriteAsync(pIoStorage->pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage, + pMetaXfer->Core.Key, &Seg, 1, + pMetaXfer->cbMeta, pIoTask, + &pvTask); + if ( RT_SUCCESS(rcReq) + || rcReq != VERR_VD_ASYNC_IO_IN_PROGRESS) + { + VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_NONE); + vdIoTaskFree(pDisk, pIoTask); + } + else + RTListMove(&pMetaXfer->ListIoCtxWaiting, &pMetaXfer->ListIoCtxShwWrites); + } + else + rcReq = VERR_NO_MEMORY; + + /* Cleanup if there was an error or the request completed already. */ + if (rcReq != VERR_VD_ASYNC_IO_IN_PROGRESS) + vdIoCtxContinueDeferredList(pIoStorage, &pMetaXfer->ListIoCtxShwWrites, pfnComplete, pvUser, rcReq); + } + + /* Remove if not used anymore. */ + if (!fFlush) + { + pMetaXfer->cRefs--; + if (!pMetaXfer->cRefs && RTListIsEmpty(&pMetaXfer->ListIoCtxWaiting)) + { + /* Remove from the AVL tree. */ + LogFlow(("Removing meta xfer=%#p\n", pMetaXfer)); + bool fRemoved = RTAvlrFileOffsetRemove(pIoStorage->pTreeMetaXfers, pMetaXfer->Core.Key) != NULL; + Assert(fRemoved); NOREF(fRemoved); + RTMemFree(pMetaXfer); + } + } + else if (fFlush) + RTMemFree(pMetaXfer); + + return VINF_SUCCESS; +} + +/** + * Processes a list of waiting I/O tasks. The disk lock must be held by caller. + * + * @returns nothing. + * @param pDisk The disk to process the list for. + */ +static void vdIoTaskProcessWaitingList(PVDISK pDisk) +{ + LogFlowFunc(("pDisk=%#p\n", pDisk)); + + VD_IS_LOCKED(pDisk); + + PVDIOTASK pHead = ASMAtomicXchgPtrT(&pDisk->pIoTasksPendingHead, NULL, PVDIOTASK); + + Log(("I/O task list cleared\n")); + + /* Reverse order. */ + PVDIOTASK pCur = pHead; + pHead = NULL; + while (pCur) + { + PVDIOTASK pInsert = pCur; + pCur = pCur->pNext; + pInsert->pNext = pHead; + pHead = pInsert; + } + + while (pHead) + { + PVDIOSTORAGE pIoStorage = pHead->pIoStorage; + + if (!pHead->fMeta) + vdUserXferCompleted(pIoStorage, pHead->Type.User.pIoCtx, + pHead->pfnComplete, pHead->pvUser, + pHead->Type.User.cbTransfer, pHead->rcReq); + else + vdMetaXferCompleted(pIoStorage, pHead->pfnComplete, pHead->pvUser, + pHead->Type.Meta.pMetaXfer, pHead->rcReq); + + pCur = pHead; + pHead = pHead->pNext; + vdIoTaskFree(pDisk, pCur); + } +} + +/** + * Process any I/O context on the halted list. + * + * @returns nothing. + * @param pDisk The disk. + */ +static void vdIoCtxProcessHaltedList(PVDISK pDisk) +{ + LogFlowFunc(("pDisk=%#p\n", pDisk)); + + VD_IS_LOCKED(pDisk); + + /* Get the waiting list and process it in FIFO order. */ + PVDIOCTX pIoCtxHead = ASMAtomicXchgPtrT(&pDisk->pIoCtxHaltedHead, 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) + { + PVDIOCTX pTmp = pCur; + + pCur = pCur->pIoCtxNext; + pTmp->pIoCtxNext = NULL; + + /* Continue */ + pTmp->fFlags &= ~VDIOCTX_FLAGS_BLOCKED; + vdIoCtxContinue(pTmp, pTmp->rcReq); + } +} + +/** + * Unlock the disk and process pending tasks. + * + * @returns VBox status code. + * @param pDisk The disk to unlock. + * @param pIoCtxRc The I/O context to get the status code from, optional. + */ +static int vdDiskUnlock(PVDISK pDisk, PVDIOCTX pIoCtxRc) +{ + int rc = VINF_SUCCESS; + + VD_IS_LOCKED(pDisk); + + /* + * Process the list of waiting I/O tasks first + * because they might complete I/O contexts. + * Same for the list of halted I/O contexts. + * Afterwards comes the list of new I/O contexts. + */ + vdIoTaskProcessWaitingList(pDisk); + vdIoCtxProcessHaltedList(pDisk); + rc = vdDiskProcessWaitingIoCtx(pDisk, pIoCtxRc); + ASMAtomicXchgBool(&pDisk->fLocked, false); + + /* + * Need to check for new I/O tasks and waiting I/O contexts now + * again as other threads might added them while we processed + * previous lists. + */ + while ( ASMAtomicUoReadPtrT(&pDisk->pIoCtxHead, PVDIOCTX) != NULL + || ASMAtomicUoReadPtrT(&pDisk->pIoTasksPendingHead, PVDIOTASK) != NULL + || ASMAtomicUoReadPtrT(&pDisk->pIoCtxHaltedHead, PVDIOCTX) != NULL) + { + /* Try lock disk again. */ + if (ASMAtomicCmpXchgBool(&pDisk->fLocked, true, false)) + { + vdIoTaskProcessWaitingList(pDisk); + vdIoCtxProcessHaltedList(pDisk); + vdDiskProcessWaitingIoCtx(pDisk, NULL); + ASMAtomicXchgBool(&pDisk->fLocked, false); + } + else /* Let the other thread everything when he unlocks the disk. */ + break; + } + + return rc; +} + +/** + * Try to lock the disk to complete pressing of the I/O task. + * The completion is deferred if the disk is locked already. + * + * @returns nothing. + * @param pIoTask The I/O task to complete. + */ +static void vdXferTryLockDiskDeferIoTask(PVDIOTASK pIoTask) +{ + PVDIOSTORAGE pIoStorage = pIoTask->pIoStorage; + PVDISK pDisk = pIoStorage->pVDIo->pDisk; + + Log(("Deferring I/O task pIoTask=%p\n", pIoTask)); + + /* Put it on the waiting list. */ + PVDIOTASK pNext = ASMAtomicUoReadPtrT(&pDisk->pIoTasksPendingHead, PVDIOTASK); + PVDIOTASK pHeadOld; + pIoTask->pNext = pNext; + while (!ASMAtomicCmpXchgExPtr(&pDisk->pIoTasksPendingHead, pIoTask, pNext, &pHeadOld)) + { + pNext = pHeadOld; + Assert(pNext != pIoTask); + pIoTask->pNext = pNext; + ASMNopPause(); + } + + if (ASMAtomicCmpXchgBool(&pDisk->fLocked, true, false)) + { + /* Release disk lock, it will take care of processing all lists. */ + vdDiskUnlock(pDisk, NULL); + } +} + +static DECLCALLBACK(int) vdIOIntReqCompleted(void *pvUser, int rcReq) +{ + PVDIOTASK pIoTask = (PVDIOTASK)pvUser; + + LogFlowFunc(("Task completed pIoTask=%#p\n", pIoTask)); + + pIoTask->rcReq = rcReq; + vdXferTryLockDiskDeferIoTask(pIoTask); + return VINF_SUCCESS; +} + +/** + * VD I/O interface callback for opening a file. + */ +static DECLCALLBACK(int) vdIOIntOpen(void *pvUser, const char *pszLocation, + unsigned uOpenFlags, PPVDIOSTORAGE ppIoStorage) +{ + int rc = VINF_SUCCESS; + PVDIO pVDIo = (PVDIO)pvUser; + PVDIOSTORAGE pIoStorage = (PVDIOSTORAGE)RTMemAllocZ(sizeof(VDIOSTORAGE)); + + if (!pIoStorage) + return VERR_NO_MEMORY; + + /* Create the AVl tree. */ + pIoStorage->pTreeMetaXfers = (PAVLRFOFFTREE)RTMemAllocZ(sizeof(AVLRFOFFTREE)); + if (pIoStorage->pTreeMetaXfers) + { + rc = pVDIo->pInterfaceIo->pfnOpen(pVDIo->pInterfaceIo->Core.pvUser, + pszLocation, uOpenFlags, + vdIOIntReqCompleted, + &pIoStorage->pStorage); + if (RT_SUCCESS(rc)) + { + pIoStorage->pVDIo = pVDIo; + *ppIoStorage = pIoStorage; + return VINF_SUCCESS; + } + + RTMemFree(pIoStorage->pTreeMetaXfers); + } + else + rc = VERR_NO_MEMORY; + + RTMemFree(pIoStorage); + return rc; +} + +static DECLCALLBACK(int) vdIOIntTreeMetaXferDestroy(PAVLRFOFFNODECORE pNode, void *pvUser) +{ + RT_NOREF2(pNode, pvUser); + AssertMsgFailed(("Tree should be empty at this point!\n")); + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) vdIOIntClose(void *pvUser, PVDIOSTORAGE pIoStorage) +{ + int rc = VINF_SUCCESS; + PVDIO pVDIo = (PVDIO)pvUser; + + /* We free everything here, even if closing the file failed for some reason. */ + rc = pVDIo->pInterfaceIo->pfnClose(pVDIo->pInterfaceIo->Core.pvUser, pIoStorage->pStorage); + RTAvlrFileOffsetDestroy(pIoStorage->pTreeMetaXfers, vdIOIntTreeMetaXferDestroy, NULL); + RTMemFree(pIoStorage->pTreeMetaXfers); + RTMemFree(pIoStorage); + return rc; +} + +static DECLCALLBACK(int) vdIOIntDelete(void *pvUser, const char *pcszFilename) +{ + PVDIO pVDIo = (PVDIO)pvUser; + return pVDIo->pInterfaceIo->pfnDelete(pVDIo->pInterfaceIo->Core.pvUser, + pcszFilename); +} + +static DECLCALLBACK(int) vdIOIntMove(void *pvUser, const char *pcszSrc, const char *pcszDst, + unsigned fMove) +{ + PVDIO pVDIo = (PVDIO)pvUser; + return pVDIo->pInterfaceIo->pfnMove(pVDIo->pInterfaceIo->Core.pvUser, + pcszSrc, pcszDst, fMove); +} + +static DECLCALLBACK(int) vdIOIntGetFreeSpace(void *pvUser, const char *pcszFilename, + int64_t *pcbFreeSpace) +{ + PVDIO pVDIo = (PVDIO)pvUser; + return pVDIo->pInterfaceIo->pfnGetFreeSpace(pVDIo->pInterfaceIo->Core.pvUser, + pcszFilename, pcbFreeSpace); +} + +static DECLCALLBACK(int) vdIOIntGetModificationTime(void *pvUser, const char *pcszFilename, + PRTTIMESPEC pModificationTime) +{ + PVDIO pVDIo = (PVDIO)pvUser; + return pVDIo->pInterfaceIo->pfnGetModificationTime(pVDIo->pInterfaceIo->Core.pvUser, + pcszFilename, pModificationTime); +} + +static DECLCALLBACK(int) vdIOIntGetSize(void *pvUser, PVDIOSTORAGE pIoStorage, + uint64_t *pcbSize) +{ + PVDIO pVDIo = (PVDIO)pvUser; + return pVDIo->pInterfaceIo->pfnGetSize(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage, pcbSize); +} + +static DECLCALLBACK(int) vdIOIntSetSize(void *pvUser, PVDIOSTORAGE pIoStorage, + uint64_t cbSize) +{ + PVDIO pVDIo = (PVDIO)pvUser; + return pVDIo->pInterfaceIo->pfnSetSize(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage, cbSize); +} + +static DECLCALLBACK(int) vdIOIntSetAllocationSize(void *pvUser, PVDIOSTORAGE pIoStorage, + uint64_t cbSize, uint32_t fFlags, + PVDINTERFACEPROGRESS pIfProgress, + unsigned uPercentStart, unsigned uPercentSpan) +{ + PVDIO pVDIo = (PVDIO)pvUser; + int rc = pVDIo->pInterfaceIo->pfnSetAllocationSize(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage, cbSize, fFlags); + if (rc == VERR_NOT_SUPPORTED) + { + /* Fallback if the underlying medium does not support optimized storage allocation. */ + uint64_t cbSizeCur = 0; + rc = pVDIo->pInterfaceIo->pfnGetSize(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage, &cbSizeCur); + if (RT_SUCCESS(rc)) + { + if (cbSizeCur < cbSize) + { + const size_t cbBuf = 128 * _1K; + void *pvBuf = RTMemTmpAllocZ(cbBuf); + if (RT_LIKELY(pvBuf)) + { + uint64_t cbFill = cbSize - cbSizeCur; + uint64_t uOff = 0; + + /* Write data to all blocks. */ + while ( uOff < cbFill + && RT_SUCCESS(rc)) + { + size_t cbChunk = (size_t)RT_MIN(cbFill - uOff, cbBuf); + + rc = pVDIo->pInterfaceIo->pfnWriteSync(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage, cbSizeCur + uOff, + pvBuf, cbChunk, NULL); + if (RT_SUCCESS(rc)) + { + uOff += cbChunk; + + rc = vdIfProgress(pIfProgress, uPercentStart + uOff * uPercentSpan / cbFill); + } + } + + RTMemTmpFree(pvBuf); + } + else + rc = VERR_NO_MEMORY; + } + else if (cbSizeCur > cbSize) + rc = pVDIo->pInterfaceIo->pfnSetSize(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage, cbSize); + } + } + + if (RT_SUCCESS(rc)) + rc = vdIfProgress(pIfProgress, uPercentStart + uPercentSpan); + + return rc; +} + +static DECLCALLBACK(int) vdIOIntReadUser(void *pvUser, PVDIOSTORAGE pIoStorage, uint64_t uOffset, + PVDIOCTX pIoCtx, size_t cbRead) +{ + int rc = VINF_SUCCESS; + PVDIO pVDIo = (PVDIO)pvUser; + PVDISK pDisk = pVDIo->pDisk; + + LogFlowFunc(("pvUser=%#p pIoStorage=%#p uOffset=%llu pIoCtx=%#p cbRead=%u\n", + pvUser, pIoStorage, uOffset, pIoCtx, cbRead)); + + /** @todo Enable check for sync I/O later. */ + if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC)) + VD_IS_LOCKED(pDisk); + + Assert(cbRead > 0); + + if (pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC) + { + RTSGSEG Seg; + unsigned cSegments = 1; + size_t cbTaskRead = 0; + + /* Synchronous I/O contexts only have one buffer segment. */ + AssertMsgReturn(pIoCtx->Req.Io.SgBuf.cSegs == 1, + ("Invalid number of buffer segments for synchronous I/O context"), + VERR_INVALID_PARAMETER); + + cbTaskRead = RTSgBufSegArrayCreate(&pIoCtx->Req.Io.SgBuf, &Seg, &cSegments, cbRead); + Assert(cbRead == cbTaskRead); + Assert(cSegments == 1); + rc = pVDIo->pInterfaceIo->pfnReadSync(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage, uOffset, + Seg.pvSeg, cbRead, NULL); + if (RT_SUCCESS(rc)) + { + Assert(cbRead == (uint32_t)cbRead); + ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbRead); + } + } + else + { + /* Build the S/G array and spawn a new I/O task */ + while (cbRead) + { + RTSGSEG aSeg[VD_IO_TASK_SEGMENTS_MAX]; + unsigned cSegments = VD_IO_TASK_SEGMENTS_MAX; + size_t cbTaskRead = RTSgBufSegArrayCreate(&pIoCtx->Req.Io.SgBuf, aSeg, &cSegments, cbRead); + + Assert(cSegments > 0); + Assert(cbTaskRead > 0); + AssertMsg(cbTaskRead <= cbRead, ("Invalid number of bytes to read\n")); + + 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 + + Assert(cbTaskRead == (uint32_t)cbTaskRead); + PVDIOTASK pIoTask = vdIoTaskUserAlloc(pIoStorage, NULL, NULL, pIoCtx, (uint32_t)cbTaskRead); + + if (!pIoTask) + return VERR_NO_MEMORY; + + ASMAtomicIncU32(&pIoCtx->cDataTransfersPending); + + void *pvTask; + Log(("Spawning pIoTask=%p pIoCtx=%p\n", pIoTask, pIoCtx)); + rc = pVDIo->pInterfaceIo->pfnReadAsync(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage, uOffset, + aSeg, cSegments, cbTaskRead, pIoTask, + &pvTask); + if (RT_SUCCESS(rc)) + { + AssertMsg(cbTaskRead <= pIoCtx->Req.Io.cbTransferLeft, ("Impossible!\n")); + ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbTaskRead); + ASMAtomicDecU32(&pIoCtx->cDataTransfersPending); + vdIoTaskFree(pDisk, pIoTask); + } + else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS) + { + ASMAtomicDecU32(&pIoCtx->cDataTransfersPending); + vdIoTaskFree(pDisk, pIoTask); + break; + } + + uOffset += cbTaskRead; + cbRead -= cbTaskRead; + } + } + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) vdIOIntWriteUser(void *pvUser, PVDIOSTORAGE pIoStorage, uint64_t uOffset, + PVDIOCTX pIoCtx, size_t cbWrite, PFNVDXFERCOMPLETED pfnComplete, + void *pvCompleteUser) +{ + int rc = VINF_SUCCESS; + PVDIO pVDIo = (PVDIO)pvUser; + PVDISK pDisk = pVDIo->pDisk; + + LogFlowFunc(("pvUser=%#p pIoStorage=%#p uOffset=%llu pIoCtx=%#p cbWrite=%u\n", + pvUser, pIoStorage, uOffset, pIoCtx, cbWrite)); + + /** @todo Enable check for sync I/O later. */ + if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC)) + VD_IS_LOCKED(pDisk); + + Assert(cbWrite > 0); + + if (pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC) + { + RTSGSEG Seg; + unsigned cSegments = 1; + size_t cbTaskWrite = 0; + + /* Synchronous I/O contexts only have one buffer segment. */ + AssertMsgReturn(pIoCtx->Req.Io.SgBuf.cSegs == 1, + ("Invalid number of buffer segments for synchronous I/O context"), + VERR_INVALID_PARAMETER); + + cbTaskWrite = RTSgBufSegArrayCreate(&pIoCtx->Req.Io.SgBuf, &Seg, &cSegments, cbWrite); + Assert(cbWrite == cbTaskWrite); + Assert(cSegments == 1); + rc = pVDIo->pInterfaceIo->pfnWriteSync(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage, uOffset, + Seg.pvSeg, cbWrite, NULL); + if (RT_SUCCESS(rc)) + { + Assert(pIoCtx->Req.Io.cbTransferLeft >= cbWrite); + ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbWrite); + } + } + else + { + /* 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->Req.Io.SgBuf, aSeg, &cSegments, cbWrite); + + Assert(cSegments > 0); + Assert(cbTaskWrite > 0); + 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 + + Assert(cbTaskWrite == (uint32_t)cbTaskWrite); + PVDIOTASK pIoTask = vdIoTaskUserAlloc(pIoStorage, pfnComplete, pvCompleteUser, pIoCtx, (uint32_t)cbTaskWrite); + + if (!pIoTask) + return VERR_NO_MEMORY; + + ASMAtomicIncU32(&pIoCtx->cDataTransfersPending); + + void *pvTask; + Log(("Spawning pIoTask=%p pIoCtx=%p\n", pIoTask, pIoCtx)); + rc = pVDIo->pInterfaceIo->pfnWriteAsync(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage, + uOffset, aSeg, cSegments, + cbTaskWrite, pIoTask, &pvTask); + if (RT_SUCCESS(rc)) + { + AssertMsg(cbTaskWrite <= pIoCtx->Req.Io.cbTransferLeft, ("Impossible!\n")); + ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbTaskWrite); + ASMAtomicDecU32(&pIoCtx->cDataTransfersPending); + vdIoTaskFree(pDisk, pIoTask); + } + else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS) + { + ASMAtomicDecU32(&pIoCtx->cDataTransfersPending); + vdIoTaskFree(pDisk, pIoTask); + break; + } + + uOffset += cbTaskWrite; + cbWrite -= cbTaskWrite; + } + } + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) vdIOIntReadMeta(void *pvUser, PVDIOSTORAGE pIoStorage, uint64_t uOffset, + void *pvBuf, size_t cbRead, PVDIOCTX pIoCtx, + PPVDMETAXFER ppMetaXfer, PFNVDXFERCOMPLETED pfnComplete, + void *pvCompleteUser) +{ + PVDIO pVDIo = (PVDIO)pvUser; + PVDISK pDisk = pVDIo->pDisk; + int rc = VINF_SUCCESS; + RTSGSEG Seg; + PVDIOTASK pIoTask; + PVDMETAXFER pMetaXfer = NULL; + void *pvTask = NULL; + + LogFlowFunc(("pvUser=%#p pIoStorage=%#p uOffset=%llu pvBuf=%#p cbRead=%u\n", + pvUser, pIoStorage, uOffset, pvBuf, cbRead)); + + AssertMsgReturn( pIoCtx + || (!ppMetaXfer && !pfnComplete && !pvCompleteUser), + ("A synchronous metadata read is requested but the parameters are wrong\n"), + VERR_INVALID_POINTER); + + /** @todo Enable check for sync I/O later. */ + if ( pIoCtx + && !(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC)) + VD_IS_LOCKED(pDisk); + + if ( !pIoCtx + || pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC) + { + /* Handle synchronous metadata I/O. */ + /** @todo Integrate with metadata transfers below. */ + rc = pVDIo->pInterfaceIo->pfnReadSync(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage, uOffset, + pvBuf, cbRead, NULL); + if (ppMetaXfer) + *ppMetaXfer = NULL; + } + else + { + pMetaXfer = (PVDMETAXFER)RTAvlrFileOffsetGet(pIoStorage->pTreeMetaXfers, uOffset); + if (!pMetaXfer) + { +#ifdef RT_STRICT + pMetaXfer = (PVDMETAXFER)RTAvlrFileOffsetGetBestFit(pIoStorage->pTreeMetaXfers, uOffset, false /* fAbove */); + AssertMsg(!pMetaXfer || (pMetaXfer->Core.Key + (RTFOFF)pMetaXfer->cbMeta <= (RTFOFF)uOffset), + ("Overlapping meta transfers!\n")); +#endif + + /* Allocate a new meta transfer. */ + pMetaXfer = vdMetaXferAlloc(pIoStorage, uOffset, cbRead); + if (!pMetaXfer) + return VERR_NO_MEMORY; + + pIoTask = vdIoTaskMetaAlloc(pIoStorage, pfnComplete, pvCompleteUser, 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 = pVDIo->pInterfaceIo->pfnReadAsync(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage, + uOffset, &Seg, 1, + cbRead, pIoTask, &pvTask); + + if (RT_SUCCESS(rc) || rc == VERR_VD_ASYNC_IO_IN_PROGRESS) + { + bool fInserted = RTAvlrFileOffsetInsert(pIoStorage->pTreeMetaXfers, &pMetaXfer->Core); + Assert(fInserted); NOREF(fInserted); + } + else + RTMemFree(pMetaXfer); + + if (RT_SUCCESS(rc)) + { + VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_NONE); + vdIoTaskFree(pDisk, pIoTask); + } + else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS && !pfnComplete) + rc = VERR_VD_NOT_ENOUGH_METADATA; + } + + Assert(VALID_PTR(pMetaXfer) || RT_FAILURE(rc)); + + if (RT_SUCCESS(rc) || rc == VERR_VD_NOT_ENOUGH_METADATA || rc == VERR_VD_ASYNC_IO_IN_PROGRESS) + { + /* 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); + Assert(pMetaXfer->Core.Key == (RTFOFF)uOffset); + if (pMetaXfer->pbDataShw) + memcpy(pvBuf, pMetaXfer->pbDataShw, cbRead); + else + memcpy(pvBuf, pMetaXfer->abData, cbRead); + *ppMetaXfer = pMetaXfer; + } + } + } + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) vdIOIntWriteMeta(void *pvUser, PVDIOSTORAGE pIoStorage, uint64_t uOffset, + const void *pvBuf, size_t cbWrite, PVDIOCTX pIoCtx, + PFNVDXFERCOMPLETED pfnComplete, void *pvCompleteUser) +{ + PVDIO pVDIo = (PVDIO)pvUser; + PVDISK pDisk = pVDIo->pDisk; + int rc = VINF_SUCCESS; + RTSGSEG Seg; + PVDIOTASK pIoTask; + PVDMETAXFER pMetaXfer = NULL; + bool fInTree = false; + void *pvTask = NULL; + + LogFlowFunc(("pvUser=%#p pIoStorage=%#p uOffset=%llu pvBuf=%#p cbWrite=%u\n", + pvUser, pIoStorage, uOffset, pvBuf, cbWrite)); + + AssertMsgReturn( pIoCtx + || (!pfnComplete && !pvCompleteUser), + ("A synchronous metadata write is requested but the parameters are wrong\n"), + VERR_INVALID_POINTER); + + /** @todo Enable check for sync I/O later. */ + if ( pIoCtx + && !(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC)) + VD_IS_LOCKED(pDisk); + + if ( !pIoCtx + || pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC) + { + /* Handle synchronous metadata I/O. */ + /** @todo Integrate with metadata transfers below. */ + rc = pVDIo->pInterfaceIo->pfnWriteSync(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage, uOffset, + pvBuf, cbWrite, NULL); + } + else + { + pMetaXfer = (PVDMETAXFER)RTAvlrFileOffsetGet(pIoStorage->pTreeMetaXfers, uOffset); + if (!pMetaXfer) + { + /* Allocate a new meta transfer. */ + pMetaXfer = vdMetaXferAlloc(pIoStorage, uOffset, cbWrite); + if (!pMetaXfer) + return VERR_NO_MEMORY; + } + else + { + Assert(pMetaXfer->cbMeta >= cbWrite); + Assert(pMetaXfer->Core.Key == (RTFOFF)uOffset); + fInTree = true; + } + + if (VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_NONE) + { + 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 = pVDIo->pInterfaceIo->pfnWriteAsync(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->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); + if (fInTree && !pMetaXfer->cRefs) + { + LogFlow(("Removing meta xfer=%#p\n", pMetaXfer)); + bool fRemoved = RTAvlrFileOffsetRemove(pIoStorage->pTreeMetaXfers, pMetaXfer->Core.Key) != NULL; + AssertMsg(fRemoved, ("Metadata transfer wasn't removed\n")); NOREF(fRemoved); + 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; + + if (!fInTree) + { + bool fInserted = RTAvlrFileOffsetInsert(pIoStorage->pTreeMetaXfers, &pMetaXfer->Core); + Assert(fInserted); NOREF(fInserted); + } + + RTListAppend(&pMetaXfer->ListIoCtxWaiting, &pDeferred->NodeDeferred); + } + else + { + RTMemFree(pMetaXfer); + pMetaXfer = NULL; + } + } + else + { + /* I/O is in progress, update shadow buffer and add to waiting list. */ + Assert(VDMETAXFER_TXDIR_GET(pMetaXfer->fFlags) == VDMETAXFER_TXDIR_WRITE); + if (!pMetaXfer->pbDataShw) + { + /* Allocate shadow buffer and set initial state. */ + LogFlowFunc(("pMetaXfer=%#p Creating shadow buffer\n", pMetaXfer)); + pMetaXfer->pbDataShw = (uint8_t *)RTMemAlloc(pMetaXfer->cbMeta); + if (RT_LIKELY(pMetaXfer->pbDataShw)) + memcpy(pMetaXfer->pbDataShw, pMetaXfer->abData, pMetaXfer->cbMeta); + else + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + /* Update with written data and append to waiting list. */ + PVDIOCTXDEFERRED pDeferred = (PVDIOCTXDEFERRED)RTMemAllocZ(sizeof(VDIOCTXDEFERRED)); + if (pDeferred) + { + LogFlowFunc(("pMetaXfer=%#p Updating shadow buffer\n", pMetaXfer)); + + RTListInit(&pDeferred->NodeDeferred); + pDeferred->pIoCtx = pIoCtx; + ASMAtomicIncU32(&pIoCtx->cMetaTransfersPending); + memcpy(pMetaXfer->pbDataShw, pvBuf, cbWrite); + RTListAppend(&pMetaXfer->ListIoCtxShwWrites, &pDeferred->NodeDeferred); + } + else + { + /* + * Free shadow buffer if there is no one depending on it, i.e. + * we just allocated it. + */ + if (RTListIsEmpty(&pMetaXfer->ListIoCtxShwWrites)) + { + RTMemFree(pMetaXfer->pbDataShw); + pMetaXfer->pbDataShw = NULL; + } + rc = VERR_NO_MEMORY; + } + } + } + } + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(void) vdIOIntMetaXferRelease(void *pvUser, PVDMETAXFER pMetaXfer) +{ + PVDIO pVDIo = (PVDIO)pvUser; + PVDISK pDisk = pVDIo->pDisk; + PVDIOSTORAGE pIoStorage; + + /* + * It is possible that we get called with a NULL metadata xfer handle + * for synchronous I/O. Just exit. + */ + if (!pMetaXfer) + return; + + pIoStorage = pMetaXfer->pIoStorage; + + VD_IS_LOCKED(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. */ + LogFlow(("Removing meta xfer=%#p\n", pMetaXfer)); + bool fRemoved = RTAvlrFileOffsetRemove(pIoStorage->pTreeMetaXfers, pMetaXfer->Core.Key) != NULL; + AssertMsg(fRemoved, ("Metadata transfer wasn't removed\n")); NOREF(fRemoved); + + RTMemFree(pMetaXfer); + } +} + +static DECLCALLBACK(int) vdIOIntFlush(void *pvUser, PVDIOSTORAGE pIoStorage, PVDIOCTX pIoCtx, + PFNVDXFERCOMPLETED pfnComplete, void *pvCompleteUser) +{ + PVDIO pVDIo = (PVDIO)pvUser; + PVDISK pDisk = pVDIo->pDisk; + int rc = VINF_SUCCESS; + PVDIOTASK pIoTask; + PVDMETAXFER pMetaXfer = NULL; + void *pvTask = NULL; + + LogFlowFunc(("pvUser=%#p pIoStorage=%#p pIoCtx=%#p\n", + pvUser, pIoStorage, pIoCtx)); + + AssertMsgReturn( pIoCtx + || (!pfnComplete && !pvCompleteUser), + ("A synchronous metadata write is requested but the parameters are wrong\n"), + VERR_INVALID_POINTER); + + /** @todo Enable check for sync I/O later. */ + if ( pIoCtx + && !(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC)) + VD_IS_LOCKED(pDisk); + + if (pVDIo->fIgnoreFlush) + return VINF_SUCCESS; + + if ( !pIoCtx + || pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC) + { + /* Handle synchronous flushes. */ + /** @todo Integrate with metadata transfers below. */ + rc = pVDIo->pInterfaceIo->pfnFlushSync(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage); + } + else + { + /* Allocate a new meta transfer. */ + pMetaXfer = vdMetaXferAlloc(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); + + 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 = pVDIo->pInterfaceIo->pfnFlushAsync(pVDIo->pInterfaceIo->Core.pvUser, + pIoStorage->pStorage, + pIoTask, &pvTask); + if (RT_SUCCESS(rc)) + { + VDMETAXFER_TXDIR_SET(pMetaXfer->fFlags, VDMETAXFER_TXDIR_NONE); + ASMAtomicDecU32(&pIoCtx->cMetaTransfersPending); + vdIoTaskFree(pDisk, pIoTask); + RTMemFree(pDeferred); + RTMemFree(pMetaXfer); + } + else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS) + RTMemFree(pMetaXfer); + } + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(size_t) vdIOIntIoCtxCopyTo(void *pvUser, PVDIOCTX pIoCtx, + const void *pvBuf, size_t cbBuf) +{ + PVDIO pVDIo = (PVDIO)pvUser; + PVDISK pDisk = pVDIo->pDisk; + size_t cbCopied = 0; + + /** @todo Enable check for sync I/O later. */ + if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC)) + VD_IS_LOCKED(pDisk); + + cbCopied = vdIoCtxCopyTo(pIoCtx, (uint8_t *)pvBuf, cbBuf); + Assert(cbCopied == cbBuf); + + /// @todo Assert(pIoCtx->Req.Io.cbTransferLeft >= cbCopied); - triggers with vdCopyHelper/dmgRead. + ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbCopied); + + return cbCopied; +} + +static DECLCALLBACK(size_t) vdIOIntIoCtxCopyFrom(void *pvUser, PVDIOCTX pIoCtx, + void *pvBuf, size_t cbBuf) +{ + PVDIO pVDIo = (PVDIO)pvUser; + PVDISK pDisk = pVDIo->pDisk; + size_t cbCopied = 0; + + /** @todo Enable check for sync I/O later. */ + if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC)) + VD_IS_LOCKED(pDisk); + + cbCopied = vdIoCtxCopyFrom(pIoCtx, (uint8_t *)pvBuf, cbBuf); + Assert(cbCopied == cbBuf); + + /// @todo Assert(pIoCtx->Req.Io.cbTransferLeft > cbCopied); - triggers with vdCopyHelper/dmgRead. + ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbCopied); + + return cbCopied; +} + +static DECLCALLBACK(size_t) vdIOIntIoCtxSet(void *pvUser, PVDIOCTX pIoCtx, int ch, size_t cb) +{ + PVDIO pVDIo = (PVDIO)pvUser; + PVDISK pDisk = pVDIo->pDisk; + size_t cbSet = 0; + + /** @todo Enable check for sync I/O later. */ + if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC)) + VD_IS_LOCKED(pDisk); + + cbSet = vdIoCtxSet(pIoCtx, ch, cb); + Assert(cbSet == cb); + + /// @todo Assert(pIoCtx->Req.Io.cbTransferLeft >= cbSet); - triggers with vdCopyHelper/dmgRead. + ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbSet); + + return cbSet; +} + +static DECLCALLBACK(size_t) vdIOIntIoCtxSegArrayCreate(void *pvUser, PVDIOCTX pIoCtx, + PRTSGSEG paSeg, unsigned *pcSeg, + size_t cbData) +{ + PVDIO pVDIo = (PVDIO)pvUser; + PVDISK pDisk = pVDIo->pDisk; + size_t cbCreated = 0; + + /** @todo It is possible that this gets called from a filter plugin + * outside of the disk lock. Refine assertion or remove completely. */ +#if 0 + /** @todo Enable check for sync I/O later. */ + if (!(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC)) + VD_IS_LOCKED(pDisk); +#else + NOREF(pDisk); +#endif + + cbCreated = RTSgBufSegArrayCreate(&pIoCtx->Req.Io.SgBuf, paSeg, pcSeg, cbData); + Assert(!paSeg || cbData == cbCreated); + + return cbCreated; +} + +static DECLCALLBACK(void) vdIOIntIoCtxCompleted(void *pvUser, PVDIOCTX pIoCtx, int rcReq, + size_t cbCompleted) +{ + PVDIO pVDIo = (PVDIO)pvUser; + PVDISK pDisk = pVDIo->pDisk; + + LogFlowFunc(("pvUser=%#p pIoCtx=%#p rcReq=%Rrc cbCompleted=%zu\n", + pvUser, pIoCtx, rcReq, cbCompleted)); + + /* + * Grab the disk critical section to avoid races with other threads which + * might still modify the I/O context. + * Example is that iSCSI is doing an asynchronous write but calls us already + * while the other thread is still hanging in vdWriteHelperAsync and couldn't update + * the blocked state yet. + * It can overwrite the state to true before we call vdIoCtxContinue and the + * the request would hang indefinite. + */ + ASMAtomicCmpXchgS32(&pIoCtx->rcReq, rcReq, VINF_SUCCESS); + Assert(pIoCtx->Req.Io.cbTransferLeft >= cbCompleted); + ASMAtomicSubU32(&pIoCtx->Req.Io.cbTransferLeft, (uint32_t)cbCompleted); + + /* Set next transfer function if the current one finished. + * @todo: Find a better way to prevent vdIoCtxContinue from calling the current helper again. */ + if (!pIoCtx->Req.Io.cbTransferLeft) + { + pIoCtx->pfnIoCtxTransfer = pIoCtx->pfnIoCtxTransferNext; + pIoCtx->pfnIoCtxTransferNext = NULL; + } + + vdIoCtxAddToWaitingList(&pDisk->pIoCtxHaltedHead, pIoCtx); + if (ASMAtomicCmpXchgBool(&pDisk->fLocked, true, false)) + { + /* Immediately drop the lock again, it will take care of processing the list. */ + vdDiskUnlock(pDisk, NULL); + } +} + +static DECLCALLBACK(bool) vdIOIntIoCtxIsSynchronous(void *pvUser, PVDIOCTX pIoCtx) +{ + NOREF(pvUser); + return !!(pIoCtx->fFlags & VDIOCTX_FLAGS_SYNC); +} + +static DECLCALLBACK(bool) vdIOIntIoCtxIsZero(void *pvUser, PVDIOCTX pIoCtx, size_t cbCheck, + bool fAdvance) +{ + NOREF(pvUser); + + bool fIsZero = RTSgBufIsZero(&pIoCtx->Req.Io.SgBuf, cbCheck); + if (fIsZero && fAdvance) + RTSgBufAdvance(&pIoCtx->Req.Io.SgBuf, cbCheck); + + return fIsZero; +} + +static DECLCALLBACK(size_t) vdIOIntIoCtxGetDataUnitSize(void *pvUser, PVDIOCTX pIoCtx) +{ + RT_NOREF1(pIoCtx); + PVDIO pVDIo = (PVDIO)pvUser; + PVDISK pDisk = pVDIo->pDisk; + size_t cbSector = 0; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, VD_LAST_IMAGE); + AssertPtrReturn(pImage, 0); + + PCVDREGIONLIST pRegionList = NULL; + int rc = pImage->Backend->pfnQueryRegions(pImage->pBackendData, &pRegionList); + if (RT_SUCCESS(rc)) + { + cbSector = pRegionList->aRegions[0].cbBlock; + + AssertPtr(pImage->Backend->pfnRegionListRelease); + pImage->Backend->pfnRegionListRelease(pImage->pBackendData, pRegionList); + } + + return cbSector; +} + +/** + * VD I/O interface callback for opening a file (limited version for VDGetFormat). + */ +static DECLCALLBACK(int) vdIOIntOpenLimited(void *pvUser, const char *pszLocation, + uint32_t fOpen, PPVDIOSTORAGE ppIoStorage) +{ + int rc = VINF_SUCCESS; + PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser; + PVDIOSTORAGE pIoStorage = (PVDIOSTORAGE)RTMemAllocZ(sizeof(VDIOSTORAGE)); + + if (!pIoStorage) + return VERR_NO_MEMORY; + + rc = pInterfaceIo->pfnOpen(NULL, pszLocation, fOpen, NULL, &pIoStorage->pStorage); + if (RT_SUCCESS(rc)) + *ppIoStorage = pIoStorage; + else + RTMemFree(pIoStorage); + + return rc; +} + +static DECLCALLBACK(int) vdIOIntCloseLimited(void *pvUser, PVDIOSTORAGE pIoStorage) +{ + PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser; + int rc = pInterfaceIo->pfnClose(NULL, pIoStorage->pStorage); + + RTMemFree(pIoStorage); + return rc; +} + +static DECLCALLBACK(int) vdIOIntDeleteLimited(void *pvUser, const char *pcszFilename) +{ + PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser; + return pInterfaceIo->pfnDelete(NULL, pcszFilename); +} + +static DECLCALLBACK(int) vdIOIntMoveLimited(void *pvUser, const char *pcszSrc, + const char *pcszDst, unsigned fMove) +{ + PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser; + return pInterfaceIo->pfnMove(NULL, pcszSrc, pcszDst, fMove); +} + +static DECLCALLBACK(int) vdIOIntGetFreeSpaceLimited(void *pvUser, const char *pcszFilename, + int64_t *pcbFreeSpace) +{ + PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser; + return pInterfaceIo->pfnGetFreeSpace(NULL, pcszFilename, pcbFreeSpace); +} + +static DECLCALLBACK(int) vdIOIntGetModificationTimeLimited(void *pvUser, + const char *pcszFilename, + PRTTIMESPEC pModificationTime) +{ + PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser; + return pInterfaceIo->pfnGetModificationTime(NULL, pcszFilename, pModificationTime); +} + +static DECLCALLBACK(int) vdIOIntGetSizeLimited(void *pvUser, PVDIOSTORAGE pIoStorage, + uint64_t *pcbSize) +{ + PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser; + return pInterfaceIo->pfnGetSize(NULL, pIoStorage->pStorage, pcbSize); +} + +static DECLCALLBACK(int) vdIOIntSetSizeLimited(void *pvUser, PVDIOSTORAGE pIoStorage, + uint64_t cbSize) +{ + PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser; + return pInterfaceIo->pfnSetSize(NULL, pIoStorage->pStorage, cbSize); +} + +static DECLCALLBACK(int) vdIOIntWriteUserLimited(void *pvUser, PVDIOSTORAGE pStorage, + uint64_t uOffset, PVDIOCTX pIoCtx, + size_t cbWrite, + PFNVDXFERCOMPLETED pfnComplete, + void *pvCompleteUser) +{ + NOREF(pvUser); + NOREF(pStorage); + NOREF(uOffset); + NOREF(pIoCtx); + NOREF(cbWrite); + NOREF(pfnComplete); + NOREF(pvCompleteUser); + AssertMsgFailedReturn(("This needs to be implemented when called\n"), VERR_NOT_IMPLEMENTED); +} + +static DECLCALLBACK(int) vdIOIntReadUserLimited(void *pvUser, PVDIOSTORAGE pStorage, + uint64_t uOffset, PVDIOCTX pIoCtx, + size_t cbRead) +{ + NOREF(pvUser); + NOREF(pStorage); + NOREF(uOffset); + NOREF(pIoCtx); + NOREF(cbRead); + AssertMsgFailedReturn(("This needs to be implemented when called\n"), VERR_NOT_IMPLEMENTED); +} + +static DECLCALLBACK(int) vdIOIntWriteMetaLimited(void *pvUser, PVDIOSTORAGE pStorage, + uint64_t uOffset, const void *pvBuffer, + size_t cbBuffer, PVDIOCTX pIoCtx, + PFNVDXFERCOMPLETED pfnComplete, + void *pvCompleteUser) +{ + PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser; + + AssertMsgReturn(!pIoCtx && !pfnComplete && !pvCompleteUser, + ("Async I/O not implemented for the limited interface"), + VERR_NOT_SUPPORTED); + + return pInterfaceIo->pfnWriteSync(NULL, pStorage->pStorage, uOffset, pvBuffer, cbBuffer, NULL); +} + +static DECLCALLBACK(int) vdIOIntReadMetaLimited(void *pvUser, PVDIOSTORAGE pStorage, + uint64_t uOffset, void *pvBuffer, + size_t cbBuffer, PVDIOCTX pIoCtx, + PPVDMETAXFER ppMetaXfer, + PFNVDXFERCOMPLETED pfnComplete, + void *pvCompleteUser) +{ + PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser; + + AssertMsgReturn(!pIoCtx && !ppMetaXfer && !pfnComplete && !pvCompleteUser, + ("Async I/O not implemented for the limited interface"), + VERR_NOT_SUPPORTED); + + return pInterfaceIo->pfnReadSync(NULL, pStorage->pStorage, uOffset, pvBuffer, cbBuffer, NULL); +} + +#if 0 /* unsed */ +static int vdIOIntMetaXferReleaseLimited(void *pvUser, PVDMETAXFER pMetaXfer) +{ + /* This is a NOP in this case. */ + NOREF(pvUser); + NOREF(pMetaXfer); + return VINF_SUCCESS; +} +#endif + +static DECLCALLBACK(int) vdIOIntFlushLimited(void *pvUser, PVDIOSTORAGE pStorage, + PVDIOCTX pIoCtx, + PFNVDXFERCOMPLETED pfnComplete, + void *pvCompleteUser) +{ + PVDINTERFACEIO pInterfaceIo = (PVDINTERFACEIO)pvUser; + + AssertMsgReturn(!pIoCtx && !pfnComplete && !pvCompleteUser, + ("Async I/O not implemented for the limited interface"), + VERR_NOT_SUPPORTED); + + return pInterfaceIo->pfnFlushSync(NULL, pStorage->pStorage); +} + +/** + * internal: send output to the log (unconditionally). + */ +static DECLCALLBACK(int) vdLogMessage(void *pvUser, const char *pszFormat, va_list args) +{ + NOREF(pvUser); + RTLogPrintfV(pszFormat, args); + return VINF_SUCCESS; +} + +DECLINLINE(int) vdMessageWrapper(PVDISK pDisk, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = pDisk->pInterfaceError->pfnMessage(pDisk->pInterfaceError->Core.pvUser, + pszFormat, va); + va_end(va); + return rc; +} + + +/** + * internal: adjust PCHS geometry + */ +static void vdFixupPCHSGeometry(PVDGEOMETRY pPCHS, uint64_t cbSize) +{ + /* Fix broken PCHS geometry. Can happen for two reasons: either the backend + * mixes up PCHS and LCHS, or the application used to create the source + * image has put garbage in it. Additionally, if the PCHS geometry covers + * more than the image size, set it back to the default. */ + if ( pPCHS->cHeads > 16 + || pPCHS->cSectors > 63 + || pPCHS->cCylinders == 0 + || (uint64_t)pPCHS->cHeads * pPCHS->cSectors * pPCHS->cCylinders * 512 > cbSize) + { + Assert(!(RT_MIN(cbSize / 512 / 16 / 63, 16383) - (uint32_t)RT_MIN(cbSize / 512 / 16 / 63, 16383))); + pPCHS->cCylinders = (uint32_t)RT_MIN(cbSize / 512 / 16 / 63, 16383); + pPCHS->cHeads = 16; + pPCHS->cSectors = 63; + } +} + +/** + * internal: adjust LCHS geometry + */ +static void vdFixupLCHSGeometry(PVDGEOMETRY pLCHS, uint64_t cbSize) +{ + /* Fix broken LCHS geometry. Can happen for two reasons: either the backend + * mixes up PCHS and LCHS, or the application used to create the source + * image has put garbage in it. The fix in this case is to clear the LCHS + * geometry to trigger autodetection when it is used next. If the geometry + * already says "please autodetect" (cylinders=0) keep it. */ + if ( ( pLCHS->cHeads > 255 + || pLCHS->cHeads == 0 + || pLCHS->cSectors > 63 + || pLCHS->cSectors == 0) + && pLCHS->cCylinders != 0) + { + pLCHS->cCylinders = 0; + pLCHS->cHeads = 0; + pLCHS->cSectors = 0; + } + /* Always recompute the number of cylinders stored in the LCHS + * geometry if it isn't set to "autotedetect" at the moment. + * This is very useful if the destination image size is + * larger or smaller than the source image size. Do not modify + * the number of heads and sectors. Windows guests hate it. */ + if ( pLCHS->cCylinders != 0 + && pLCHS->cHeads != 0 /* paranoia */ + && pLCHS->cSectors != 0 /* paranoia */) + { + Assert(!(RT_MIN(cbSize / 512 / pLCHS->cHeads / pLCHS->cSectors, 1024) - (uint32_t)RT_MIN(cbSize / 512 / pLCHS->cHeads / pLCHS->cSectors, 1024))); + pLCHS->cCylinders = (uint32_t)RT_MIN(cbSize / 512 / pLCHS->cHeads / pLCHS->cSectors, 1024); + } +} + +/** + * Sets the I/O callbacks of the given interface to the fallback methods + * + * @returns nothing. + * @param pIfIo The I/O interface to setup. + */ +static void vdIfIoFallbackCallbacksSetup(PVDINTERFACEIO pIfIo) +{ + pIfIo->pfnOpen = vdIOOpenFallback; + pIfIo->pfnClose = vdIOCloseFallback; + pIfIo->pfnDelete = vdIODeleteFallback; + pIfIo->pfnMove = vdIOMoveFallback; + pIfIo->pfnGetFreeSpace = vdIOGetFreeSpaceFallback; + pIfIo->pfnGetModificationTime = vdIOGetModificationTimeFallback; + pIfIo->pfnGetSize = vdIOGetSizeFallback; + pIfIo->pfnSetSize = vdIOSetSizeFallback; + pIfIo->pfnSetAllocationSize = vdIOSetAllocationSizeFallback; + pIfIo->pfnReadSync = vdIOReadSyncFallback; + pIfIo->pfnWriteSync = vdIOWriteSyncFallback; + pIfIo->pfnFlushSync = vdIOFlushSyncFallback; + pIfIo->pfnReadAsync = vdIOReadAsyncFallback; + pIfIo->pfnWriteAsync = vdIOWriteAsyncFallback; + pIfIo->pfnFlushAsync = vdIOFlushAsyncFallback; +} + +/** + * Sets the internal I/O callbacks of the given interface. + * + * @returns nothing. + * @param pIfIoInt The internal I/O interface to setup. + */ +static void vdIfIoIntCallbacksSetup(PVDINTERFACEIOINT pIfIoInt) +{ + pIfIoInt->pfnOpen = vdIOIntOpen; + pIfIoInt->pfnClose = vdIOIntClose; + pIfIoInt->pfnDelete = vdIOIntDelete; + pIfIoInt->pfnMove = vdIOIntMove; + pIfIoInt->pfnGetFreeSpace = vdIOIntGetFreeSpace; + pIfIoInt->pfnGetModificationTime = vdIOIntGetModificationTime; + pIfIoInt->pfnGetSize = vdIOIntGetSize; + pIfIoInt->pfnSetSize = vdIOIntSetSize; + pIfIoInt->pfnSetAllocationSize = vdIOIntSetAllocationSize; + pIfIoInt->pfnReadUser = vdIOIntReadUser; + pIfIoInt->pfnWriteUser = vdIOIntWriteUser; + pIfIoInt->pfnReadMeta = vdIOIntReadMeta; + pIfIoInt->pfnWriteMeta = vdIOIntWriteMeta; + pIfIoInt->pfnMetaXferRelease = vdIOIntMetaXferRelease; + pIfIoInt->pfnFlush = vdIOIntFlush; + pIfIoInt->pfnIoCtxCopyFrom = vdIOIntIoCtxCopyFrom; + pIfIoInt->pfnIoCtxCopyTo = vdIOIntIoCtxCopyTo; + pIfIoInt->pfnIoCtxSet = vdIOIntIoCtxSet; + pIfIoInt->pfnIoCtxSegArrayCreate = vdIOIntIoCtxSegArrayCreate; + pIfIoInt->pfnIoCtxCompleted = vdIOIntIoCtxCompleted; + pIfIoInt->pfnIoCtxIsSynchronous = vdIOIntIoCtxIsSynchronous; + pIfIoInt->pfnIoCtxIsZero = vdIOIntIoCtxIsZero; + pIfIoInt->pfnIoCtxGetDataUnitSize = vdIOIntIoCtxGetDataUnitSize; +} + +/** + * Internally used completion handler for synchronous I/O contexts. + */ +static DECLCALLBACK(void) vdIoCtxSyncComplete(void *pvUser1, void *pvUser2, int rcReq) +{ + RT_NOREF2(pvUser1, rcReq); + RTSEMEVENT hEvent = (RTSEMEVENT)pvUser2; + + RTSemEventSignal(hEvent); +} + +/** + * Initializes HDD backends. + * + * @returns VBox status code. + */ +VBOXDDU_DECL(int) VDInit(void) +{ + int rc = vdPluginInit(); + LogRel(("VD: VDInit finished with %Rrc\n", rc)); + return rc; +} + +/** + * Destroys loaded HDD backends. + * + * @returns VBox status code. + */ +VBOXDDU_DECL(int) VDShutdown(void) +{ + return vdPluginTerm(); +} + +/** + * Loads a single plugin given by filename. + * + * @returns VBox status code. + * @param pszFilename The plugin filename to load. + */ +VBOXDDU_DECL(int) VDPluginLoadFromFilename(const char *pszFilename) +{ + if (!vdPluginIsInitialized()) + { + int rc = VDInit(); + if (RT_FAILURE(rc)) + return rc; + } + + return vdPluginLoadFromFilename(pszFilename); +} + +/** + * Load all plugins from a given path. + * + * @returns VBox statuse code. + * @param pszPath The path to load plugins from. + */ +VBOXDDU_DECL(int) VDPluginLoadFromPath(const char *pszPath) +{ + if (!vdPluginIsInitialized()) + { + int rc = VDInit(); + if (RT_FAILURE(rc)) + return rc; + } + + return vdPluginLoadFromPath(pszPath); +} + +/** + * Unloads a single plugin given by filename. + * + * @returns VBox status code. + * @param pszFilename The plugin filename to unload. + */ +VBOXDDU_DECL(int) VDPluginUnloadFromFilename(const char *pszFilename) +{ + if (!vdPluginIsInitialized()) + { + int rc = VDInit(); + if (RT_FAILURE(rc)) + return rc; + } + + return vdPluginUnloadFromFilename(pszFilename); +} + +/** + * Unload all plugins from a given path. + * + * @returns VBox statuse code. + * @param pszPath The path to unload plugins from. + */ +VBOXDDU_DECL(int) VDPluginUnloadFromPath(const char *pszPath) +{ + if (!vdPluginIsInitialized()) + { + int rc = VDInit(); + if (RT_FAILURE(rc)) + return rc; + } + + return vdPluginUnloadFromPath(pszPath); +} + +/** + * Lists all HDD backends and their capabilities in a caller-provided buffer. + * + * @returns VBox status code. + * VERR_BUFFER_OVERFLOW if not enough space is passed. + * @param cEntriesAlloc Number of list entries available. + * @param pEntries Pointer to array for the entries. + * @param pcEntriesUsed Number of entries returned. + */ +VBOXDDU_DECL(int) VDBackendInfo(unsigned cEntriesAlloc, PVDBACKENDINFO pEntries, + unsigned *pcEntriesUsed) +{ + int rc = VINF_SUCCESS; + + LogFlowFunc(("cEntriesAlloc=%u pEntries=%#p pcEntriesUsed=%#p\n", cEntriesAlloc, pEntries, pcEntriesUsed)); + /* Check arguments. */ + AssertMsgReturn(cEntriesAlloc, + ("cEntriesAlloc=%u\n", cEntriesAlloc), + VERR_INVALID_PARAMETER); + AssertMsgReturn(VALID_PTR(pEntries), + ("pEntries=%#p\n", pEntries), + VERR_INVALID_PARAMETER); + AssertMsgReturn(VALID_PTR(pcEntriesUsed), + ("pcEntriesUsed=%#p\n", pcEntriesUsed), + VERR_INVALID_PARAMETER); + if (!vdPluginIsInitialized()) + VDInit(); + + uint32_t cBackends = vdGetImageBackendCount(); + if (cEntriesAlloc < cBackends) + { + *pcEntriesUsed = cBackends; + return VERR_BUFFER_OVERFLOW; + } + + for (unsigned i = 0; i < cBackends; i++) + { + PCVDIMAGEBACKEND pBackend; + rc = vdQueryImageBackend(i, &pBackend); + AssertRC(rc); + + pEntries[i].pszBackend = pBackend->pszBackendName; + pEntries[i].uBackendCaps = pBackend->uBackendCaps; + pEntries[i].paFileExtensions = pBackend->paFileExtensions; + pEntries[i].paConfigInfo = pBackend->paConfigInfo; + pEntries[i].pfnComposeLocation = pBackend->pfnComposeLocation; + pEntries[i].pfnComposeName = pBackend->pfnComposeName; + } + + LogFlowFunc(("returns %Rrc *pcEntriesUsed=%u\n", rc, cBackends)); + *pcEntriesUsed = cBackends; + return rc; +} + +/** + * Lists the capabilities of a backend identified by its name. + * + * @returns VBox status code. + * @param pszBackend The backend name. + * @param pEntry Pointer to an entry. + */ +VBOXDDU_DECL(int) VDBackendInfoOne(const char *pszBackend, PVDBACKENDINFO pEntry) +{ + LogFlowFunc(("pszBackend=%#p pEntry=%#p\n", pszBackend, pEntry)); + /* Check arguments. */ + AssertMsgReturn(VALID_PTR(pszBackend), + ("pszBackend=%#p\n", pszBackend), + VERR_INVALID_PARAMETER); + AssertMsgReturn(VALID_PTR(pEntry), + ("pEntry=%#p\n", pEntry), + VERR_INVALID_PARAMETER); + if (!vdPluginIsInitialized()) + VDInit(); + + PCVDIMAGEBACKEND pBackend; + int rc = vdFindImageBackend(pszBackend, &pBackend); + if (RT_SUCCESS(rc)) + { + pEntry->pszBackend = pBackend->pszBackendName; + pEntry->uBackendCaps = pBackend->uBackendCaps; + pEntry->paFileExtensions = pBackend->paFileExtensions; + pEntry->paConfigInfo = pBackend->paConfigInfo; + } + + return rc; +} + +/** + * Lists all filters and their capabilities in a caller-provided buffer. + * + * @return VBox status code. + * VERR_BUFFER_OVERFLOW if not enough space is passed. + * @param cEntriesAlloc Number of list entries available. + * @param pEntries Pointer to array for the entries. + * @param pcEntriesUsed Number of entries returned. + */ +VBOXDDU_DECL(int) VDFilterInfo(unsigned cEntriesAlloc, PVDFILTERINFO pEntries, + unsigned *pcEntriesUsed) +{ + int rc = VINF_SUCCESS; + + LogFlowFunc(("cEntriesAlloc=%u pEntries=%#p pcEntriesUsed=%#p\n", cEntriesAlloc, pEntries, pcEntriesUsed)); + /* Check arguments. */ + AssertMsgReturn(cEntriesAlloc, + ("cEntriesAlloc=%u\n", cEntriesAlloc), + VERR_INVALID_PARAMETER); + AssertMsgReturn(VALID_PTR(pEntries), + ("pEntries=%#p\n", pEntries), + VERR_INVALID_PARAMETER); + AssertMsgReturn(VALID_PTR(pcEntriesUsed), + ("pcEntriesUsed=%#p\n", pcEntriesUsed), + VERR_INVALID_PARAMETER); + if (!vdPluginIsInitialized()) + VDInit(); + + uint32_t cBackends = vdGetFilterBackendCount(); + if (cEntriesAlloc < cBackends) + { + *pcEntriesUsed = cBackends; + return VERR_BUFFER_OVERFLOW; + } + + for (unsigned i = 0; i < cBackends; i++) + { + PCVDFILTERBACKEND pBackend; + rc = vdQueryFilterBackend(i, &pBackend); + pEntries[i].pszFilter = pBackend->pszBackendName; + pEntries[i].paConfigInfo = pBackend->paConfigInfo; + } + + LogFlowFunc(("returns %Rrc *pcEntriesUsed=%u\n", rc, cBackends)); + *pcEntriesUsed = cBackends; + return rc; +} + +/** + * Lists the capabilities of a filter identified by its name. + * + * @return VBox status code. + * @param pszFilter The filter name (case insensitive). + * @param pEntry Pointer to an entry. + */ +VBOXDDU_DECL(int) VDFilterInfoOne(const char *pszFilter, PVDFILTERINFO pEntry) +{ + LogFlowFunc(("pszFilter=%#p pEntry=%#p\n", pszFilter, pEntry)); + /* Check arguments. */ + AssertMsgReturn(VALID_PTR(pszFilter), + ("pszFilter=%#p\n", pszFilter), + VERR_INVALID_PARAMETER); + AssertMsgReturn(VALID_PTR(pEntry), + ("pEntry=%#p\n", pEntry), + VERR_INVALID_PARAMETER); + if (!vdPluginIsInitialized()) + VDInit(); + + PCVDFILTERBACKEND pBackend; + int rc = vdFindFilterBackend(pszFilter, &pBackend); + if (RT_SUCCESS(rc)) + { + pEntry->pszFilter = pBackend->pszBackendName; + pEntry->paConfigInfo = pBackend->paConfigInfo; + } + + return rc; +} + +/** + * Allocates and initializes an empty HDD container. + * No image files are opened. + * + * @returns VBox status code. + * @param pVDIfsDisk Pointer to the per-disk VD interface list. + * @param enmType Type of the image container. + * @param ppDisk Where to store the reference to HDD container. + */ +VBOXDDU_DECL(int) VDCreate(PVDINTERFACE pVDIfsDisk, VDTYPE enmType, PVDISK *ppDisk) +{ + int rc = VINF_SUCCESS; + PVDISK pDisk = NULL; + + LogFlowFunc(("pVDIfsDisk=%#p\n", pVDIfsDisk)); + do + { + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(ppDisk), + ("ppDisk=%#p\n", ppDisk), + rc = VERR_INVALID_PARAMETER); + + pDisk = (PVDISK)RTMemAllocZ(sizeof(VDISK)); + if (pDisk) + { + pDisk->u32Signature = VDISK_SIGNATURE; + pDisk->enmType = enmType; + pDisk->cImages = 0; + pDisk->pBase = NULL; + pDisk->pLast = NULL; + pDisk->cbSize = 0; + pDisk->PCHSGeometry.cCylinders = 0; + pDisk->PCHSGeometry.cHeads = 0; + pDisk->PCHSGeometry.cSectors = 0; + pDisk->LCHSGeometry.cCylinders = 0; + pDisk->LCHSGeometry.cHeads = 0; + pDisk->LCHSGeometry.cSectors = 0; + pDisk->pVDIfsDisk = pVDIfsDisk; + pDisk->pInterfaceError = NULL; + pDisk->pInterfaceThreadSync = NULL; + pDisk->pIoCtxLockOwner = NULL; + pDisk->pIoCtxHead = NULL; + pDisk->fLocked = false; + pDisk->hMemCacheIoCtx = NIL_RTMEMCACHE; + pDisk->hMemCacheIoTask = NIL_RTMEMCACHE; + RTListInit(&pDisk->ListFilterChainWrite); + RTListInit(&pDisk->ListFilterChainRead); + + /* Create the I/O ctx cache */ + rc = RTMemCacheCreate(&pDisk->hMemCacheIoCtx, sizeof(VDIOCTX), 0, UINT32_MAX, + NULL, NULL, NULL, 0); + if (RT_FAILURE(rc)) + break; + + /* Create the I/O task cache */ + rc = RTMemCacheCreate(&pDisk->hMemCacheIoTask, sizeof(VDIOTASK), 0, UINT32_MAX, + NULL, NULL, NULL, 0); + if (RT_FAILURE(rc)) + break; + + pDisk->pInterfaceError = VDIfErrorGet(pVDIfsDisk); + pDisk->pInterfaceThreadSync = VDIfThreadSyncGet(pVDIfsDisk); + + *ppDisk = pDisk; + } + else + { + rc = VERR_NO_MEMORY; + break; + } + } while (0); + + if ( RT_FAILURE(rc) + && pDisk) + { + if (pDisk->hMemCacheIoCtx != NIL_RTMEMCACHE) + RTMemCacheDestroy(pDisk->hMemCacheIoCtx); + if (pDisk->hMemCacheIoTask != NIL_RTMEMCACHE) + RTMemCacheDestroy(pDisk->hMemCacheIoTask); + } + + LogFlowFunc(("returns %Rrc (pDisk=%#p)\n", rc, pDisk)); + return rc; +} + +/** + * Destroys HDD container. + * If container has opened image files they will be closed. + * + * @returns VBox status code. + * @param pDisk Pointer to HDD container. + */ +VBOXDDU_DECL(int) VDDestroy(PVDISK pDisk) +{ + int rc = VINF_SUCCESS; + LogFlowFunc(("pDisk=%#p\n", pDisk)); + do + { + /* sanity check */ + AssertPtrBreak(pDisk); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + Assert(!pDisk->fLocked); + + rc = VDCloseAll(pDisk); + int rc2 = VDFilterRemoveAll(pDisk); + if (RT_SUCCESS(rc)) + rc = rc2; + + RTMemCacheDestroy(pDisk->hMemCacheIoCtx); + RTMemCacheDestroy(pDisk->hMemCacheIoTask); + RTMemFree(pDisk); + } while (0); + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Try to get the backend name which can use this image. + * + * @returns VBox status code. + * VINF_SUCCESS if a plugin was found. + * ppszFormat contains the string which can be used as backend name. + * VERR_NOT_SUPPORTED if no backend was found. + * @param pVDIfsDisk Pointer to the per-disk VD interface list. + * @param pVDIfsImage Pointer to the per-image VD interface list. + * @param pszFilename Name of the image file for which the backend is queried. + * @param enmDesiredType The desired image type, VDTYPE_INVALID if anything goes. + * @param ppszFormat Receives pointer of the UTF-8 string which contains the format name. + * The returned pointer must be freed using RTStrFree(). + * @param penmType Where to store the type of the image. + */ +VBOXDDU_DECL(int) VDGetFormat(PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage, + const char *pszFilename, VDTYPE enmDesiredType, + char **ppszFormat, VDTYPE *penmType) +{ + int rc = VERR_NOT_SUPPORTED; + VDINTERFACEIOINT VDIfIoInt; + VDINTERFACEIO VDIfIoFallback; + PVDINTERFACEIO pInterfaceIo; + + LogFlowFunc(("pszFilename=\"%s\"\n", pszFilename)); + /* Check arguments. */ + AssertMsgReturn(VALID_PTR(pszFilename) && *pszFilename, + ("pszFilename=%#p \"%s\"\n", pszFilename, pszFilename), + VERR_INVALID_PARAMETER); + AssertMsgReturn(VALID_PTR(ppszFormat), + ("ppszFormat=%#p\n", ppszFormat), + VERR_INVALID_PARAMETER); + AssertMsgReturn(VALID_PTR(penmType), + ("penmType=%#p\n", penmType), + VERR_INVALID_PARAMETER); + AssertReturn(enmDesiredType >= VDTYPE_INVALID && enmDesiredType <= VDTYPE_FLOPPY, VERR_INVALID_PARAMETER); + + if (!vdPluginIsInitialized()) + VDInit(); + + pInterfaceIo = VDIfIoGet(pVDIfsImage); + if (!pInterfaceIo) + { + /* + * Caller doesn't provide an I/O interface, create our own using the + * native file API. + */ + vdIfIoFallbackCallbacksSetup(&VDIfIoFallback); + pInterfaceIo = &VDIfIoFallback; + } + + /* Set up the internal I/O interface. */ + AssertReturn(!VDIfIoIntGet(pVDIfsImage), VERR_INVALID_PARAMETER); + VDIfIoInt.pfnOpen = vdIOIntOpenLimited; + VDIfIoInt.pfnClose = vdIOIntCloseLimited; + VDIfIoInt.pfnDelete = vdIOIntDeleteLimited; + VDIfIoInt.pfnMove = vdIOIntMoveLimited; + VDIfIoInt.pfnGetFreeSpace = vdIOIntGetFreeSpaceLimited; + VDIfIoInt.pfnGetModificationTime = vdIOIntGetModificationTimeLimited; + VDIfIoInt.pfnGetSize = vdIOIntGetSizeLimited; + VDIfIoInt.pfnSetSize = vdIOIntSetSizeLimited; + VDIfIoInt.pfnReadUser = vdIOIntReadUserLimited; + VDIfIoInt.pfnWriteUser = vdIOIntWriteUserLimited; + VDIfIoInt.pfnReadMeta = vdIOIntReadMetaLimited; + VDIfIoInt.pfnWriteMeta = vdIOIntWriteMetaLimited; + VDIfIoInt.pfnFlush = vdIOIntFlushLimited; + rc = VDInterfaceAdd(&VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT, + pInterfaceIo, sizeof(VDINTERFACEIOINT), &pVDIfsImage); + AssertRC(rc); + + /** @todo r=bird: Would be better to do a scoring approach here, where the + * backend that scores the highest is choosen. That way we don't have to depend + * on registration order and filename suffixes to figure out what RAW should + * handle and not. Besides, the registration order won't cut it for plug-ins + * anyway, as they end up after the builtin ones. + */ + + /* Find the backend supporting this file format. */ + for (unsigned i = 0; i < vdGetImageBackendCount(); i++) + { + PCVDIMAGEBACKEND pBackend; + rc = vdQueryImageBackend(i, &pBackend); + AssertRC(rc); + + if (pBackend->pfnProbe) + { + rc = pBackend->pfnProbe(pszFilename, pVDIfsDisk, pVDIfsImage, enmDesiredType, penmType); + if ( RT_SUCCESS(rc) + /* The correct backend has been found, but there is a small + * incompatibility so that the file cannot be used. Stop here + * and signal success - the actual open will of course fail, + * but that will create a really sensible error message. */ + + /** @todo r=bird: this bit of code is _certifiably_ _insane_ as it allows + * simple stuff like VERR_EOF to pass thru. I've just amended it with + * disallowing VERR_EOF too, but someone needs to pick up the courage to + * fix this stuff properly or at least update the docs! + * (Parallels returns VERR_EOF, btw.) */ + + || ( rc != VERR_VD_GEN_INVALID_HEADER + && rc != VERR_VD_VDI_INVALID_HEADER + && rc != VERR_VD_VMDK_INVALID_HEADER + && rc != VERR_VD_ISCSI_INVALID_HEADER + && rc != VERR_VD_VHD_INVALID_HEADER + && rc != VERR_VD_RAW_INVALID_HEADER + && rc != VERR_VD_RAW_SIZE_MODULO_512 + && rc != VERR_VD_RAW_SIZE_MODULO_2048 + && rc != VERR_VD_RAW_SIZE_OPTICAL_TOO_SMALL + && rc != VERR_VD_RAW_SIZE_FLOPPY_TOO_BIG + && rc != VERR_VD_PARALLELS_INVALID_HEADER + && rc != VERR_VD_DMG_INVALID_HEADER + && rc != VERR_EOF /* bird for viso */ + )) + { + /* Copy the name into the new string. */ + char *pszFormat = RTStrDup(pBackend->pszBackendName); + if (!pszFormat) + { + rc = VERR_NO_MEMORY; + break; + } + *ppszFormat = pszFormat; + /* Do not consider the typical file access errors as success, + * which allows the caller to deal with such issues. */ + if ( rc != VERR_ACCESS_DENIED + && rc != VERR_PATH_NOT_FOUND + && rc != VERR_FILE_NOT_FOUND) + rc = VINF_SUCCESS; + break; + } + rc = VERR_NOT_SUPPORTED; + } + } + + /* Try the cache backends. */ + if (rc == VERR_NOT_SUPPORTED) + { + for (unsigned i = 0; i < vdGetCacheBackendCount(); i++) + { + PCVDCACHEBACKEND pBackend; + rc = vdQueryCacheBackend(i, &pBackend); + AssertRC(rc); + + if (pBackend->pfnProbe) + { + rc = pBackend->pfnProbe(pszFilename, pVDIfsDisk, pVDIfsImage); + if ( RT_SUCCESS(rc) + || (rc != VERR_VD_GEN_INVALID_HEADER)) + { + /* Copy the name into the new string. */ + char *pszFormat = RTStrDup(pBackend->pszBackendName); + if (!pszFormat) + { + rc = VERR_NO_MEMORY; + break; + } + *ppszFormat = pszFormat; + rc = VINF_SUCCESS; + break; + } + rc = VERR_NOT_SUPPORTED; + } + } + } + + LogFlowFunc(("returns %Rrc *ppszFormat=\"%s\"\n", rc, *ppszFormat)); + return rc; +} + +/** + * Opens an image file. + * + * The first opened image file in HDD container must have a base image type, + * others (next opened images) must be a differencing or undo images. + * Linkage is checked for differencing image to be in consistence with the previously opened image. + * When another differencing image is opened and the last image was opened in read/write access + * mode, then the last image is reopened in read-only with deny write sharing mode. This allows + * other processes to use images in read-only mode too. + * + * Note that the image is opened in read-only mode if a read/write open is not possible. + * Use VDIsReadOnly to check open mode. + * + * @returns VBox status code. + * @param pDisk Pointer to HDD container. + * @param pszBackend Name of the image file backend to use. + * @param pszFilename Name of the image file to open. + * @param uOpenFlags Image file open mode, see VD_OPEN_FLAGS_* constants. + * @param pVDIfsImage Pointer to the per-image VD interface list. + */ +VBOXDDU_DECL(int) VDOpen(PVDISK pDisk, const char *pszBackend, + const char *pszFilename, unsigned uOpenFlags, + PVDINTERFACE pVDIfsImage) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false; + PVDIMAGE pImage = NULL; + + LogFlowFunc(("pDisk=%#p pszBackend=\"%s\" pszFilename=\"%s\" uOpenFlags=%#x, pVDIfsImage=%#p\n", + pDisk, pszBackend, pszFilename, uOpenFlags, pVDIfsImage)); + + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pszBackend) && *pszBackend, + ("pszBackend=%#p \"%s\"\n", pszBackend, pszBackend), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt(VALID_PTR(pszFilename) && *pszFilename, + ("pszFilename=%#p \"%s\"\n", pszFilename, pszFilename), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0, + ("uOpenFlags=%#x\n", uOpenFlags), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt( !(uOpenFlags & VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS) + || (uOpenFlags & VD_OPEN_FLAGS_READONLY), + ("uOpenFlags=%#x\n", uOpenFlags), + rc = VERR_INVALID_PARAMETER); + + /* + * Destroy the current discard state first which might still have pending blocks + * for the currently opened image which will be switched to readonly mode. + */ + /* Lock disk for writing, as we modify pDisk information below. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + rc = vdDiscardStateDestroy(pDisk); + if (RT_FAILURE(rc)) + break; + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + fLockWrite = false; + + /* Set up image descriptor. */ + pImage = (PVDIMAGE)RTMemAllocZ(sizeof(VDIMAGE)); + if (!pImage) + { + rc = VERR_NO_MEMORY; + break; + } + pImage->pszFilename = RTStrDup(pszFilename); + if (!pImage->pszFilename) + { + rc = VERR_NO_MEMORY; + break; + } + + pImage->cbImage = VD_IMAGE_SIZE_UNINITIALIZED; + pImage->VDIo.pDisk = pDisk; + pImage->pVDIfsImage = pVDIfsImage; + + rc = vdFindImageBackend(pszBackend, &pImage->Backend); + if (RT_FAILURE(rc)) + break; + if (!pImage->Backend) + { + rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("VD: unknown backend name '%s'"), pszBackend); + break; + } + + /* + * Fail if the backend can't do async I/O but the + * flag is set. + */ + if ( !(pImage->Backend->uBackendCaps & VD_CAP_ASYNC) + && (uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO)) + { + rc = vdError(pDisk, VERR_NOT_SUPPORTED, RT_SRC_POS, + N_("VD: Backend '%s' does not support async I/O"), pszBackend); + break; + } + + /* + * Fail if the backend doesn't support the discard operation but the + * flag is set. + */ + if ( !(pImage->Backend->uBackendCaps & VD_CAP_DISCARD) + && (uOpenFlags & VD_OPEN_FLAGS_DISCARD)) + { + rc = vdError(pDisk, VERR_VD_DISCARD_NOT_SUPPORTED, RT_SRC_POS, + N_("VD: Backend '%s' does not support discard"), pszBackend); + break; + } + + /* Set up the I/O interface. */ + pImage->VDIo.pInterfaceIo = VDIfIoGet(pVDIfsImage); + if (!pImage->VDIo.pInterfaceIo) + { + vdIfIoFallbackCallbacksSetup(&pImage->VDIo.VDIfIo); + rc = VDInterfaceAdd(&pImage->VDIo.VDIfIo.Core, "VD_IO", VDINTERFACETYPE_IO, + pDisk, sizeof(VDINTERFACEIO), &pVDIfsImage); + pImage->VDIo.pInterfaceIo = &pImage->VDIo.VDIfIo; + } + + /* Set up the internal I/O interface. */ + AssertBreakStmt(!VDIfIoIntGet(pVDIfsImage), rc = VERR_INVALID_PARAMETER); + vdIfIoIntCallbacksSetup(&pImage->VDIo.VDIfIoInt); + rc = VDInterfaceAdd(&pImage->VDIo.VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT, + &pImage->VDIo, sizeof(VDINTERFACEIOINT), &pImage->pVDIfsImage); + AssertRC(rc); + + pImage->uOpenFlags = uOpenFlags & (VD_OPEN_FLAGS_HONOR_SAME | VD_OPEN_FLAGS_DISCARD | VD_OPEN_FLAGS_IGNORE_FLUSH | VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS); + pImage->VDIo.fIgnoreFlush = (uOpenFlags & VD_OPEN_FLAGS_IGNORE_FLUSH) != 0; + rc = pImage->Backend->pfnOpen(pImage->pszFilename, + uOpenFlags & ~(VD_OPEN_FLAGS_HONOR_SAME | VD_OPEN_FLAGS_IGNORE_FLUSH | VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS), + pDisk->pVDIfsDisk, + pImage->pVDIfsImage, + pDisk->enmType, + &pImage->pBackendData); + /* + * If the image is corrupted and there is a repair method try to repair it + * first if it was openend in read-write mode and open again afterwards. + */ + if ( RT_UNLIKELY(rc == VERR_VD_IMAGE_CORRUPTED) + && !(uOpenFlags & VD_OPEN_FLAGS_READONLY) + && pImage->Backend->pfnRepair) + { + rc = pImage->Backend->pfnRepair(pszFilename, pDisk->pVDIfsDisk, pImage->pVDIfsImage, 0 /* fFlags */); + if (RT_SUCCESS(rc)) + rc = pImage->Backend->pfnOpen(pImage->pszFilename, + uOpenFlags & ~(VD_OPEN_FLAGS_HONOR_SAME | VD_OPEN_FLAGS_IGNORE_FLUSH | VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS), + pDisk->pVDIfsDisk, + pImage->pVDIfsImage, + pDisk->enmType, + &pImage->pBackendData); + else + { + rc = vdError(pDisk, rc, RT_SRC_POS, + N_("VD: error %Rrc repairing corrupted image file '%s'"), rc, pszFilename); + break; + } + } + else if (RT_UNLIKELY(rc == VERR_VD_IMAGE_CORRUPTED)) + { + rc = vdError(pDisk, rc, RT_SRC_POS, + N_("VD: Image file '%s' is corrupted and can't be opened"), pszFilename); + break; + } + + /* If the open in read-write mode failed, retry in read-only mode. */ + if (RT_FAILURE(rc)) + { + if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY) + && ( rc == VERR_ACCESS_DENIED + || rc == VERR_PERMISSION_DENIED + || rc == VERR_WRITE_PROTECT + || rc == VERR_SHARING_VIOLATION + || rc == VERR_FILE_LOCK_FAILED)) + rc = pImage->Backend->pfnOpen(pImage->pszFilename, + (uOpenFlags & ~(VD_OPEN_FLAGS_HONOR_SAME | VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS)) + | VD_OPEN_FLAGS_READONLY, + pDisk->pVDIfsDisk, + pImage->pVDIfsImage, + pDisk->enmType, + &pImage->pBackendData); + if (RT_FAILURE(rc)) + { + rc = vdError(pDisk, rc, RT_SRC_POS, + N_("VD: error %Rrc opening image file '%s'"), rc, pszFilename); + break; + } + } + + /* Lock disk for writing, as we modify pDisk information below. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + pImage->VDIo.pBackendData = pImage->pBackendData; + + /* Check image type. As the image itself has only partial knowledge + * whether it's a base image or not, this info is derived here. The + * base image can be fixed or normal, all others must be normal or + * diff images. Some image formats don't distinguish between normal + * and diff images, so this must be corrected here. */ + unsigned uImageFlags; + uImageFlags = pImage->Backend->pfnGetImageFlags(pImage->pBackendData); + if (RT_FAILURE(rc)) + uImageFlags = VD_IMAGE_FLAGS_NONE; + if ( RT_SUCCESS(rc) + && !(uOpenFlags & VD_OPEN_FLAGS_INFO)) + { + if ( pDisk->cImages == 0 + && (uImageFlags & VD_IMAGE_FLAGS_DIFF)) + { + rc = VERR_VD_INVALID_TYPE; + break; + } + else if (pDisk->cImages != 0) + { + if (uImageFlags & VD_IMAGE_FLAGS_FIXED) + { + rc = VERR_VD_INVALID_TYPE; + break; + } + else + uImageFlags |= VD_IMAGE_FLAGS_DIFF; + } + } + + /* Ensure we always get correct diff information, even if the backend + * doesn't actually have a stored flag for this. It must not return + * bogus information for the parent UUID if it is not a diff image. */ + RTUUID parentUuid; + RTUuidClear(&parentUuid); + rc2 = pImage->Backend->pfnGetParentUuid(pImage->pBackendData, &parentUuid); + if (RT_SUCCESS(rc2) && !RTUuidIsNull(&parentUuid)) + uImageFlags |= VD_IMAGE_FLAGS_DIFF; + + pImage->uImageFlags = uImageFlags; + + /* Force sane optimization settings. It's not worth avoiding writes + * to fixed size images. The overhead would have almost no payback. */ + if (uImageFlags & VD_IMAGE_FLAGS_FIXED) + pImage->uOpenFlags |= VD_OPEN_FLAGS_HONOR_SAME; + + /** @todo optionally check UUIDs */ + + /* Cache disk information. */ + pDisk->cbSize = vdImageGetSize(pImage); + + /* Cache PCHS geometry. */ + rc2 = pImage->Backend->pfnGetPCHSGeometry(pImage->pBackendData, + &pDisk->PCHSGeometry); + if (RT_FAILURE(rc2)) + { + pDisk->PCHSGeometry.cCylinders = 0; + pDisk->PCHSGeometry.cHeads = 0; + pDisk->PCHSGeometry.cSectors = 0; + } + else + { + /* Make sure the PCHS geometry is properly clipped. */ + pDisk->PCHSGeometry.cCylinders = RT_MIN(pDisk->PCHSGeometry.cCylinders, 16383); + pDisk->PCHSGeometry.cHeads = RT_MIN(pDisk->PCHSGeometry.cHeads, 16); + pDisk->PCHSGeometry.cSectors = RT_MIN(pDisk->PCHSGeometry.cSectors, 63); + } + + /* Cache LCHS geometry. */ + rc2 = pImage->Backend->pfnGetLCHSGeometry(pImage->pBackendData, + &pDisk->LCHSGeometry); + if (RT_FAILURE(rc2)) + { + pDisk->LCHSGeometry.cCylinders = 0; + pDisk->LCHSGeometry.cHeads = 0; + pDisk->LCHSGeometry.cSectors = 0; + } + else + { + /* Make sure the LCHS geometry is properly clipped. */ + pDisk->LCHSGeometry.cHeads = RT_MIN(pDisk->LCHSGeometry.cHeads, 255); + pDisk->LCHSGeometry.cSectors = RT_MIN(pDisk->LCHSGeometry.cSectors, 63); + } + + if (pDisk->cImages != 0) + { + /* Switch previous image to read-only mode. */ + unsigned uOpenFlagsPrevImg; + uOpenFlagsPrevImg = pDisk->pLast->Backend->pfnGetOpenFlags(pDisk->pLast->pBackendData); + if (!(uOpenFlagsPrevImg & VD_OPEN_FLAGS_READONLY)) + { + uOpenFlagsPrevImg |= VD_OPEN_FLAGS_READONLY; + rc = pDisk->pLast->Backend->pfnSetOpenFlags(pDisk->pLast->pBackendData, uOpenFlagsPrevImg); + } + } + + if (RT_SUCCESS(rc)) + { + /* Image successfully opened, make it the last image. */ + vdAddImageToList(pDisk, pImage); + if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY)) + pDisk->uModified = VD_IMAGE_MODIFIED_FIRST; + } + else + { + /* Error detected, but image opened. Close image. */ + rc2 = pImage->Backend->pfnClose(pImage->pBackendData, false); + AssertRC(rc2); + pImage->pBackendData = NULL; + } + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + if (RT_FAILURE(rc)) + { + if (pImage) + { + if (pImage->pszFilename) + RTStrFree(pImage->pszFilename); + RTMemFree(pImage); + } + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Opens a cache image. + * + * @return VBox status code. + * @param pDisk Pointer to the HDD container which should use the cache image. + * @param pszBackend Name of the cache file backend to use (case insensitive). + * @param pszFilename Name of the cache image to open. + * @param uOpenFlags Image file open mode, see VD_OPEN_FLAGS_* constants. + * @param pVDIfsCache Pointer to the per-cache VD interface list. + */ +VBOXDDU_DECL(int) VDCacheOpen(PVDISK pDisk, const char *pszBackend, + const char *pszFilename, unsigned uOpenFlags, + PVDINTERFACE pVDIfsCache) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false; + PVDCACHE pCache = NULL; + + LogFlowFunc(("pDisk=%#p pszBackend=\"%s\" pszFilename=\"%s\" uOpenFlags=%#x, pVDIfsCache=%#p\n", + pDisk, pszBackend, pszFilename, uOpenFlags, pVDIfsCache)); + + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pszBackend) && *pszBackend, + ("pszBackend=%#p \"%s\"\n", pszBackend, pszBackend), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt(VALID_PTR(pszFilename) && *pszFilename, + ("pszFilename=%#p \"%s\"\n", pszFilename, pszFilename), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0, + ("uOpenFlags=%#x\n", uOpenFlags), + rc = VERR_INVALID_PARAMETER); + + /* Set up image descriptor. */ + pCache = (PVDCACHE)RTMemAllocZ(sizeof(VDCACHE)); + if (!pCache) + { + rc = VERR_NO_MEMORY; + break; + } + pCache->pszFilename = RTStrDup(pszFilename); + if (!pCache->pszFilename) + { + rc = VERR_NO_MEMORY; + break; + } + + pCache->VDIo.pDisk = pDisk; + pCache->pVDIfsCache = pVDIfsCache; + + rc = vdFindCacheBackend(pszBackend, &pCache->Backend); + if (RT_FAILURE(rc)) + break; + if (!pCache->Backend) + { + rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("VD: unknown backend name '%s'"), pszBackend); + break; + } + + /* Set up the I/O interface. */ + pCache->VDIo.pInterfaceIo = VDIfIoGet(pVDIfsCache); + if (!pCache->VDIo.pInterfaceIo) + { + vdIfIoFallbackCallbacksSetup(&pCache->VDIo.VDIfIo); + rc = VDInterfaceAdd(&pCache->VDIo.VDIfIo.Core, "VD_IO", VDINTERFACETYPE_IO, + pDisk, sizeof(VDINTERFACEIO), &pVDIfsCache); + pCache->VDIo.pInterfaceIo = &pCache->VDIo.VDIfIo; + } + + /* Set up the internal I/O interface. */ + AssertBreakStmt(!VDIfIoIntGet(pVDIfsCache), rc = VERR_INVALID_PARAMETER); + vdIfIoIntCallbacksSetup(&pCache->VDIo.VDIfIoInt); + rc = VDInterfaceAdd(&pCache->VDIo.VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT, + &pCache->VDIo, sizeof(VDINTERFACEIOINT), &pCache->pVDIfsCache); + AssertRC(rc); + + pCache->uOpenFlags = uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME; + rc = pCache->Backend->pfnOpen(pCache->pszFilename, + uOpenFlags & ~VD_OPEN_FLAGS_HONOR_SAME, + pDisk->pVDIfsDisk, + pCache->pVDIfsCache, + &pCache->pBackendData); + /* If the open in read-write mode failed, retry in read-only mode. */ + if (RT_FAILURE(rc)) + { + if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY) + && ( rc == VERR_ACCESS_DENIED + || rc == VERR_PERMISSION_DENIED + || rc == VERR_WRITE_PROTECT + || rc == VERR_SHARING_VIOLATION + || rc == VERR_FILE_LOCK_FAILED)) + rc = pCache->Backend->pfnOpen(pCache->pszFilename, + (uOpenFlags & ~VD_OPEN_FLAGS_HONOR_SAME) + | VD_OPEN_FLAGS_READONLY, + pDisk->pVDIfsDisk, + pCache->pVDIfsCache, + &pCache->pBackendData); + if (RT_FAILURE(rc)) + { + rc = vdError(pDisk, rc, RT_SRC_POS, + N_("VD: error %Rrc opening image file '%s'"), rc, pszFilename); + break; + } + } + + /* Lock disk for writing, as we modify pDisk information below. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + /* + * Check that the modification UUID of the cache and last image + * match. If not the image was modified in-between without the cache. + * The cache might contain stale data. + */ + RTUUID UuidImage, UuidCache; + + rc = pCache->Backend->pfnGetModificationUuid(pCache->pBackendData, + &UuidCache); + if (RT_SUCCESS(rc)) + { + rc = pDisk->pLast->Backend->pfnGetModificationUuid(pDisk->pLast->pBackendData, + &UuidImage); + if (RT_SUCCESS(rc)) + { + if (RTUuidCompare(&UuidImage, &UuidCache)) + rc = VERR_VD_CACHE_NOT_UP_TO_DATE; + } + } + + /* + * We assume that the user knows what he is doing if one of the images + * doesn't support the modification uuid. + */ + if (rc == VERR_NOT_SUPPORTED) + rc = VINF_SUCCESS; + + if (RT_SUCCESS(rc)) + { + /* Cache successfully opened, make it the current one. */ + if (!pDisk->pCache) + pDisk->pCache = pCache; + else + rc = VERR_VD_CACHE_ALREADY_EXISTS; + } + + if (RT_FAILURE(rc)) + { + /* Error detected, but image opened. Close image. */ + rc2 = pCache->Backend->pfnClose(pCache->pBackendData, false); + AssertRC(rc2); + pCache->pBackendData = NULL; + } + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + if (RT_FAILURE(rc)) + { + if (pCache) + { + if (pCache->pszFilename) + RTStrFree(pCache->pszFilename); + RTMemFree(pCache); + } + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +VBOXDDU_DECL(int) VDFilterAdd(PVDISK pDisk, const char *pszFilter, uint32_t fFlags, + PVDINTERFACE pVDIfsFilter) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false; + PVDFILTER pFilter = NULL; + + LogFlowFunc(("pDisk=%#p pszFilter=\"%s\" pVDIfsFilter=%#p\n", + pDisk, pszFilter, pVDIfsFilter)); + + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pszFilter) && *pszFilter, + ("pszFilter=%#p \"%s\"\n", pszFilter, pszFilter), + rc = VERR_INVALID_PARAMETER); + + AssertMsgBreakStmt(!(fFlags & ~VD_FILTER_FLAGS_MASK), + ("Invalid flags set (fFlags=%#x)\n", fFlags), + rc = VERR_INVALID_PARAMETER); + + /* Set up image descriptor. */ + pFilter = (PVDFILTER)RTMemAllocZ(sizeof(VDFILTER)); + if (!pFilter) + { + rc = VERR_NO_MEMORY; + break; + } + + rc = vdFindFilterBackend(pszFilter, &pFilter->pBackend); + if (RT_FAILURE(rc)) + break; + if (!pFilter->pBackend) + { + rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("VD: unknown filter backend name '%s'"), pszFilter); + break; + } + + pFilter->VDIo.pDisk = pDisk; + pFilter->pVDIfsFilter = pVDIfsFilter; + + /* Set up the internal I/O interface. */ + AssertBreakStmt(!VDIfIoIntGet(pVDIfsFilter), rc = VERR_INVALID_PARAMETER); + vdIfIoIntCallbacksSetup(&pFilter->VDIo.VDIfIoInt); + rc = VDInterfaceAdd(&pFilter->VDIo.VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT, + &pFilter->VDIo, sizeof(VDINTERFACEIOINT), &pFilter->pVDIfsFilter); + AssertRC(rc); + + rc = pFilter->pBackend->pfnCreate(pDisk->pVDIfsDisk, fFlags & VD_FILTER_FLAGS_INFO, + pFilter->pVDIfsFilter, &pFilter->pvBackendData); + if (RT_FAILURE(rc)) + break; + + /* Lock disk for writing, as we modify pDisk information below. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + /* Add filter to chains. */ + if (fFlags & VD_FILTER_FLAGS_WRITE) + { + RTListAppend(&pDisk->ListFilterChainWrite, &pFilter->ListNodeChainWrite); + vdFilterRetain(pFilter); + } + + if (fFlags & VD_FILTER_FLAGS_READ) + { + RTListAppend(&pDisk->ListFilterChainRead, &pFilter->ListNodeChainRead); + vdFilterRetain(pFilter); + } + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + if (RT_FAILURE(rc)) + { + if (pFilter) + RTMemFree(pFilter); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Creates and opens a new base image file. + * + * @returns VBox status code. + * @param pDisk Pointer to HDD container. + * @param pszBackend Name of the image file backend to use. + * @param pszFilename Name of the image file to create. + * @param cbSize Image size in bytes. + * @param uImageFlags Flags specifying special image features. + * @param pszComment Pointer to image comment. NULL is ok. + * @param pPCHSGeometry Pointer to physical disk geometry <= (16383,16,63). Not NULL. + * @param pLCHSGeometry Pointer to logical disk geometry <= (x,255,63). Not NULL. + * @param pUuid New UUID of the image. If NULL, a new UUID is created. + * @param uOpenFlags Image file open mode, see VD_OPEN_FLAGS_* constants. + * @param pVDIfsImage Pointer to the per-image VD interface list. + * @param pVDIfsOperation Pointer to the per-operation VD interface list. + */ +VBOXDDU_DECL(int) VDCreateBase(PVDISK pDisk, const char *pszBackend, + const char *pszFilename, uint64_t cbSize, + unsigned uImageFlags, const char *pszComment, + PCVDGEOMETRY pPCHSGeometry, + PCVDGEOMETRY pLCHSGeometry, + PCRTUUID pUuid, unsigned uOpenFlags, + PVDINTERFACE pVDIfsImage, + PVDINTERFACE pVDIfsOperation) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false, fLockRead = false; + PVDIMAGE pImage = NULL; + RTUUID uuid; + + LogFlowFunc(("pDisk=%#p pszBackend=\"%s\" pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" PCHS=%u/%u/%u LCHS=%u/%u/%u Uuid=%RTuuid uOpenFlags=%#x pVDIfsImage=%#p pVDIfsOperation=%#p\n", + pDisk, pszBackend, pszFilename, cbSize, uImageFlags, pszComment, + pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, + pPCHSGeometry->cSectors, pLCHSGeometry->cCylinders, + pLCHSGeometry->cHeads, pLCHSGeometry->cSectors, pUuid, + uOpenFlags, pVDIfsImage, pVDIfsOperation)); + + PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation); + + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pszBackend) && *pszBackend, + ("pszBackend=%#p \"%s\"\n", pszBackend, pszBackend), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt(VALID_PTR(pszFilename) && *pszFilename, + ("pszFilename=%#p \"%s\"\n", pszFilename, pszFilename), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt(cbSize, + ("cbSize=%llu\n", cbSize), + rc = VERR_INVALID_PARAMETER); + if (cbSize % 512) + { + rc = vdError(pDisk, VERR_VD_INVALID_SIZE, RT_SRC_POS, + N_("VD: The given disk size %llu is not aligned on a sector boundary (512 bytes)"), cbSize); + break; + } + AssertMsgBreakStmt( ((uImageFlags & ~VD_IMAGE_FLAGS_MASK) == 0) + || ((uImageFlags & (VD_IMAGE_FLAGS_FIXED | VD_IMAGE_FLAGS_DIFF)) != VD_IMAGE_FLAGS_FIXED), + ("uImageFlags=%#x\n", uImageFlags), + rc = VERR_INVALID_PARAMETER); + /* The PCHS geometry fields may be 0 to leave it for later. */ + AssertMsgBreakStmt( VALID_PTR(pPCHSGeometry) + && pPCHSGeometry->cHeads <= 16 + && pPCHSGeometry->cSectors <= 63, + ("pPCHSGeometry=%#p PCHS=%u/%u/%u\n", pPCHSGeometry, + pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, + pPCHSGeometry->cSectors), + rc = VERR_INVALID_PARAMETER); + /* The LCHS geometry fields may be 0 to leave it to later autodetection. */ + AssertMsgBreakStmt( VALID_PTR(pLCHSGeometry) + && pLCHSGeometry->cHeads <= 255 + && pLCHSGeometry->cSectors <= 63, + ("pLCHSGeometry=%#p LCHS=%u/%u/%u\n", pLCHSGeometry, + pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, + pLCHSGeometry->cSectors), + rc = VERR_INVALID_PARAMETER); + /* The UUID may be NULL. */ + AssertMsgBreakStmt(pUuid == NULL || VALID_PTR(pUuid), + ("pUuid=%#p UUID=%RTuuid\n", pUuid, pUuid), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0, + ("uOpenFlags=%#x\n", uOpenFlags), + rc = VERR_INVALID_PARAMETER); + + /* Check state. Needs a temporary read lock. Holding the write lock + * all the time would be blocking other activities for too long. */ + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + AssertMsgBreakStmt(pDisk->cImages == 0, + ("Create base image cannot be done with other images open\n"), + rc = VERR_VD_INVALID_STATE); + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + fLockRead = false; + + /* Set up image descriptor. */ + pImage = (PVDIMAGE)RTMemAllocZ(sizeof(VDIMAGE)); + if (!pImage) + { + rc = VERR_NO_MEMORY; + break; + } + pImage->pszFilename = RTStrDup(pszFilename); + if (!pImage->pszFilename) + { + rc = VERR_NO_MEMORY; + break; + } + pImage->cbImage = VD_IMAGE_SIZE_UNINITIALIZED; + pImage->VDIo.pDisk = pDisk; + pImage->pVDIfsImage = pVDIfsImage; + + /* Set up the I/O interface. */ + pImage->VDIo.pInterfaceIo = VDIfIoGet(pVDIfsImage); + if (!pImage->VDIo.pInterfaceIo) + { + vdIfIoFallbackCallbacksSetup(&pImage->VDIo.VDIfIo); + rc = VDInterfaceAdd(&pImage->VDIo.VDIfIo.Core, "VD_IO", VDINTERFACETYPE_IO, + pDisk, sizeof(VDINTERFACEIO), &pVDIfsImage); + pImage->VDIo.pInterfaceIo = &pImage->VDIo.VDIfIo; + } + + /* Set up the internal I/O interface. */ + AssertBreakStmt(!VDIfIoIntGet(pVDIfsImage), rc = VERR_INVALID_PARAMETER); + vdIfIoIntCallbacksSetup(&pImage->VDIo.VDIfIoInt); + rc = VDInterfaceAdd(&pImage->VDIo.VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT, + &pImage->VDIo, sizeof(VDINTERFACEIOINT), &pImage->pVDIfsImage); + AssertRC(rc); + + rc = vdFindImageBackend(pszBackend, &pImage->Backend); + if (RT_FAILURE(rc)) + break; + if (!pImage->Backend) + { + rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("VD: unknown backend name '%s'"), pszBackend); + break; + } + if (!(pImage->Backend->uBackendCaps & ( VD_CAP_CREATE_FIXED + | VD_CAP_CREATE_DYNAMIC))) + { + rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("VD: backend '%s' cannot create base images"), pszBackend); + break; + } + if ( ( (uImageFlags & VD_VMDK_IMAGE_FLAGS_SPLIT_2G) + && !(pImage->Backend->uBackendCaps & VD_CAP_CREATE_SPLIT_2G)) + || ( (uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED) + && RTStrICmp(pszBackend, "VMDK"))) + { + rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("VD: backend '%s' does not support the selected image variant"), pszBackend); + break; + } + + /* Create UUID if the caller didn't specify one. */ + if (!pUuid) + { + rc = RTUuidCreate(&uuid); + if (RT_FAILURE(rc)) + { + rc = vdError(pDisk, rc, RT_SRC_POS, + N_("VD: cannot generate UUID for image '%s'"), + pszFilename); + break; + } + pUuid = &uuid; + } + + pImage->uOpenFlags = uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME; + uImageFlags &= ~VD_IMAGE_FLAGS_DIFF; + pImage->VDIo.fIgnoreFlush = (uOpenFlags & VD_OPEN_FLAGS_IGNORE_FLUSH) != 0; + rc = pImage->Backend->pfnCreate(pImage->pszFilename, cbSize, + uImageFlags, pszComment, pPCHSGeometry, + pLCHSGeometry, pUuid, + uOpenFlags & ~VD_OPEN_FLAGS_HONOR_SAME, + 0, 99, + pDisk->pVDIfsDisk, + pImage->pVDIfsImage, + pVDIfsOperation, + pDisk->enmType, + &pImage->pBackendData); + + if (RT_SUCCESS(rc)) + { + pImage->VDIo.pBackendData = pImage->pBackendData; + pImage->uImageFlags = uImageFlags; + + /* Force sane optimization settings. It's not worth avoiding writes + * to fixed size images. The overhead would have almost no payback. */ + if (uImageFlags & VD_IMAGE_FLAGS_FIXED) + pImage->uOpenFlags |= VD_OPEN_FLAGS_HONOR_SAME; + + /* Lock disk for writing, as we modify pDisk information below. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + /** @todo optionally check UUIDs */ + + /* Re-check state, as the lock wasn't held and another image + * creation call could have been done by another thread. */ + AssertMsgStmt(pDisk->cImages == 0, + ("Create base image cannot be done with other images open\n"), + rc = VERR_VD_INVALID_STATE); + } + + if (RT_SUCCESS(rc)) + { + /* Cache disk information. */ + pDisk->cbSize = vdImageGetSize(pImage); + + /* Cache PCHS geometry. */ + rc2 = pImage->Backend->pfnGetPCHSGeometry(pImage->pBackendData, + &pDisk->PCHSGeometry); + if (RT_FAILURE(rc2)) + { + pDisk->PCHSGeometry.cCylinders = 0; + pDisk->PCHSGeometry.cHeads = 0; + pDisk->PCHSGeometry.cSectors = 0; + } + else + { + /* Make sure the CHS geometry is properly clipped. */ + pDisk->PCHSGeometry.cCylinders = RT_MIN(pDisk->PCHSGeometry.cCylinders, 16383); + pDisk->PCHSGeometry.cHeads = RT_MIN(pDisk->PCHSGeometry.cHeads, 16); + pDisk->PCHSGeometry.cSectors = RT_MIN(pDisk->PCHSGeometry.cSectors, 63); + } + + /* Cache LCHS geometry. */ + rc2 = pImage->Backend->pfnGetLCHSGeometry(pImage->pBackendData, + &pDisk->LCHSGeometry); + if (RT_FAILURE(rc2)) + { + pDisk->LCHSGeometry.cCylinders = 0; + pDisk->LCHSGeometry.cHeads = 0; + pDisk->LCHSGeometry.cSectors = 0; + } + else + { + /* Make sure the CHS geometry is properly clipped. */ + pDisk->LCHSGeometry.cHeads = RT_MIN(pDisk->LCHSGeometry.cHeads, 255); + pDisk->LCHSGeometry.cSectors = RT_MIN(pDisk->LCHSGeometry.cSectors, 63); + } + + /* Image successfully opened, make it the last image. */ + vdAddImageToList(pDisk, pImage); + if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY)) + pDisk->uModified = VD_IMAGE_MODIFIED_FIRST; + } + else + { + /* Error detected, image may or may not be opened. Close and delete + * image if it was opened. */ + if (pImage->pBackendData) + { + rc2 = pImage->Backend->pfnClose(pImage->pBackendData, true); + AssertRC(rc2); + pImage->pBackendData = NULL; + } + } + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + else if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + if (RT_FAILURE(rc)) + { + if (pImage) + { + if (pImage->pszFilename) + RTStrFree(pImage->pszFilename); + RTMemFree(pImage); + } + } + + if (RT_SUCCESS(rc) && pIfProgress && pIfProgress->pfnProgress) + pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Creates and opens a new differencing image file in HDD container. + * See comments for VDOpen function about differencing images. + * + * @returns VBox status code. + * @param pDisk Pointer to HDD container. + * @param pszBackend Name of the image file backend to use. + * @param pszFilename Name of the differencing image file to create. + * @param uImageFlags Flags specifying special image features. + * @param pszComment Pointer to image comment. NULL is ok. + * @param pUuid New UUID of the image. If NULL, a new UUID is created. + * @param pParentUuid New parent UUID of the image. If NULL, the UUID is queried automatically. + * @param uOpenFlags Image file open mode, see VD_OPEN_FLAGS_* constants. + * @param pVDIfsImage Pointer to the per-image VD interface list. + * @param pVDIfsOperation Pointer to the per-operation VD interface list. + */ +VBOXDDU_DECL(int) VDCreateDiff(PVDISK pDisk, const char *pszBackend, + const char *pszFilename, unsigned uImageFlags, + const char *pszComment, PCRTUUID pUuid, + PCRTUUID pParentUuid, unsigned uOpenFlags, + PVDINTERFACE pVDIfsImage, + PVDINTERFACE pVDIfsOperation) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false, fLockRead = false; + PVDIMAGE pImage = NULL; + RTUUID uuid; + + LogFlowFunc(("pDisk=%#p pszBackend=\"%s\" pszFilename=\"%s\" uImageFlags=%#x pszComment=\"%s\" Uuid=%RTuuid uOpenFlags=%#x pVDIfsImage=%#p pVDIfsOperation=%#p\n", + pDisk, pszBackend, pszFilename, uImageFlags, pszComment, pUuid, uOpenFlags, pVDIfsImage, pVDIfsOperation)); + + PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation); + + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pszBackend) && *pszBackend, + ("pszBackend=%#p \"%s\"\n", pszBackend, pszBackend), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt(VALID_PTR(pszFilename) && *pszFilename, + ("pszFilename=%#p \"%s\"\n", pszFilename, pszFilename), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt((uImageFlags & ~VD_IMAGE_FLAGS_MASK) == 0, + ("uImageFlags=%#x\n", uImageFlags), + rc = VERR_INVALID_PARAMETER); + /* The UUID may be NULL. */ + AssertMsgBreakStmt(pUuid == NULL || VALID_PTR(pUuid), + ("pUuid=%#p UUID=%RTuuid\n", pUuid, pUuid), + rc = VERR_INVALID_PARAMETER); + /* The parent UUID may be NULL. */ + AssertMsgBreakStmt(pParentUuid == NULL || VALID_PTR(pParentUuid), + ("pParentUuid=%#p ParentUUID=%RTuuid\n", pParentUuid, pParentUuid), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0, + ("uOpenFlags=%#x\n", uOpenFlags), + rc = VERR_INVALID_PARAMETER); + + /* Check state. Needs a temporary read lock. Holding the write lock + * all the time would be blocking other activities for too long. */ + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + AssertMsgBreakStmt(pDisk->cImages != 0, + ("Create diff image cannot be done without other images open\n"), + rc = VERR_VD_INVALID_STATE); + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + fLockRead = false; + + /* + * Destroy the current discard state first which might still have pending blocks + * for the currently opened image which will be switched to readonly mode. + */ + /* Lock disk for writing, as we modify pDisk information below. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + rc = vdDiscardStateDestroy(pDisk); + if (RT_FAILURE(rc)) + break; + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + fLockWrite = false; + + /* Set up image descriptor. */ + pImage = (PVDIMAGE)RTMemAllocZ(sizeof(VDIMAGE)); + if (!pImage) + { + rc = VERR_NO_MEMORY; + break; + } + pImage->pszFilename = RTStrDup(pszFilename); + if (!pImage->pszFilename) + { + rc = VERR_NO_MEMORY; + break; + } + + rc = vdFindImageBackend(pszBackend, &pImage->Backend); + if (RT_FAILURE(rc)) + break; + if (!pImage->Backend) + { + rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("VD: unknown backend name '%s'"), pszBackend); + break; + } + if ( !(pImage->Backend->uBackendCaps & VD_CAP_DIFF) + || !(pImage->Backend->uBackendCaps & ( VD_CAP_CREATE_FIXED + | VD_CAP_CREATE_DYNAMIC))) + { + rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("VD: backend '%s' cannot create diff images"), pszBackend); + break; + } + + pImage->cbImage = VD_IMAGE_SIZE_UNINITIALIZED; + pImage->VDIo.pDisk = pDisk; + pImage->pVDIfsImage = pVDIfsImage; + + /* Set up the I/O interface. */ + pImage->VDIo.pInterfaceIo = VDIfIoGet(pVDIfsImage); + if (!pImage->VDIo.pInterfaceIo) + { + vdIfIoFallbackCallbacksSetup(&pImage->VDIo.VDIfIo); + rc = VDInterfaceAdd(&pImage->VDIo.VDIfIo.Core, "VD_IO", VDINTERFACETYPE_IO, + pDisk, sizeof(VDINTERFACEIO), &pVDIfsImage); + pImage->VDIo.pInterfaceIo = &pImage->VDIo.VDIfIo; + } + + /* Set up the internal I/O interface. */ + AssertBreakStmt(!VDIfIoIntGet(pVDIfsImage), rc = VERR_INVALID_PARAMETER); + vdIfIoIntCallbacksSetup(&pImage->VDIo.VDIfIoInt); + rc = VDInterfaceAdd(&pImage->VDIo.VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT, + &pImage->VDIo, sizeof(VDINTERFACEIOINT), &pImage->pVDIfsImage); + AssertRC(rc); + + /* Create UUID if the caller didn't specify one. */ + if (!pUuid) + { + rc = RTUuidCreate(&uuid); + if (RT_FAILURE(rc)) + { + rc = vdError(pDisk, rc, RT_SRC_POS, + N_("VD: cannot generate UUID for image '%s'"), + pszFilename); + break; + } + pUuid = &uuid; + } + + pImage->uOpenFlags = uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME; + pImage->VDIo.fIgnoreFlush = (uOpenFlags & VD_OPEN_FLAGS_IGNORE_FLUSH) != 0; + uImageFlags |= VD_IMAGE_FLAGS_DIFF; + rc = pImage->Backend->pfnCreate(pImage->pszFilename, pDisk->cbSize, + uImageFlags | VD_IMAGE_FLAGS_DIFF, + pszComment, &pDisk->PCHSGeometry, + &pDisk->LCHSGeometry, pUuid, + uOpenFlags & ~VD_OPEN_FLAGS_HONOR_SAME, + 0, 99, + pDisk->pVDIfsDisk, + pImage->pVDIfsImage, + pVDIfsOperation, + pDisk->enmType, + &pImage->pBackendData); + + if (RT_SUCCESS(rc)) + { + pImage->VDIo.pBackendData = pImage->pBackendData; + pImage->uImageFlags = uImageFlags; + + /* Lock disk for writing, as we modify pDisk information below. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + /* Switch previous image to read-only mode. */ + unsigned uOpenFlagsPrevImg; + uOpenFlagsPrevImg = pDisk->pLast->Backend->pfnGetOpenFlags(pDisk->pLast->pBackendData); + if (!(uOpenFlagsPrevImg & VD_OPEN_FLAGS_READONLY)) + { + uOpenFlagsPrevImg |= VD_OPEN_FLAGS_READONLY; + rc = pDisk->pLast->Backend->pfnSetOpenFlags(pDisk->pLast->pBackendData, uOpenFlagsPrevImg); + } + + /** @todo optionally check UUIDs */ + + /* Re-check state, as the lock wasn't held and another image + * creation call could have been done by another thread. */ + AssertMsgStmt(pDisk->cImages != 0, + ("Create diff image cannot be done without other images open\n"), + rc = VERR_VD_INVALID_STATE); + } + + if (RT_SUCCESS(rc)) + { + RTUUID Uuid; + RTTIMESPEC ts; + + if (pParentUuid && !RTUuidIsNull(pParentUuid)) + { + Uuid = *pParentUuid; + pImage->Backend->pfnSetParentUuid(pImage->pBackendData, &Uuid); + } + else + { + rc2 = pDisk->pLast->Backend->pfnGetUuid(pDisk->pLast->pBackendData, + &Uuid); + if (RT_SUCCESS(rc2)) + pImage->Backend->pfnSetParentUuid(pImage->pBackendData, &Uuid); + } + rc2 = pDisk->pLast->Backend->pfnGetModificationUuid(pDisk->pLast->pBackendData, + &Uuid); + if (RT_SUCCESS(rc2)) + pImage->Backend->pfnSetParentModificationUuid(pImage->pBackendData, + &Uuid); + if (pDisk->pLast->Backend->pfnGetTimestamp) + rc2 = pDisk->pLast->Backend->pfnGetTimestamp(pDisk->pLast->pBackendData, + &ts); + else + rc2 = VERR_NOT_IMPLEMENTED; + if (RT_SUCCESS(rc2) && pImage->Backend->pfnSetParentTimestamp) + pImage->Backend->pfnSetParentTimestamp(pImage->pBackendData, &ts); + + if (pImage->Backend->pfnSetParentFilename) + rc2 = pImage->Backend->pfnSetParentFilename(pImage->pBackendData, pDisk->pLast->pszFilename); + } + + if (RT_SUCCESS(rc)) + { + /* Image successfully opened, make it the last image. */ + vdAddImageToList(pDisk, pImage); + if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY)) + pDisk->uModified = VD_IMAGE_MODIFIED_FIRST; + } + else + { + /* Error detected, but image opened. Close and delete image. */ + rc2 = pImage->Backend->pfnClose(pImage->pBackendData, true); + AssertRC(rc2); + pImage->pBackendData = NULL; + } + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + else if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + if (RT_FAILURE(rc)) + { + if (pImage) + { + if (pImage->pszFilename) + RTStrFree(pImage->pszFilename); + RTMemFree(pImage); + } + } + + if (RT_SUCCESS(rc) && pIfProgress && pIfProgress->pfnProgress) + pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * Creates and opens new cache image file in HDD container. + * + * @return VBox status code. + * @param pDisk Name of the cache file backend to use (case insensitive). + * @param pszFilename Name of the differencing cache file to create. + * @param cbSize Maximum size of the cache. + * @param uImageFlags Flags specifying special cache features. + * @param pszComment Pointer to image comment. NULL is ok. + * @param pUuid New UUID of the image. If NULL, a new UUID is created. + * @param uOpenFlags Image file open mode, see VD_OPEN_FLAGS_* constants. + * @param pVDIfsCache Pointer to the per-cache VD interface list. + * @param pVDIfsOperation Pointer to the per-operation VD interface list. + */ +VBOXDDU_DECL(int) VDCreateCache(PVDISK pDisk, const char *pszBackend, + const char *pszFilename, uint64_t cbSize, + unsigned uImageFlags, const char *pszComment, + PCRTUUID pUuid, unsigned uOpenFlags, + PVDINTERFACE pVDIfsCache, PVDINTERFACE pVDIfsOperation) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false, fLockRead = false; + PVDCACHE pCache = NULL; + RTUUID uuid; + + LogFlowFunc(("pDisk=%#p pszBackend=\"%s\" pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" Uuid=%RTuuid uOpenFlags=%#x pVDIfsImage=%#p pVDIfsOperation=%#p\n", + pDisk, pszBackend, pszFilename, cbSize, uImageFlags, pszComment, pUuid, uOpenFlags, pVDIfsCache, pVDIfsOperation)); + + PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation); + + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pszBackend) && *pszBackend, + ("pszBackend=%#p \"%s\"\n", pszBackend, pszBackend), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt(VALID_PTR(pszFilename) && *pszFilename, + ("pszFilename=%#p \"%s\"\n", pszFilename, pszFilename), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt(cbSize, + ("cbSize=%llu\n", cbSize), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt((uImageFlags & ~VD_IMAGE_FLAGS_MASK) == 0, + ("uImageFlags=%#x\n", uImageFlags), + rc = VERR_INVALID_PARAMETER); + /* The UUID may be NULL. */ + AssertMsgBreakStmt(pUuid == NULL || VALID_PTR(pUuid), + ("pUuid=%#p UUID=%RTuuid\n", pUuid, pUuid), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0, + ("uOpenFlags=%#x\n", uOpenFlags), + rc = VERR_INVALID_PARAMETER); + + /* Check state. Needs a temporary read lock. Holding the write lock + * all the time would be blocking other activities for too long. */ + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + AssertMsgBreakStmt(!pDisk->pCache, + ("Create cache image cannot be done with a cache already attached\n"), + rc = VERR_VD_CACHE_ALREADY_EXISTS); + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + fLockRead = false; + + /* Set up image descriptor. */ + pCache = (PVDCACHE)RTMemAllocZ(sizeof(VDCACHE)); + if (!pCache) + { + rc = VERR_NO_MEMORY; + break; + } + pCache->pszFilename = RTStrDup(pszFilename); + if (!pCache->pszFilename) + { + rc = VERR_NO_MEMORY; + break; + } + + rc = vdFindCacheBackend(pszBackend, &pCache->Backend); + if (RT_FAILURE(rc)) + break; + if (!pCache->Backend) + { + rc = vdError(pDisk, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("VD: unknown backend name '%s'"), pszBackend); + break; + } + + pCache->VDIo.pDisk = pDisk; + pCache->pVDIfsCache = pVDIfsCache; + + /* Set up the I/O interface. */ + pCache->VDIo.pInterfaceIo = VDIfIoGet(pVDIfsCache); + if (!pCache->VDIo.pInterfaceIo) + { + vdIfIoFallbackCallbacksSetup(&pCache->VDIo.VDIfIo); + rc = VDInterfaceAdd(&pCache->VDIo.VDIfIo.Core, "VD_IO", VDINTERFACETYPE_IO, + pDisk, sizeof(VDINTERFACEIO), &pVDIfsCache); + pCache->VDIo.pInterfaceIo = &pCache->VDIo.VDIfIo; + } + + /* Set up the internal I/O interface. */ + AssertBreakStmt(!VDIfIoIntGet(pVDIfsCache), rc = VERR_INVALID_PARAMETER); + vdIfIoIntCallbacksSetup(&pCache->VDIo.VDIfIoInt); + rc = VDInterfaceAdd(&pCache->VDIo.VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT, + &pCache->VDIo, sizeof(VDINTERFACEIOINT), &pCache->pVDIfsCache); + AssertRC(rc); + + /* Create UUID if the caller didn't specify one. */ + if (!pUuid) + { + rc = RTUuidCreate(&uuid); + if (RT_FAILURE(rc)) + { + rc = vdError(pDisk, rc, RT_SRC_POS, + N_("VD: cannot generate UUID for image '%s'"), + pszFilename); + break; + } + pUuid = &uuid; + } + + pCache->uOpenFlags = uOpenFlags & VD_OPEN_FLAGS_HONOR_SAME; + pCache->VDIo.fIgnoreFlush = (uOpenFlags & VD_OPEN_FLAGS_IGNORE_FLUSH) != 0; + rc = pCache->Backend->pfnCreate(pCache->pszFilename, cbSize, + uImageFlags, + pszComment, pUuid, + uOpenFlags & ~VD_OPEN_FLAGS_HONOR_SAME, + 0, 99, + pDisk->pVDIfsDisk, + pCache->pVDIfsCache, + pVDIfsOperation, + &pCache->pBackendData); + + if (RT_SUCCESS(rc)) + { + /* Lock disk for writing, as we modify pDisk information below. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + pCache->VDIo.pBackendData = pCache->pBackendData; + + /* Re-check state, as the lock wasn't held and another image + * creation call could have been done by another thread. */ + AssertMsgStmt(!pDisk->pCache, + ("Create cache image cannot be done with another cache open\n"), + rc = VERR_VD_CACHE_ALREADY_EXISTS); + } + + if ( RT_SUCCESS(rc) + && pDisk->pLast) + { + RTUUID UuidModification; + + /* Set same modification Uuid as the last image. */ + rc = pDisk->pLast->Backend->pfnGetModificationUuid(pDisk->pLast->pBackendData, + &UuidModification); + if (RT_SUCCESS(rc)) + { + rc = pCache->Backend->pfnSetModificationUuid(pCache->pBackendData, + &UuidModification); + } + + if (rc == VERR_NOT_SUPPORTED) + rc = VINF_SUCCESS; + } + + if (RT_SUCCESS(rc)) + { + /* Cache successfully created. */ + pDisk->pCache = pCache; + } + else + { + /* Error detected, but image opened. Close and delete image. */ + rc2 = pCache->Backend->pfnClose(pCache->pBackendData, true); + AssertRC(rc2); + pCache->pBackendData = NULL; + } + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + else if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + if (RT_FAILURE(rc)) + { + if (pCache) + { + if (pCache->pszFilename) + RTStrFree(pCache->pszFilename); + RTMemFree(pCache); + } + } + + if (RT_SUCCESS(rc) && pIfProgress && pIfProgress->pfnProgress) + pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Merges two images (not necessarily with direct parent/child relationship). + * As a side effect the source image and potentially the other images which + * are also merged to the destination are deleted from both the disk and the + * images in the HDD container. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @param pDisk Pointer to HDD container. + * @param nImageFrom Name of the image file to merge from. + * @param nImageTo Name of the image file to merge to. + * @param pVDIfsOperation Pointer to the per-operation VD interface list. + */ +VBOXDDU_DECL(int) VDMerge(PVDISK pDisk, unsigned nImageFrom, + unsigned nImageTo, PVDINTERFACE pVDIfsOperation) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false, fLockRead = false; + void *pvBuf = NULL; + + LogFlowFunc(("pDisk=%#p nImageFrom=%u nImageTo=%u pVDIfsOperation=%#p\n", + pDisk, nImageFrom, nImageTo, pVDIfsOperation)); + + PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation); + + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* For simplicity reasons lock for writing as the image reopen below + * might need it. After all the reopen is usually needed. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + PVDIMAGE pImageFrom = vdGetImageByNumber(pDisk, nImageFrom); + PVDIMAGE pImageTo = vdGetImageByNumber(pDisk, nImageTo); + if (!pImageFrom || !pImageTo) + { + rc = VERR_VD_IMAGE_NOT_FOUND; + break; + } + AssertBreakStmt(pImageFrom != pImageTo, rc = VERR_INVALID_PARAMETER); + + /* Make sure destination image is writable. */ + unsigned uOpenFlags = pImageTo->Backend->pfnGetOpenFlags(pImageTo->pBackendData); + if (uOpenFlags & VD_OPEN_FLAGS_READONLY) + { + /* + * Clear skip consistency checks because the image is made writable now and + * skipping consistency checks is only possible for readonly images. + */ + uOpenFlags &= ~(VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS); + rc = pImageTo->Backend->pfnSetOpenFlags(pImageTo->pBackendData, + uOpenFlags); + if (RT_FAILURE(rc)) + break; + } + + /* Get size of destination image. */ + uint64_t cbSize = vdImageGetSize(pImageTo); + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + fLockWrite = false; + + /* Allocate tmp buffer. */ + pvBuf = RTMemTmpAlloc(VD_MERGE_BUFFER_SIZE); + if (!pvBuf) + { + rc = VERR_NO_MEMORY; + break; + } + + /* Merging is done directly on the images itself. This potentially + * causes trouble if the disk is full in the middle of operation. */ + if (nImageFrom < nImageTo) + { + /* Merge parent state into child. This means writing all not + * allocated blocks in the destination image which are allocated in + * the images to be merged. */ + uint64_t uOffset = 0; + uint64_t cbRemaining = cbSize; + + do + { + size_t cbThisRead = RT_MIN(VD_MERGE_BUFFER_SIZE, cbRemaining); + RTSGSEG SegmentBuf; + RTSGBUF SgBuf; + VDIOCTX IoCtx; + + 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); + + /* Need to hold the write lock during a read-write operation. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + rc = pImageTo->Backend->pfnRead(pImageTo->pBackendData, + uOffset, cbThisRead, + &IoCtx, &cbThisRead); + if (rc == VERR_VD_BLOCK_FREE) + { + /* 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. */ + for (PVDIMAGE pCurrImage = pImageTo->pPrev; + pCurrImage != NULL && pCurrImage != pImageFrom->pPrev && rc == VERR_VD_BLOCK_FREE; + pCurrImage = pCurrImage->pPrev) + { + /* + * Skip reading when offset exceeds image size which can happen when the target is + * bigger than the source. + */ + uint64_t cbImage = vdImageGetSize(pCurrImage); + if (uOffset < cbImage) + { + cbThisRead = RT_MIN(cbThisRead, cbImage - uOffset); + rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData, + uOffset, cbThisRead, + &IoCtx, &cbThisRead); + } + else + rc = VERR_VD_BLOCK_FREE; + } + + if (rc != VERR_VD_BLOCK_FREE) + { + if (RT_FAILURE(rc)) + break; + /* Updating the cache is required because this might be a live merge. */ + rc = vdWriteHelperEx(pDisk, pImageTo, pImageFrom->pPrev, + uOffset, pvBuf, cbThisRead, + VDIOCTX_FLAGS_READ_UPDATE_CACHE, 0); + if (RT_FAILURE(rc)) + break; + } + else + rc = VINF_SUCCESS; + } + else if (RT_FAILURE(rc)) + break; + + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + fLockWrite = false; + + uOffset += cbThisRead; + cbRemaining -= cbThisRead; + + if (pIfProgress && pIfProgress->pfnProgress) + { + /** @todo r=klaus: this can update the progress to the same + * percentage over and over again if the image format makes + * relatively small increments. */ + rc = pIfProgress->pfnProgress(pIfProgress->Core.pvUser, + uOffset * 99 / cbSize); + if (RT_FAILURE(rc)) + break; + } + } while (uOffset < cbSize); + } + else + { + /* + * We may need to update the parent uuid of the child coming after + * the last image to be merged. We have to reopen it read/write. + * + * This is done before we do the actual merge to prevent an + * inconsistent chain if the mode change fails for some reason. + */ + if (pImageFrom->pNext) + { + PVDIMAGE pImageChild = pImageFrom->pNext; + + /* Take the write lock. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + /* We need to open the image in read/write mode. */ + uOpenFlags = pImageChild->Backend->pfnGetOpenFlags(pImageChild->pBackendData); + + if (uOpenFlags & VD_OPEN_FLAGS_READONLY) + { + uOpenFlags &= ~VD_OPEN_FLAGS_READONLY; + rc = pImageChild->Backend->pfnSetOpenFlags(pImageChild->pBackendData, + uOpenFlags); + if (RT_FAILURE(rc)) + break; + } + + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + fLockWrite = false; + } + + /* If the merge is from the last image we have to relay all writes + * to the merge destination as well, so that concurrent writes + * (in case of a live merge) are handled correctly. */ + if (!pImageFrom->pNext) + { + /* Take the write lock. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + pDisk->pImageRelay = pImageTo; + + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + fLockWrite = false; + } + + /* Merge child state into parent. This means writing all blocks + * which are allocated in the image up to the source image to the + * destination image. */ + unsigned uProgressOld = 0; + uint64_t uOffset = 0; + uint64_t cbRemaining = cbSize; + do + { + size_t cbThisRead = RT_MIN(VD_MERGE_BUFFER_SIZE, cbRemaining); + RTSGSEG SegmentBuf; + RTSGBUF SgBuf; + VDIOCTX IoCtx; + + rc = VERR_VD_BLOCK_FREE; + + 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); + + /* Need to hold the write lock during a read-write operation. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + /* 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. */ + for (PVDIMAGE pCurrImage = pImageFrom; + pCurrImage != NULL && pCurrImage != pImageTo && rc == VERR_VD_BLOCK_FREE; + pCurrImage = pCurrImage->pPrev) + { + /* + * Skip reading when offset exceeds image size which can happen when the target is + * bigger than the source. + */ + uint64_t cbImage = vdImageGetSize(pCurrImage); + if (uOffset < cbImage) + { + cbThisRead = RT_MIN(cbThisRead, cbImage - uOffset); + rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData, + uOffset, cbThisRead, + &IoCtx, &cbThisRead); + } + else + rc = VERR_VD_BLOCK_FREE; + } + + if (rc != VERR_VD_BLOCK_FREE) + { + if (RT_FAILURE(rc)) + break; + rc = vdWriteHelper(pDisk, pImageTo, uOffset, pvBuf, + cbThisRead, VDIOCTX_FLAGS_READ_UPDATE_CACHE); + if (RT_FAILURE(rc)) + break; + } + else + rc = VINF_SUCCESS; + + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + fLockWrite = false; + + uOffset += cbThisRead; + cbRemaining -= cbThisRead; + + unsigned uProgressNew = uOffset * 99 / cbSize; + if (uProgressNew != uProgressOld) + { + uProgressOld = uProgressNew; + + if (pIfProgress && pIfProgress->pfnProgress) + { + rc = pIfProgress->pfnProgress(pIfProgress->Core.pvUser, + uProgressOld); + if (RT_FAILURE(rc)) + break; + } + } + + } while (uOffset < cbSize); + + /* In case we set up a "write proxy" image above we must clear + * this again now to prevent stray writes. Failure or not. */ + if (!pImageFrom->pNext) + { + /* Take the write lock. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + pDisk->pImageRelay = NULL; + + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + fLockWrite = false; + } + } + + /* + * Leave in case of an error to avoid corrupted data in the image chain + * (includes cancelling the operation by the user). + */ + if (RT_FAILURE(rc)) + break; + + /* Need to hold the write lock while finishing the merge. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + /* Update parent UUID so that image chain is consistent. + * The two attempts work around the problem that some backends + * (e.g. iSCSI) do not support UUIDs, so we exploit the fact that + * so far there can only be one such image in the chain. */ + /** @todo needs a better long-term solution, passing the UUID + * knowledge from the caller or some such */ + RTUUID Uuid; + PVDIMAGE pImageChild = NULL; + if (nImageFrom < nImageTo) + { + if (pImageFrom->pPrev) + { + /* plan A: ask the parent itself for its UUID */ + rc = pImageFrom->pPrev->Backend->pfnGetUuid(pImageFrom->pPrev->pBackendData, + &Uuid); + if (RT_FAILURE(rc)) + { + /* plan B: ask the child of the parent for parent UUID */ + rc = pImageFrom->Backend->pfnGetParentUuid(pImageFrom->pBackendData, + &Uuid); + } + AssertRC(rc); + } + else + RTUuidClear(&Uuid); + rc = pImageTo->Backend->pfnSetParentUuid(pImageTo->pBackendData, + &Uuid); + AssertRC(rc); + } + else + { + /* Update the parent uuid of the child of the last merged image. */ + if (pImageFrom->pNext) + { + /* plan A: ask the parent itself for its UUID */ + rc = pImageTo->Backend->pfnGetUuid(pImageTo->pBackendData, + &Uuid); + if (RT_FAILURE(rc)) + { + /* plan B: ask the child of the parent for parent UUID */ + rc = pImageTo->pNext->Backend->pfnGetParentUuid(pImageTo->pNext->pBackendData, + &Uuid); + } + AssertRC(rc); + + rc = pImageFrom->Backend->pfnSetParentUuid(pImageFrom->pNext->pBackendData, + &Uuid); + AssertRC(rc); + + pImageChild = pImageFrom->pNext; + } + } + + /* Delete the no longer needed images. */ + PVDIMAGE pImg = pImageFrom, pTmp; + while (pImg != pImageTo) + { + if (nImageFrom < nImageTo) + pTmp = pImg->pNext; + else + pTmp = pImg->pPrev; + vdRemoveImageFromList(pDisk, pImg); + pImg->Backend->pfnClose(pImg->pBackendData, true); + RTMemFree(pImg->pszFilename); + RTMemFree(pImg); + pImg = pTmp; + } + + /* Make sure destination image is back to read only if necessary. */ + if (pImageTo != pDisk->pLast) + { + uOpenFlags = pImageTo->Backend->pfnGetOpenFlags(pImageTo->pBackendData); + uOpenFlags |= VD_OPEN_FLAGS_READONLY; + rc = pImageTo->Backend->pfnSetOpenFlags(pImageTo->pBackendData, + uOpenFlags); + if (RT_FAILURE(rc)) + break; + } + + /* + * Make sure the child is readonly + * for the child -> parent merge direction + * if necessary. + */ + if ( nImageFrom > nImageTo + && pImageChild + && pImageChild != pDisk->pLast) + { + uOpenFlags = pImageChild->Backend->pfnGetOpenFlags(pImageChild->pBackendData); + uOpenFlags |= VD_OPEN_FLAGS_READONLY; + rc = pImageChild->Backend->pfnSetOpenFlags(pImageChild->pBackendData, + uOpenFlags); + if (RT_FAILURE(rc)) + break; + } + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + else if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + if (pvBuf) + RTMemTmpFree(pvBuf); + + if (RT_SUCCESS(rc) && pIfProgress && pIfProgress->pfnProgress) + pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Copies an image from one HDD container to another - extended version. + * The copy is opened in the target HDD container. + * It is possible to convert between different image formats, because the + * backend for the destination may be different from the source. + * If both the source and destination reference the same HDD container, + * then the image is moved (by copying/deleting or renaming) to the new location. + * The source container is unchanged if the move operation fails, otherwise + * the image at the new location is opened in the same way as the old one was. + * + * @note The read/write accesses across disks are not synchronized, just the + * accesses to each disk. Once there is a use case which requires a defined + * read/write behavior in this situation this needs to be extended. + * + * @returns VBox status code. + * @retval VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @param pDiskFrom Pointer to source HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pDiskTo Pointer to destination HDD container. + * @param pszBackend Name of the image file backend to use (may be NULL to use the same as the source, case insensitive). + * @param pszFilename New name of the image (may be NULL to specify that the + * copy destination is the destination container, or + * if pDiskFrom == pDiskTo, i.e. when moving). + * @param fMoveByRename If true, attempt to perform a move by renaming (if successful the new size is ignored). + * @param cbSize New image size (0 means leave unchanged). + * @param nImageFromSame todo + * @param nImageToSame todo + * @param uImageFlags Flags specifying special destination image features. + * @param pDstUuid New UUID of the destination image. If NULL, a new UUID is created. + * This parameter is used if and only if a true copy is created. + * In all rename/move cases or copy to existing image cases the modification UUIDs are copied over. + * @param uOpenFlags Image file open mode, see VD_OPEN_FLAGS_* constants. + * Only used if the destination image is created. + * @param pVDIfsOperation Pointer to the per-operation VD interface list. + * @param pDstVDIfsImage Pointer to the per-image VD interface list, for the + * destination image. + * @param pDstVDIfsOperation Pointer to the per-operation VD interface list, + * for the destination operation. + */ +VBOXDDU_DECL(int) VDCopyEx(PVDISK pDiskFrom, unsigned nImage, PVDISK pDiskTo, + const char *pszBackend, const char *pszFilename, + bool fMoveByRename, uint64_t cbSize, + unsigned nImageFromSame, unsigned nImageToSame, + unsigned uImageFlags, PCRTUUID pDstUuid, + unsigned uOpenFlags, PVDINTERFACE pVDIfsOperation, + PVDINTERFACE pDstVDIfsImage, + PVDINTERFACE pDstVDIfsOperation) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockReadFrom = false, fLockWriteFrom = false, fLockWriteTo = false; + PVDIMAGE pImageTo = NULL; + + LogFlowFunc(("pDiskFrom=%#p nImage=%u pDiskTo=%#p pszBackend=\"%s\" pszFilename=\"%s\" fMoveByRename=%d cbSize=%llu nImageFromSame=%u nImageToSame=%u uImageFlags=%#x pDstUuid=%#p uOpenFlags=%#x pVDIfsOperation=%#p pDstVDIfsImage=%#p pDstVDIfsOperation=%#p\n", + pDiskFrom, nImage, pDiskTo, pszBackend, pszFilename, fMoveByRename, cbSize, nImageFromSame, nImageToSame, uImageFlags, pDstUuid, uOpenFlags, pVDIfsOperation, pDstVDIfsImage, pDstVDIfsOperation)); + + PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation); + PVDINTERFACEPROGRESS pDstIfProgress = VDIfProgressGet(pDstVDIfsOperation); + + do { + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pDiskFrom), ("pDiskFrom=%#p\n", pDiskFrom), + rc = VERR_INVALID_PARAMETER); + AssertMsg(pDiskFrom->u32Signature == VDISK_SIGNATURE, + ("u32Signature=%08x\n", pDiskFrom->u32Signature)); + + rc2 = vdThreadStartRead(pDiskFrom); + AssertRC(rc2); + fLockReadFrom = true; + PVDIMAGE pImageFrom = vdGetImageByNumber(pDiskFrom, nImage); + AssertPtrBreakStmt(pImageFrom, rc = VERR_VD_IMAGE_NOT_FOUND); + AssertMsgBreakStmt(VALID_PTR(pDiskTo), ("pDiskTo=%#p\n", pDiskTo), + rc = VERR_INVALID_PARAMETER); + AssertMsg(pDiskTo->u32Signature == VDISK_SIGNATURE, + ("u32Signature=%08x\n", pDiskTo->u32Signature)); + AssertMsgBreakStmt( (nImageFromSame < nImage || nImageFromSame == VD_IMAGE_CONTENT_UNKNOWN) + && (nImageToSame < pDiskTo->cImages || nImageToSame == VD_IMAGE_CONTENT_UNKNOWN) + && ( (nImageFromSame == VD_IMAGE_CONTENT_UNKNOWN && nImageToSame == VD_IMAGE_CONTENT_UNKNOWN) + || (nImageFromSame != VD_IMAGE_CONTENT_UNKNOWN && nImageToSame != VD_IMAGE_CONTENT_UNKNOWN)), + ("nImageFromSame=%u nImageToSame=%u\n", nImageFromSame, nImageToSame), + rc = VERR_INVALID_PARAMETER); + + /* Move the image. */ + if (pDiskFrom == pDiskTo) + { + /* Rename only works when backends are the same, are file based + * and the rename method is implemented. */ + if ( fMoveByRename + && !RTStrICmp(pszBackend, pImageFrom->Backend->pszBackendName) + && pImageFrom->Backend->uBackendCaps & VD_CAP_FILE + && pImageFrom->Backend->pfnRename) + { + rc2 = vdThreadFinishRead(pDiskFrom); + AssertRC(rc2); + fLockReadFrom = false; + + rc2 = vdThreadStartWrite(pDiskFrom); + AssertRC(rc2); + fLockWriteFrom = true; + rc = pImageFrom->Backend->pfnRename(pImageFrom->pBackendData, pszFilename ? pszFilename : pImageFrom->pszFilename); + break; + } + + /** @todo Moving (including shrinking/growing) of the image is + * requested, but the rename attempt failed or it wasn't possible. + * Must now copy image to temp location. */ + AssertReleaseMsgFailed(("VDCopy: moving by copy/delete not implemented\n")); + } + + /* pszFilename is allowed to be NULL, as this indicates copy to the existing image. */ + AssertMsgBreakStmt(pszFilename == NULL || (VALID_PTR(pszFilename) && *pszFilename), + ("pszFilename=%#p \"%s\"\n", pszFilename, pszFilename), + rc = VERR_INVALID_PARAMETER); + + uint64_t cbSizeFrom; + cbSizeFrom = vdImageGetSize(pImageFrom); + if (cbSizeFrom == 0) + { + rc = VERR_VD_VALUE_NOT_FOUND; + break; + } + + VDGEOMETRY PCHSGeometryFrom = {0, 0, 0}; + VDGEOMETRY LCHSGeometryFrom = {0, 0, 0}; + pImageFrom->Backend->pfnGetPCHSGeometry(pImageFrom->pBackendData, &PCHSGeometryFrom); + pImageFrom->Backend->pfnGetLCHSGeometry(pImageFrom->pBackendData, &LCHSGeometryFrom); + + RTUUID ImageUuid, ImageModificationUuid; + if (pDiskFrom != pDiskTo) + { + if (pDstUuid) + ImageUuid = *pDstUuid; + else + RTUuidCreate(&ImageUuid); + } + else + { + rc = pImageFrom->Backend->pfnGetUuid(pImageFrom->pBackendData, &ImageUuid); + if (RT_FAILURE(rc)) + RTUuidCreate(&ImageUuid); + } + rc = pImageFrom->Backend->pfnGetModificationUuid(pImageFrom->pBackendData, &ImageModificationUuid); + if (RT_FAILURE(rc)) + RTUuidClear(&ImageModificationUuid); + + char szComment[1024]; + rc = pImageFrom->Backend->pfnGetComment(pImageFrom->pBackendData, szComment, sizeof(szComment)); + if (RT_FAILURE(rc)) + szComment[0] = '\0'; + else + szComment[sizeof(szComment) - 1] = '\0'; + + rc2 = vdThreadFinishRead(pDiskFrom); + AssertRC(rc2); + fLockReadFrom = false; + + rc2 = vdThreadStartRead(pDiskTo); + AssertRC(rc2); + unsigned cImagesTo = pDiskTo->cImages; + rc2 = vdThreadFinishRead(pDiskTo); + AssertRC(rc2); + + if (pszFilename) + { + if (cbSize == 0) + cbSize = cbSizeFrom; + + /* Create destination image with the properties of source image. */ + /** @todo replace the VDCreateDiff/VDCreateBase calls by direct + * calls to the backend. Unifies the code and reduces the API + * dependencies. Would also make the synchronization explicit. */ + if (cImagesTo > 0) + { + rc = VDCreateDiff(pDiskTo, pszBackend, pszFilename, + uImageFlags, szComment, &ImageUuid, + NULL /* pParentUuid */, + uOpenFlags & ~VD_OPEN_FLAGS_READONLY, + pDstVDIfsImage, NULL); + + rc2 = vdThreadStartWrite(pDiskTo); + AssertRC(rc2); + fLockWriteTo = true; + } else { + /** @todo hack to force creation of a fixed image for + * the RAW backend, which can't handle anything else. */ + if (!RTStrICmp(pszBackend, "RAW")) + uImageFlags |= VD_IMAGE_FLAGS_FIXED; + + vdFixupPCHSGeometry(&PCHSGeometryFrom, cbSize); + vdFixupLCHSGeometry(&LCHSGeometryFrom, cbSize); + + rc = VDCreateBase(pDiskTo, pszBackend, pszFilename, cbSize, + uImageFlags, szComment, + &PCHSGeometryFrom, &LCHSGeometryFrom, + NULL, uOpenFlags & ~VD_OPEN_FLAGS_READONLY, + pDstVDIfsImage, NULL); + + rc2 = vdThreadStartWrite(pDiskTo); + AssertRC(rc2); + fLockWriteTo = true; + + if (RT_SUCCESS(rc) && !RTUuidIsNull(&ImageUuid)) + pDiskTo->pLast->Backend->pfnSetUuid(pDiskTo->pLast->pBackendData, &ImageUuid); + } + if (RT_FAILURE(rc)) + break; + + pImageTo = pDiskTo->pLast; + AssertPtrBreakStmt(pImageTo, rc = VERR_VD_IMAGE_NOT_FOUND); + + cbSize = RT_MIN(cbSize, cbSizeFrom); + } + else + { + pImageTo = pDiskTo->pLast; + AssertPtrBreakStmt(pImageTo, rc = VERR_VD_IMAGE_NOT_FOUND); + + uint64_t cbSizeTo; + cbSizeTo = vdImageGetSize(pImageTo); + if (cbSizeTo == 0) + { + rc = VERR_VD_VALUE_NOT_FOUND; + break; + } + + if (cbSize == 0) + cbSize = RT_MIN(cbSizeFrom, cbSizeTo); + + vdFixupPCHSGeometry(&PCHSGeometryFrom, cbSize); + vdFixupLCHSGeometry(&LCHSGeometryFrom, cbSize); + + /* Update the geometry in the destination image. */ + pImageTo->Backend->pfnSetPCHSGeometry(pImageTo->pBackendData, &PCHSGeometryFrom); + pImageTo->Backend->pfnSetLCHSGeometry(pImageTo->pBackendData, &LCHSGeometryFrom); + } + + rc2 = vdThreadFinishWrite(pDiskTo); + AssertRC(rc2); + fLockWriteTo = false; + + /* Whether we can take the optimized copy path (false) or not. + * Don't optimize if the image existed or if it is a child image. */ + bool fSuppressRedundantIo = ( !(pszFilename == NULL || cImagesTo > 0) + || (nImageToSame != VD_IMAGE_CONTENT_UNKNOWN)); + unsigned cImagesFromReadBack, cImagesToReadBack; + + if (nImageFromSame == VD_IMAGE_CONTENT_UNKNOWN) + cImagesFromReadBack = 0; + else + { + if (nImage == VD_LAST_IMAGE) + cImagesFromReadBack = pDiskFrom->cImages - nImageFromSame - 1; + else + cImagesFromReadBack = nImage - nImageFromSame; + } + + if (nImageToSame == VD_IMAGE_CONTENT_UNKNOWN) + cImagesToReadBack = 0; + else + cImagesToReadBack = pDiskTo->cImages - nImageToSame - 1; + + /* Copy the data. */ + rc = vdCopyHelper(pDiskFrom, pImageFrom, pDiskTo, cbSize, + cImagesFromReadBack, cImagesToReadBack, + fSuppressRedundantIo, pIfProgress, pDstIfProgress); + + if (RT_SUCCESS(rc)) + { + rc2 = vdThreadStartWrite(pDiskTo); + AssertRC(rc2); + fLockWriteTo = true; + + /* Only set modification UUID if it is non-null, since the source + * backend might not provide a valid modification UUID. */ + if (!RTUuidIsNull(&ImageModificationUuid)) + pImageTo->Backend->pfnSetModificationUuid(pImageTo->pBackendData, &ImageModificationUuid); + + /* Set the requested open flags if they differ from the value + * required for creating the image and copying the contents. */ + if ( pImageTo && pszFilename + && uOpenFlags != (uOpenFlags & ~VD_OPEN_FLAGS_READONLY)) + rc = pImageTo->Backend->pfnSetOpenFlags(pImageTo->pBackendData, + uOpenFlags); + } + } while (0); + + if (RT_FAILURE(rc) && pImageTo && pszFilename) + { + /* Take the write lock only if it is not taken. Not worth making the + * above code even more complicated. */ + if (RT_UNLIKELY(!fLockWriteTo)) + { + rc2 = vdThreadStartWrite(pDiskTo); + AssertRC(rc2); + fLockWriteTo = true; + } + /* Error detected, but new image created. Remove image from list. */ + vdRemoveImageFromList(pDiskTo, pImageTo); + + /* Close and delete image. */ + rc2 = pImageTo->Backend->pfnClose(pImageTo->pBackendData, true); + AssertRC(rc2); + pImageTo->pBackendData = NULL; + + /* Free remaining resources. */ + if (pImageTo->pszFilename) + RTStrFree(pImageTo->pszFilename); + + RTMemFree(pImageTo); + } + + if (RT_UNLIKELY(fLockWriteTo)) + { + rc2 = vdThreadFinishWrite(pDiskTo); + AssertRC(rc2); + } + if (RT_UNLIKELY(fLockWriteFrom)) + { + rc2 = vdThreadFinishWrite(pDiskFrom); + AssertRC(rc2); + } + else if (RT_UNLIKELY(fLockReadFrom)) + { + rc2 = vdThreadFinishRead(pDiskFrom); + AssertRC(rc2); + } + + if (RT_SUCCESS(rc)) + { + if (pIfProgress && pIfProgress->pfnProgress) + pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100); + if (pDstIfProgress && pDstIfProgress->pfnProgress) + pDstIfProgress->pfnProgress(pDstIfProgress->Core.pvUser, 100); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Copies an image from one HDD container to another. + * The copy is opened in the target HDD container. + * It is possible to convert between different image formats, because the + * backend for the destination may be different from the source. + * If both the source and destination reference the same HDD container, + * then the image is moved (by copying/deleting or renaming) to the new location. + * The source container is unchanged if the move operation fails, otherwise + * the image at the new location is opened in the same way as the old one was. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @param pDiskFrom Pointer to source HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pDiskTo Pointer to destination HDD container. + * @param pszBackend Name of the image file backend to use. + * @param pszFilename New name of the image (may be NULL if pDiskFrom == pDiskTo). + * @param fMoveByRename If true, attempt to perform a move by renaming (if successful the new size is ignored). + * @param cbSize New image size (0 means leave unchanged). + * @param uImageFlags Flags specifying special destination image features. + * @param pDstUuid New UUID of the destination image. If NULL, a new UUID is created. + * This parameter is used if and only if a true copy is created. + * In all rename/move cases the UUIDs are copied over. + * @param uOpenFlags Image file open mode, see VD_OPEN_FLAGS_* constants. + * Only used if the destination image is created. + * @param pVDIfsOperation Pointer to the per-operation VD interface list. + * @param pDstVDIfsImage Pointer to the per-image VD interface list, for the + * destination image. + * @param pDstVDIfsOperation Pointer to the per-image VD interface list, + * for the destination image. + */ +VBOXDDU_DECL(int) VDCopy(PVDISK pDiskFrom, unsigned nImage, PVDISK pDiskTo, + const char *pszBackend, const char *pszFilename, + bool fMoveByRename, uint64_t cbSize, + unsigned uImageFlags, PCRTUUID pDstUuid, + unsigned uOpenFlags, PVDINTERFACE pVDIfsOperation, + PVDINTERFACE pDstVDIfsImage, + PVDINTERFACE pDstVDIfsOperation) +{ + return VDCopyEx(pDiskFrom, nImage, pDiskTo, pszBackend, pszFilename, fMoveByRename, + cbSize, VD_IMAGE_CONTENT_UNKNOWN, VD_IMAGE_CONTENT_UNKNOWN, + uImageFlags, pDstUuid, uOpenFlags, pVDIfsOperation, + pDstVDIfsImage, pDstVDIfsOperation); +} + +/** + * Optimizes the storage consumption of an image. Typically the unused blocks + * have to be wiped with zeroes to achieve a substantial reduced storage use. + * Another optimization done is reordering the image blocks, which can provide + * a significant performance boost, as reads and writes tend to use less random + * file offsets. + * + * @return VBox status code. + * @return VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @return VERR_VD_IMAGE_READ_ONLY if image is not writable. + * @return VERR_NOT_SUPPORTED if this kind of image can be compacted, but + * the code for this isn't implemented yet. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pVDIfsOperation Pointer to the per-operation VD interface list. + */ +VBOXDDU_DECL(int) VDCompact(PVDISK pDisk, unsigned nImage, + PVDINTERFACE pVDIfsOperation) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockRead = false, fLockWrite = false; + void *pvBuf = NULL; + void *pvTmp = NULL; + + LogFlowFunc(("pDisk=%#p nImage=%u pVDIfsOperation=%#p\n", + pDisk, nImage, pVDIfsOperation)); + + PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation); + + do { + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pDisk), ("pDisk=%#p\n", pDisk), + rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, + ("u32Signature=%08x\n", pDisk->u32Signature)); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + /* If there is no compact callback for not file based backends then + * the backend doesn't need compaction. No need to make much fuss about + * this. For file based ones signal this as not yet supported. */ + if (!pImage->Backend->pfnCompact) + { + if (pImage->Backend->uBackendCaps & VD_CAP_FILE) + rc = VERR_NOT_SUPPORTED; + else + rc = VINF_SUCCESS; + break; + } + + /* Insert interface for reading parent state into per-operation list, + * if there is a parent image. */ + VDINTERFACEPARENTSTATE VDIfParent; + VDPARENTSTATEDESC ParentUser; + if (pImage->pPrev) + { + VDIfParent.pfnParentRead = vdParentRead; + ParentUser.pDisk = pDisk; + ParentUser.pImage = pImage->pPrev; + rc = VDInterfaceAdd(&VDIfParent.Core, "VDCompact_ParentState", VDINTERFACETYPE_PARENTSTATE, + &ParentUser, sizeof(VDINTERFACEPARENTSTATE), &pVDIfsOperation); + AssertRC(rc); + } + + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + fLockRead = false; + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + rc = pImage->Backend->pfnCompact(pImage->pBackendData, + 0, 99, + pDisk->pVDIfsDisk, + pImage->pVDIfsImage, + pVDIfsOperation); + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + else if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + if (pvBuf) + RTMemTmpFree(pvBuf); + if (pvTmp) + RTMemTmpFree(pvTmp); + + if (RT_SUCCESS(rc)) + { + if (pIfProgress && pIfProgress->pfnProgress) + pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Resizes the given disk image to the given size. + * + * @return VBox status + * @return VERR_VD_IMAGE_READ_ONLY if image is not writable. + * @return VERR_NOT_SUPPORTED if this kind of image can be compacted, but + * + * @param pDisk Pointer to the HDD container. + * @param cbSize New size of the image. + * @param pPCHSGeometry Pointer to the new physical disk geometry <= (16383,16,63). Not NULL. + * @param pLCHSGeometry Pointer to the new logical disk geometry <= (x,255,63). Not NULL. + * @param pVDIfsOperation Pointer to the per-operation VD interface list. + */ +VBOXDDU_DECL(int) VDResize(PVDISK pDisk, uint64_t cbSize, + PCVDGEOMETRY pPCHSGeometry, + PCVDGEOMETRY pLCHSGeometry, + PVDINTERFACE pVDIfsOperation) +{ + /** @todo r=klaus resizing was designed to be part of VDCopy, so having a separate function is not desirable. */ + int rc = VINF_SUCCESS; + int rc2; + bool fLockRead = false, fLockWrite = false; + + LogFlowFunc(("pDisk=%#p cbSize=%llu pVDIfsOperation=%#p\n", + pDisk, cbSize, pVDIfsOperation)); + + PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation); + + do { + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pDisk), ("pDisk=%#p\n", pDisk), + rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, + ("u32Signature=%08x\n", pDisk->u32Signature)); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + /* Must have at least one image in the chain, will resize last. */ + AssertMsgBreakStmt(pDisk->cImages >= 1, ("cImages=%u\n", pDisk->cImages), + rc = VERR_NOT_SUPPORTED); + + PVDIMAGE pImage = pDisk->pLast; + + /* If there is no compact callback for not file based backends then + * the backend doesn't need compaction. No need to make much fuss about + * this. For file based ones signal this as not yet supported. */ + if (!pImage->Backend->pfnResize) + { + if (pImage->Backend->uBackendCaps & VD_CAP_FILE) + rc = VERR_NOT_SUPPORTED; + else + rc = VINF_SUCCESS; + break; + } + + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + fLockRead = false; + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + VDGEOMETRY PCHSGeometryOld; + VDGEOMETRY LCHSGeometryOld; + PCVDGEOMETRY pPCHSGeometryNew; + PCVDGEOMETRY pLCHSGeometryNew; + + if (pPCHSGeometry->cCylinders == 0) + { + /* Auto-detect marker, calculate new value ourself. */ + rc = pImage->Backend->pfnGetPCHSGeometry(pImage->pBackendData, &PCHSGeometryOld); + if (RT_SUCCESS(rc) && (PCHSGeometryOld.cCylinders != 0)) + PCHSGeometryOld.cCylinders = RT_MIN(cbSize / 512 / PCHSGeometryOld.cHeads / PCHSGeometryOld.cSectors, 16383); + else if (rc == VERR_VD_GEOMETRY_NOT_SET) + rc = VINF_SUCCESS; + + pPCHSGeometryNew = &PCHSGeometryOld; + } + else + pPCHSGeometryNew = pPCHSGeometry; + + if (pLCHSGeometry->cCylinders == 0) + { + /* Auto-detect marker, calculate new value ourself. */ + rc = pImage->Backend->pfnGetLCHSGeometry(pImage->pBackendData, &LCHSGeometryOld); + if (RT_SUCCESS(rc) && (LCHSGeometryOld.cCylinders != 0)) + LCHSGeometryOld.cCylinders = cbSize / 512 / LCHSGeometryOld.cHeads / LCHSGeometryOld.cSectors; + else if (rc == VERR_VD_GEOMETRY_NOT_SET) + rc = VINF_SUCCESS; + + pLCHSGeometryNew = &LCHSGeometryOld; + } + else + pLCHSGeometryNew = pLCHSGeometry; + + if (RT_SUCCESS(rc)) + rc = pImage->Backend->pfnResize(pImage->pBackendData, + cbSize, + pPCHSGeometryNew, + pLCHSGeometryNew, + 0, 99, + pDisk->pVDIfsDisk, + pImage->pVDIfsImage, + pVDIfsOperation); + /* Mark the image size as uninitialized so it gets recalculated the next time. */ + if (RT_SUCCESS(rc)) + pImage->cbImage = VD_IMAGE_SIZE_UNINITIALIZED; + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + else if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + if (RT_SUCCESS(rc)) + { + if (pIfProgress && pIfProgress->pfnProgress) + pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100); + + pDisk->cbSize = cbSize; + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +VBOXDDU_DECL(int) VDPrepareWithFilters(PVDISK pDisk, PVDINTERFACE pVDIfsOperation) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockRead = false, fLockWrite = false; + + LogFlowFunc(("pDisk=%#p pVDIfsOperation=%#p\n", pDisk, pVDIfsOperation)); + + PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation); + + do { + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pDisk), ("pDisk=%#p\n", pDisk), + rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, + ("u32Signature=%08x\n", pDisk->u32Signature)); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + /* Must have at least one image in the chain. */ + AssertMsgBreakStmt(pDisk->cImages >= 1, ("cImages=%u\n", pDisk->cImages), + rc = VERR_VD_NOT_OPENED); + + unsigned uOpenFlags = pDisk->pLast->Backend->pfnGetOpenFlags(pDisk->pLast->pBackendData); + AssertMsgBreakStmt(!(uOpenFlags & VD_OPEN_FLAGS_READONLY), + ("Last image should be read write"), + rc = VERR_VD_IMAGE_READ_ONLY); + + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + fLockRead = false; + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + /* + * Open all images in the chain in read write mode first to avoid running + * into an error in the middle of the process. + */ + PVDIMAGE pImage = pDisk->pBase; + + while (pImage) + { + uOpenFlags = pImage->Backend->pfnGetOpenFlags(pImage->pBackendData); + if (uOpenFlags & VD_OPEN_FLAGS_READONLY) + { + /* + * Clear skip consistency checks because the image is made writable now and + * skipping consistency checks is only possible for readonly images. + */ + uOpenFlags &= ~(VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS); + rc = pImage->Backend->pfnSetOpenFlags(pImage->pBackendData, uOpenFlags); + if (RT_FAILURE(rc)) + break; + } + pImage = pImage->pNext; + } + + if (RT_SUCCESS(rc)) + { + unsigned cImgCur = 0; + unsigned uPercentStart = 0; + unsigned uPercentSpan = 100 / pDisk->cImages - 1; + + /* Allocate tmp buffer. */ + void *pvBuf = RTMemTmpAlloc(VD_MERGE_BUFFER_SIZE); + if (!pvBuf) + { + rc = VERR_NO_MEMORY; + break; + } + + pImage = pDisk->pBase; + pDisk->fLocked = true; + + while ( pImage + && RT_SUCCESS(rc)) + { + /* Get size of image. */ + uint64_t cbSize = vdImageGetSize(pImage); + uint64_t cbSizeFile = pImage->Backend->pfnGetFileSize(pImage->pBackendData); + uint64_t cbFileWritten = 0; + uint64_t uOffset = 0; + uint64_t cbRemaining = cbSize; + + do + { + size_t cbThisRead = RT_MIN(VD_MERGE_BUFFER_SIZE, cbRemaining); + RTSGSEG SegmentBuf; + RTSGBUF SgBuf; + VDIOCTX IoCtx; + + 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); + + rc = pImage->Backend->pfnRead(pImage->pBackendData, uOffset, + cbThisRead, &IoCtx, &cbThisRead); + if (rc != VERR_VD_BLOCK_FREE) + { + if (RT_FAILURE(rc)) + break; + + /* Apply filter chains. */ + rc = vdFilterChainApplyRead(pDisk, uOffset, cbThisRead, &IoCtx); + if (RT_FAILURE(rc)) + break; + + rc = vdFilterChainApplyWrite(pDisk, uOffset, cbThisRead, &IoCtx); + if (RT_FAILURE(rc)) + break; + + RTSgBufReset(&SgBuf); + size_t cbThisWrite = 0; + size_t cbPreRead = 0; + size_t cbPostRead = 0; + rc = pImage->Backend->pfnWrite(pImage->pBackendData, uOffset, + cbThisRead, &IoCtx, &cbThisWrite, + &cbPreRead, &cbPostRead, 0); + if (RT_FAILURE(rc)) + break; + Assert(cbThisWrite == cbThisRead); + cbFileWritten += cbThisWrite; + } + else + rc = VINF_SUCCESS; + + uOffset += cbThisRead; + cbRemaining -= cbThisRead; + + if (pIfProgress && pIfProgress->pfnProgress) + { + rc2 = pIfProgress->pfnProgress(pIfProgress->Core.pvUser, + uPercentStart + cbFileWritten * uPercentSpan / cbSizeFile); + AssertRC(rc2); /* Cancelling this operation without leaving an inconsistent state is not possible. */ + } + } while (uOffset < cbSize); + + pImage = pImage->pNext; + cImgCur++; + uPercentStart += uPercentSpan; + } + + pDisk->fLocked = false; + if (pvBuf) + RTMemTmpFree(pvBuf); + } + + /* Change images except last one back to readonly. */ + pImage = pDisk->pBase; + while ( pImage != pDisk->pLast + && pImage) + { + uOpenFlags = pImage->Backend->pfnGetOpenFlags(pImage->pBackendData); + uOpenFlags |= VD_OPEN_FLAGS_READONLY; + rc2 = pImage->Backend->pfnSetOpenFlags(pImage->pBackendData, uOpenFlags); + if (RT_FAILURE(rc2)) + { + if (RT_SUCCESS(rc)) + rc = rc2; + break; + } + pImage = pImage->pNext; + } + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + else if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + if ( RT_SUCCESS(rc) + && pIfProgress + && pIfProgress->pfnProgress) + pIfProgress->pfnProgress(pIfProgress->Core.pvUser, 100); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Closes the last opened image file in HDD container. + * If previous image file was opened in read-only mode (the normal case) and + * the last opened image is in read-write mode then the previous image will be + * reopened in read/write mode. + * + * @returns VBox status code. + * @returns VERR_VD_NOT_OPENED if no image is opened in HDD container. + * @param pDisk Pointer to HDD container. + * @param fDelete If true, delete the image from the host disk. + */ +VBOXDDU_DECL(int) VDClose(PVDISK pDisk, bool fDelete) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false; + + LogFlowFunc(("pDisk=%#p fDelete=%d\n", pDisk, fDelete)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Not worth splitting this up into a read lock phase and write + * lock phase, as closing an image is a relatively fast operation + * dominated by the part which needs the write lock. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + PVDIMAGE pImage = pDisk->pLast; + if (!pImage) + { + rc = VERR_VD_NOT_OPENED; + break; + } + + /* Destroy the current discard state first which might still have pending blocks. */ + rc = vdDiscardStateDestroy(pDisk); + if (RT_FAILURE(rc)) + break; + + unsigned uOpenFlags = pImage->Backend->pfnGetOpenFlags(pImage->pBackendData); + /* Remove image from list of opened images. */ + vdRemoveImageFromList(pDisk, pImage); + /* Close (and optionally delete) image. */ + rc = pImage->Backend->pfnClose(pImage->pBackendData, fDelete); + /* Free remaining resources related to the image. */ + RTStrFree(pImage->pszFilename); + RTMemFree(pImage); + + pImage = pDisk->pLast; + if (!pImage) + break; + + /* If disk was previously in read/write mode, make sure it will stay + * like this (if possible) after closing this image. Set the open flags + * accordingly. */ + if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY)) + { + uOpenFlags = pImage->Backend->pfnGetOpenFlags(pImage->pBackendData); + uOpenFlags &= ~ VD_OPEN_FLAGS_READONLY; + rc = pImage->Backend->pfnSetOpenFlags(pImage->pBackendData, uOpenFlags); + } + + /* Cache disk information. */ + pDisk->cbSize = vdImageGetSize(pImage); + + /* Cache PCHS geometry. */ + rc2 = pImage->Backend->pfnGetPCHSGeometry(pImage->pBackendData, + &pDisk->PCHSGeometry); + if (RT_FAILURE(rc2)) + { + pDisk->PCHSGeometry.cCylinders = 0; + pDisk->PCHSGeometry.cHeads = 0; + pDisk->PCHSGeometry.cSectors = 0; + } + else + { + /* Make sure the PCHS geometry is properly clipped. */ + pDisk->PCHSGeometry.cCylinders = RT_MIN(pDisk->PCHSGeometry.cCylinders, 16383); + pDisk->PCHSGeometry.cHeads = RT_MIN(pDisk->PCHSGeometry.cHeads, 16); + pDisk->PCHSGeometry.cSectors = RT_MIN(pDisk->PCHSGeometry.cSectors, 63); + } + + /* Cache LCHS geometry. */ + rc2 = pImage->Backend->pfnGetLCHSGeometry(pImage->pBackendData, + &pDisk->LCHSGeometry); + if (RT_FAILURE(rc2)) + { + pDisk->LCHSGeometry.cCylinders = 0; + pDisk->LCHSGeometry.cHeads = 0; + pDisk->LCHSGeometry.cSectors = 0; + } + else + { + /* Make sure the LCHS geometry is properly clipped. */ + pDisk->LCHSGeometry.cHeads = RT_MIN(pDisk->LCHSGeometry.cHeads, 255); + pDisk->LCHSGeometry.cSectors = RT_MIN(pDisk->LCHSGeometry.cSectors, 63); + } + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Closes the currently opened cache image file in HDD container. + * + * @return VBox status code. + * @return VERR_VD_NOT_OPENED if no cache is opened in HDD container. + * @param pDisk Pointer to HDD container. + * @param fDelete If true, delete the image from the host disk. + */ +VBOXDDU_DECL(int) VDCacheClose(PVDISK pDisk, bool fDelete) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false; + PVDCACHE pCache = NULL; + + LogFlowFunc(("pDisk=%#p fDelete=%d\n", pDisk, fDelete)); + + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + AssertPtrBreakStmt(pDisk->pCache, rc = VERR_VD_CACHE_NOT_FOUND); + + pCache = pDisk->pCache; + pDisk->pCache = NULL; + + pCache->Backend->pfnClose(pCache->pBackendData, fDelete); + if (pCache->pszFilename) + RTStrFree(pCache->pszFilename); + RTMemFree(pCache); + } while (0); + + if (RT_LIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +VBOXDDU_DECL(int) VDFilterRemove(PVDISK pDisk, uint32_t fFlags) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false; + PVDFILTER pFilter = NULL; + + LogFlowFunc(("pDisk=%#p\n", pDisk)); + + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + AssertMsgBreakStmt(!(fFlags & ~VD_FILTER_FLAGS_MASK), + ("Invalid flags set (fFlags=%#x)\n", fFlags), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + if (fFlags & VD_FILTER_FLAGS_WRITE) + { + AssertBreakStmt(!RTListIsEmpty(&pDisk->ListFilterChainWrite), rc = VERR_VD_NOT_OPENED); + pFilter = RTListGetLast(&pDisk->ListFilterChainWrite, VDFILTER, ListNodeChainWrite); + AssertPtr(pFilter); + RTListNodeRemove(&pFilter->ListNodeChainWrite); + vdFilterRelease(pFilter); + } + + if (fFlags & VD_FILTER_FLAGS_READ) + { + AssertBreakStmt(!RTListIsEmpty(&pDisk->ListFilterChainRead), rc = VERR_VD_NOT_OPENED); + pFilter = RTListGetLast(&pDisk->ListFilterChainRead, VDFILTER, ListNodeChainRead); + AssertPtr(pFilter); + RTListNodeRemove(&pFilter->ListNodeChainRead); + vdFilterRelease(pFilter); + } + } while (0); + + if (RT_LIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Closes all opened image files in HDD container. + * + * @returns VBox status code. + * @param pDisk Pointer to HDD container. + */ +VBOXDDU_DECL(int) VDCloseAll(PVDISK pDisk) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false; + + LogFlowFunc(("pDisk=%#p\n", pDisk)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Lock the entire operation. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + PVDCACHE pCache = pDisk->pCache; + if (pCache) + { + rc2 = pCache->Backend->pfnClose(pCache->pBackendData, false); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + rc = rc2; + + if (pCache->pszFilename) + RTStrFree(pCache->pszFilename); + RTMemFree(pCache); + } + + PVDIMAGE pImage = pDisk->pLast; + while (VALID_PTR(pImage)) + { + PVDIMAGE pPrev = pImage->pPrev; + /* Remove image from list of opened images. */ + vdRemoveImageFromList(pDisk, pImage); + /* Close image. */ + rc2 = pImage->Backend->pfnClose(pImage->pBackendData, false); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + rc = rc2; + /* Free remaining resources related to the image. */ + RTStrFree(pImage->pszFilename); + RTMemFree(pImage); + pImage = pPrev; + } + Assert(!VALID_PTR(pDisk->pLast)); + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Removes all filters of the given HDD container. + * + * @return VBox status code. + * @param pDisk Pointer to HDD container. + */ +VBOXDDU_DECL(int) VDFilterRemoveAll(PVDISK pDisk) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false; + + LogFlowFunc(("pDisk=%#p\n", pDisk)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Lock the entire operation. */ + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + PVDFILTER pFilter, pFilterNext; + RTListForEachSafe(&pDisk->ListFilterChainWrite, pFilter, pFilterNext, VDFILTER, ListNodeChainWrite) + { + RTListNodeRemove(&pFilter->ListNodeChainWrite); + vdFilterRelease(pFilter); + } + + RTListForEachSafe(&pDisk->ListFilterChainRead, pFilter, pFilterNext, VDFILTER, ListNodeChainRead) + { + RTListNodeRemove(&pFilter->ListNodeChainRead); + vdFilterRelease(pFilter); + } + Assert(RTListIsEmpty(&pDisk->ListFilterChainRead)); + Assert(RTListIsEmpty(&pDisk->ListFilterChainWrite)); + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Read data from virtual HDD. + * + * @returns VBox status code. + * @retval VERR_VD_NOT_OPENED if no image is opened in HDD container. + * @param pDisk Pointer to HDD container. + * @param uOffset Offset of first reading byte from start of disk. + * @param pvBuf Pointer to buffer for reading data. + * @param cbRead Number of bytes to read. + */ +VBOXDDU_DECL(int) VDRead(PVDISK pDisk, uint64_t uOffset, void *pvBuf, + size_t cbRead) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p uOffset=%llu pvBuf=%p cbRead=%zu\n", + pDisk, uOffset, pvBuf, cbRead)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pvBuf), + ("pvBuf=%#p\n", pvBuf), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt(cbRead, + ("cbRead=%zu\n", cbRead), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + AssertMsgBreakStmt( uOffset < pDisk->cbSize + && cbRead <= pDisk->cbSize - uOffset, + ("uOffset=%llu cbRead=%zu pDisk->cbSize=%llu\n", + uOffset, cbRead, pDisk->cbSize), + rc = VERR_INVALID_PARAMETER); + + PVDIMAGE pImage = pDisk->pLast; + AssertPtrBreakStmt(pImage, rc = VERR_VD_NOT_OPENED); + + if (uOffset + cbRead > pDisk->cbSize) + { + /* Floppy images might be smaller than the standard expected by + the floppy controller code. So, we won't fail here. */ + AssertMsgBreakStmt(pDisk->enmType == VDTYPE_FLOPPY, + ("uOffset=%llu cbRead=%zu pDisk->cbSize=%llu\n", + uOffset, cbRead, pDisk->cbSize), + rc = VERR_EOF); + memset(pvBuf, 0xf6, cbRead); /* f6h = format.com filler byte */ + if (uOffset >= pDisk->cbSize) + break; + cbRead = pDisk->cbSize - uOffset; + } + + rc = vdReadHelper(pDisk, pImage, uOffset, pvBuf, cbRead, + true /* fUpdateCache */); + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Write data to virtual HDD. + * + * @returns VBox status code. + * @retval VERR_VD_NOT_OPENED if no image is opened in HDD container. + * @param pDisk Pointer to HDD container. + * @param uOffset Offset of the first byte being + * written from start of disk. + * @param pvBuf Pointer to buffer for writing data. + * @param cbWrite Number of bytes to write. + */ +VBOXDDU_DECL(int) VDWrite(PVDISK pDisk, uint64_t uOffset, const void *pvBuf, + size_t cbWrite) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false; + + LogFlowFunc(("pDisk=%#p uOffset=%llu pvBuf=%p cbWrite=%zu\n", + pDisk, uOffset, pvBuf, cbWrite)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pvBuf), + ("pvBuf=%#p\n", pvBuf), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt(cbWrite, + ("cbWrite=%zu\n", cbWrite), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + AssertMsgBreakStmt( uOffset < pDisk->cbSize + && cbWrite <= pDisk->cbSize - uOffset, + ("uOffset=%llu cbWrite=%zu pDisk->cbSize=%llu\n", + uOffset, cbWrite, pDisk->cbSize), + rc = VERR_INVALID_PARAMETER); + + PVDIMAGE pImage = pDisk->pLast; + AssertPtrBreakStmt(pImage, rc = VERR_VD_NOT_OPENED); + + vdSetModifiedFlag(pDisk); + rc = vdWriteHelper(pDisk, pImage, uOffset, pvBuf, cbWrite, + VDIOCTX_FLAGS_READ_UPDATE_CACHE); + if (RT_FAILURE(rc)) + break; + + /* If there is a merge (in the direction towards a parent) running + * concurrently then we have to also "relay" the write to this parent, + * as the merge position might be already past the position where + * this write is going. The "context" of the write can come from the + * natural chain, since merging either already did or will take care + * of the "other" content which is might be needed to fill the block + * to a full allocation size. The cache doesn't need to be touched + * as this write is covered by the previous one. */ + if (RT_UNLIKELY(pDisk->pImageRelay)) + rc = vdWriteHelper(pDisk, pDisk->pImageRelay, uOffset, + pvBuf, cbWrite, VDIOCTX_FLAGS_DEFAULT); + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Make sure the on disk representation of a virtual HDD is up to date. + * + * @returns VBox status code. + * @retval VERR_VD_NOT_OPENED if no image is opened in HDD container. + * @param pDisk Pointer to HDD container. + */ +VBOXDDU_DECL(int) VDFlush(PVDISK pDisk) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false; + + LogFlowFunc(("pDisk=%#p\n", pDisk)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + PVDIMAGE pImage = pDisk->pLast; + AssertPtrBreakStmt(pImage, rc = VERR_VD_NOT_OPENED); + + VDIOCTX IoCtx; + RTSEMEVENT hEventComplete = NIL_RTSEMEVENT; + + rc = RTSemEventCreate(&hEventComplete); + if (RT_FAILURE(rc)) + break; + + vdIoCtxInit(&IoCtx, pDisk, VDIOCTXTXDIR_FLUSH, 0, 0, pImage, NULL, + NULL, vdFlushHelperAsync, VDIOCTX_FLAGS_SYNC | VDIOCTX_FLAGS_DONT_FREE); + + IoCtx.Type.Root.pfnComplete = vdIoCtxSyncComplete; + IoCtx.Type.Root.pvUser1 = pDisk; + IoCtx.Type.Root.pvUser2 = hEventComplete; + rc = vdIoCtxProcessSync(&IoCtx, hEventComplete); + + RTSemEventDestroy(hEventComplete); + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Get number of opened images in HDD container. + * + * @returns Number of opened images for HDD container. 0 if no images have been opened. + * @param pDisk Pointer to HDD container. + */ +VBOXDDU_DECL(unsigned) VDGetCount(PVDISK pDisk) +{ + unsigned cImages; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p\n", pDisk)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, cImages = 0); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + cImages = pDisk->cImages; + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %u\n", cImages)); + return cImages; +} + +/** + * Get read/write mode of HDD container. + * + * @returns Virtual disk ReadOnly status. + * @returns true if no image is opened in HDD container. + * @param pDisk Pointer to HDD container. + */ +VBOXDDU_DECL(bool) VDIsReadOnly(PVDISK pDisk) +{ + bool fReadOnly; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p\n", pDisk)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, fReadOnly = false); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = pDisk->pLast; + AssertPtrBreakStmt(pImage, fReadOnly = true); + + unsigned uOpenFlags; + uOpenFlags = pDisk->pLast->Backend->pfnGetOpenFlags(pDisk->pLast->pBackendData); + fReadOnly = !!(uOpenFlags & VD_OPEN_FLAGS_READONLY); + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %d\n", fReadOnly)); + return fReadOnly; +} + +/** + * Get sector size of an image in HDD container. + * + * @return Virtual disk sector size in bytes. + * @return 0 if image with specified number was not opened. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + */ +VBOXDDU_DECL(uint32_t) VDGetSectorSize(PVDISK pDisk, unsigned nImage) +{ + uint64_t cbSector; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p nImage=%u\n", pDisk, nImage)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, cbSector = 0); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, cbSector = 0); + + PCVDREGIONLIST pRegionList = NULL; + int rc = pImage->Backend->pfnQueryRegions(pImage->pBackendData, &pRegionList); + if (RT_SUCCESS(rc)) + { + AssertBreakStmt(pRegionList->cRegions == 1, cbSector = 0); + cbSector = pRegionList->aRegions[0].cbBlock; + + AssertPtr(pImage->Backend->pfnRegionListRelease); + pImage->Backend->pfnRegionListRelease(pImage->pBackendData, pRegionList); + } + else + cbSector = 0; + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %u\n", cbSector)); + return cbSector; +} + +/** + * Get total capacity of an image in HDD container. + * + * @returns Virtual disk size in bytes. + * @returns 0 if no image with specified number was not opened. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + */ +VBOXDDU_DECL(uint64_t) VDGetSize(PVDISK pDisk, unsigned nImage) +{ + uint64_t cbSize; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p nImage=%u\n", pDisk, nImage)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, cbSize = 0); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, cbSize = 0); + + cbSize = vdImageGetSize(pImage); + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %llu\n", cbSize)); + return cbSize; +} + +/** + * Get total file size of an image in HDD container. + * + * @returns Virtual disk size in bytes. + * @returns 0 if no image is opened in HDD container. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + */ +VBOXDDU_DECL(uint64_t) VDGetFileSize(PVDISK pDisk, unsigned nImage) +{ + uint64_t cbSize; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p nImage=%u\n", pDisk, nImage)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, cbSize = 0); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, cbSize = 0); + cbSize = pImage->Backend->pfnGetFileSize(pImage->pBackendData); + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %llu\n", cbSize)); + return cbSize; +} + +/** + * Get virtual disk PCHS geometry stored in HDD container. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @returns VERR_VD_GEOMETRY_NOT_SET if no geometry present in the HDD container. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pPCHSGeometry Where to store PCHS geometry. Not NULL. + */ +VBOXDDU_DECL(int) VDGetPCHSGeometry(PVDISK pDisk, unsigned nImage, + PVDGEOMETRY pPCHSGeometry) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p nImage=%u pPCHSGeometry=%#p\n", + pDisk, nImage, pPCHSGeometry)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pPCHSGeometry), + ("pPCHSGeometry=%#p\n", pPCHSGeometry), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + if (pImage == pDisk->pLast) + { + /* Use cached information if possible. */ + if (pDisk->PCHSGeometry.cCylinders != 0) + *pPCHSGeometry = pDisk->PCHSGeometry; + else + rc = VERR_VD_GEOMETRY_NOT_SET; + } + else + rc = pImage->Backend->pfnGetPCHSGeometry(pImage->pBackendData, + pPCHSGeometry); + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("%Rrc (PCHS=%u/%u/%u)\n", rc, + pDisk->PCHSGeometry.cCylinders, pDisk->PCHSGeometry.cHeads, + pDisk->PCHSGeometry.cSectors)); + return rc; +} + +/** + * Store virtual disk PCHS geometry in HDD container. + * + * Note that in case of unrecoverable error all images in HDD container will be closed. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @returns VERR_VD_GEOMETRY_NOT_SET if no geometry present in the HDD container. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pPCHSGeometry Where to load PCHS geometry from. Not NULL. + */ +VBOXDDU_DECL(int) VDSetPCHSGeometry(PVDISK pDisk, unsigned nImage, + PCVDGEOMETRY pPCHSGeometry) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false; + + LogFlowFunc(("pDisk=%#p nImage=%u pPCHSGeometry=%#p PCHS=%u/%u/%u\n", + pDisk, nImage, pPCHSGeometry, pPCHSGeometry->cCylinders, + pPCHSGeometry->cHeads, pPCHSGeometry->cSectors)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt( VALID_PTR(pPCHSGeometry) + && pPCHSGeometry->cHeads <= 16 + && pPCHSGeometry->cSectors <= 63, + ("pPCHSGeometry=%#p PCHS=%u/%u/%u\n", pPCHSGeometry, + pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, + pPCHSGeometry->cSectors), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + if (pImage == pDisk->pLast) + { + if ( pPCHSGeometry->cCylinders != pDisk->PCHSGeometry.cCylinders + || pPCHSGeometry->cHeads != pDisk->PCHSGeometry.cHeads + || pPCHSGeometry->cSectors != pDisk->PCHSGeometry.cSectors) + { + /* Only update geometry if it is changed. Avoids similar checks + * in every backend. Most of the time the new geometry is set + * to the previous values, so no need to go through the hassle + * of updating an image which could be opened in read-only mode + * right now. */ + rc = pImage->Backend->pfnSetPCHSGeometry(pImage->pBackendData, + pPCHSGeometry); + + /* Cache new geometry values in any case. */ + rc2 = pImage->Backend->pfnGetPCHSGeometry(pImage->pBackendData, + &pDisk->PCHSGeometry); + if (RT_FAILURE(rc2)) + { + pDisk->PCHSGeometry.cCylinders = 0; + pDisk->PCHSGeometry.cHeads = 0; + pDisk->PCHSGeometry.cSectors = 0; + } + else + { + /* Make sure the CHS geometry is properly clipped. */ + pDisk->PCHSGeometry.cHeads = RT_MIN(pDisk->PCHSGeometry.cHeads, 255); + pDisk->PCHSGeometry.cSectors = RT_MIN(pDisk->PCHSGeometry.cSectors, 63); + } + } + } + else + { + VDGEOMETRY PCHS; + rc = pImage->Backend->pfnGetPCHSGeometry(pImage->pBackendData, + &PCHS); + if ( RT_FAILURE(rc) + || pPCHSGeometry->cCylinders != PCHS.cCylinders + || pPCHSGeometry->cHeads != PCHS.cHeads + || pPCHSGeometry->cSectors != PCHS.cSectors) + { + /* Only update geometry if it is changed. Avoids similar checks + * in every backend. Most of the time the new geometry is set + * to the previous values, so no need to go through the hassle + * of updating an image which could be opened in read-only mode + * right now. */ + rc = pImage->Backend->pfnSetPCHSGeometry(pImage->pBackendData, + pPCHSGeometry); + } + } + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Get virtual disk LCHS geometry stored in HDD container. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @returns VERR_VD_GEOMETRY_NOT_SET if no geometry present in the HDD container. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pLCHSGeometry Where to store LCHS geometry. Not NULL. + */ +VBOXDDU_DECL(int) VDGetLCHSGeometry(PVDISK pDisk, unsigned nImage, + PVDGEOMETRY pLCHSGeometry) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p nImage=%u pLCHSGeometry=%#p\n", + pDisk, nImage, pLCHSGeometry)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pLCHSGeometry), + ("pLCHSGeometry=%#p\n", pLCHSGeometry), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + if (pImage == pDisk->pLast) + { + /* Use cached information if possible. */ + if (pDisk->LCHSGeometry.cCylinders != 0) + *pLCHSGeometry = pDisk->LCHSGeometry; + else + rc = VERR_VD_GEOMETRY_NOT_SET; + } + else + rc = pImage->Backend->pfnGetLCHSGeometry(pImage->pBackendData, + pLCHSGeometry); + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc((": %Rrc (LCHS=%u/%u/%u)\n", rc, + pDisk->LCHSGeometry.cCylinders, pDisk->LCHSGeometry.cHeads, + pDisk->LCHSGeometry.cSectors)); + return rc; +} + +/** + * Store virtual disk LCHS geometry in HDD container. + * + * Note that in case of unrecoverable error all images in HDD container will be closed. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @returns VERR_VD_GEOMETRY_NOT_SET if no geometry present in the HDD container. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pLCHSGeometry Where to load LCHS geometry from. Not NULL. + */ +VBOXDDU_DECL(int) VDSetLCHSGeometry(PVDISK pDisk, unsigned nImage, + PCVDGEOMETRY pLCHSGeometry) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockWrite = false; + + LogFlowFunc(("pDisk=%#p nImage=%u pLCHSGeometry=%#p LCHS=%u/%u/%u\n", + pDisk, nImage, pLCHSGeometry, pLCHSGeometry->cCylinders, + pLCHSGeometry->cHeads, pLCHSGeometry->cSectors)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt( VALID_PTR(pLCHSGeometry) + && pLCHSGeometry->cHeads <= 255 + && pLCHSGeometry->cSectors <= 63, + ("pLCHSGeometry=%#p LCHS=%u/%u/%u\n", pLCHSGeometry, + pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, + pLCHSGeometry->cSectors), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + if (pImage == pDisk->pLast) + { + if ( pLCHSGeometry->cCylinders != pDisk->LCHSGeometry.cCylinders + || pLCHSGeometry->cHeads != pDisk->LCHSGeometry.cHeads + || pLCHSGeometry->cSectors != pDisk->LCHSGeometry.cSectors) + { + /* Only update geometry if it is changed. Avoids similar checks + * in every backend. Most of the time the new geometry is set + * to the previous values, so no need to go through the hassle + * of updating an image which could be opened in read-only mode + * right now. */ + rc = pImage->Backend->pfnSetLCHSGeometry(pImage->pBackendData, + pLCHSGeometry); + + /* Cache new geometry values in any case. */ + rc2 = pImage->Backend->pfnGetLCHSGeometry(pImage->pBackendData, + &pDisk->LCHSGeometry); + if (RT_FAILURE(rc2)) + { + pDisk->LCHSGeometry.cCylinders = 0; + pDisk->LCHSGeometry.cHeads = 0; + pDisk->LCHSGeometry.cSectors = 0; + } + else + { + /* Make sure the CHS geometry is properly clipped. */ + pDisk->LCHSGeometry.cHeads = RT_MIN(pDisk->LCHSGeometry.cHeads, 255); + pDisk->LCHSGeometry.cSectors = RT_MIN(pDisk->LCHSGeometry.cSectors, 63); + } + } + } + else + { + VDGEOMETRY LCHS; + rc = pImage->Backend->pfnGetLCHSGeometry(pImage->pBackendData, + &LCHS); + if ( RT_FAILURE(rc) + || pLCHSGeometry->cCylinders != LCHS.cCylinders + || pLCHSGeometry->cHeads != LCHS.cHeads + || pLCHSGeometry->cSectors != LCHS.cSectors) + { + /* Only update geometry if it is changed. Avoids similar checks + * in every backend. Most of the time the new geometry is set + * to the previous values, so no need to go through the hassle + * of updating an image which could be opened in read-only mode + * right now. */ + rc = pImage->Backend->pfnSetLCHSGeometry(pImage->pBackendData, + pLCHSGeometry); + } + } + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Queries the available regions of an image in the given VD container. + * + * @return VBox status code. + * @retval VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @retval VERR_NOT_SUPPORTED if the image backend doesn't support region lists. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param fFlags Combination of VD_REGION_LIST_F_* flags. + * @param ppRegionList Where to store the pointer to the region list on success, must be freed + * with VDRegionListFree(). + */ +VBOXDDU_DECL(int) VDQueryRegions(PVDISK pDisk, unsigned nImage, uint32_t fFlags, + PPVDREGIONLIST ppRegionList) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p nImage=%u fFlags=%#x ppRegionList=%#p\n", + pDisk, nImage, fFlags, ppRegionList)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(ppRegionList), + ("ppRegionList=%#p\n", ppRegionList), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + PCVDREGIONLIST pRegionList = NULL; + rc = pImage->Backend->pfnQueryRegions(pImage->pBackendData, &pRegionList); + if (RT_SUCCESS(rc)) + { + rc = vdRegionListConv(pRegionList, fFlags, ppRegionList); + + AssertPtr(pImage->Backend->pfnRegionListRelease); + pImage->Backend->pfnRegionListRelease(pImage->pBackendData, pRegionList); + } + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc((": %Rrc\n", rc)); + return rc; +} + +/** + * Frees a region list previously queried with VDQueryRegions(). + * + * @return nothing. + * @param pRegionList The region list to free. + */ +VBOXDDU_DECL(void) VDRegionListFree(PVDREGIONLIST pRegionList) +{ + RTMemFree(pRegionList); +} + +/** + * Get version of image in HDD container. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param puVersion Where to store the image version. + */ +VBOXDDU_DECL(int) VDGetVersion(PVDISK pDisk, unsigned nImage, + unsigned *puVersion) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p nImage=%u puVersion=%#p\n", + pDisk, nImage, puVersion)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(puVersion), + ("puVersion=%#p\n", puVersion), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + *puVersion = pImage->Backend->pfnGetVersion(pImage->pBackendData); + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc uVersion=%#x\n", rc, *puVersion)); + return rc; +} + +/** + * List the capabilities of image backend in HDD container. + * + * @returns VBox status code. + * @retval VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @param pDisk Pointer to the HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pBackendInfo Where to store the backend information. + */ +VBOXDDU_DECL(int) VDBackendInfoSingle(PVDISK pDisk, unsigned nImage, + PVDBACKENDINFO pBackendInfo) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p nImage=%u pBackendInfo=%#p\n", + pDisk, nImage, pBackendInfo)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pBackendInfo), + ("pBackendInfo=%#p\n", pBackendInfo), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + pBackendInfo->pszBackend = pImage->Backend->pszBackendName; + pBackendInfo->uBackendCaps = pImage->Backend->uBackendCaps; + pBackendInfo->paFileExtensions = pImage->Backend->paFileExtensions; + pBackendInfo->paConfigInfo = pImage->Backend->paConfigInfo; + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Get flags of image in HDD container. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param puImageFlags Where to store the image flags. + */ +VBOXDDU_DECL(int) VDGetImageFlags(PVDISK pDisk, unsigned nImage, + unsigned *puImageFlags) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p nImage=%u puImageFlags=%#p\n", + pDisk, nImage, puImageFlags)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(puImageFlags), + ("puImageFlags=%#p\n", puImageFlags), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + *puImageFlags = pImage->uImageFlags; + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc uImageFlags=%#x\n", rc, *puImageFlags)); + return rc; +} + +/** + * Get open flags of image in HDD container. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param puOpenFlags Where to store the image open flags. + */ +VBOXDDU_DECL(int) VDGetOpenFlags(PVDISK pDisk, unsigned nImage, + unsigned *puOpenFlags) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p nImage=%u puOpenFlags=%#p\n", + pDisk, nImage, puOpenFlags)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(puOpenFlags), + ("puOpenFlags=%#p\n", puOpenFlags), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + *puOpenFlags = pImage->Backend->pfnGetOpenFlags(pImage->pBackendData); + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc uOpenFlags=%#x\n", rc, *puOpenFlags)); + return rc; +} + +/** + * Set open flags of image in HDD container. + * This operation may cause file locking changes and/or files being reopened. + * Note that in case of unrecoverable error all images in HDD container will be closed. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param uOpenFlags Image file open mode, see VD_OPEN_FLAGS_* constants. + */ +VBOXDDU_DECL(int) VDSetOpenFlags(PVDISK pDisk, unsigned nImage, + unsigned uOpenFlags) +{ + int rc; + int rc2; + bool fLockWrite = false; + + LogFlowFunc(("pDisk=%#p uOpenFlags=%#u\n", pDisk, uOpenFlags)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt((uOpenFlags & ~VD_OPEN_FLAGS_MASK) == 0, + ("uOpenFlags=%#x\n", uOpenFlags), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + /* Destroy any discard state because the image might be changed to readonly mode. */ + rc = vdDiscardStateDestroy(pDisk); + if (RT_FAILURE(rc)) + break; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + rc = pImage->Backend->pfnSetOpenFlags(pImage->pBackendData, + uOpenFlags & ~(VD_OPEN_FLAGS_HONOR_SAME | VD_OPEN_FLAGS_IGNORE_FLUSH | VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS)); + if (RT_SUCCESS(rc)) + pImage->uOpenFlags = uOpenFlags & (VD_OPEN_FLAGS_HONOR_SAME | VD_OPEN_FLAGS_DISCARD | VD_OPEN_FLAGS_IGNORE_FLUSH | VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS); + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Get base filename of image in HDD container. Some image formats use + * other filenames as well, so don't use this for anything but informational + * purposes. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @returns VERR_BUFFER_OVERFLOW if pszFilename buffer too small to hold filename. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pszFilename Where to store the image file name. + * @param cbFilename Size of buffer pszFilename points to. + */ +VBOXDDU_DECL(int) VDGetFilename(PVDISK pDisk, unsigned nImage, + char *pszFilename, unsigned cbFilename) +{ + int rc; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p nImage=%u pszFilename=%#p cbFilename=%u\n", + pDisk, nImage, pszFilename, cbFilename)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pszFilename) && *pszFilename, + ("pszFilename=%#p \"%s\"\n", pszFilename, pszFilename), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt(cbFilename, + ("cbFilename=%u\n", cbFilename), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + size_t cb = strlen(pImage->pszFilename); + if (cb <= cbFilename) + { + strcpy(pszFilename, pImage->pszFilename); + rc = VINF_SUCCESS; + } + else + { + strncpy(pszFilename, pImage->pszFilename, cbFilename - 1); + pszFilename[cbFilename - 1] = '\0'; + rc = VERR_BUFFER_OVERFLOW; + } + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc, pszFilename=\"%s\"\n", rc, pszFilename)); + return rc; +} + +/** + * Get the comment line of image in HDD container. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @returns VERR_BUFFER_OVERFLOW if pszComment buffer too small to hold comment text. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pszComment Where to store the comment string of image. NULL is ok. + * @param cbComment The size of pszComment buffer. 0 is ok. + */ +VBOXDDU_DECL(int) VDGetComment(PVDISK pDisk, unsigned nImage, + char *pszComment, unsigned cbComment) +{ + int rc; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p nImage=%u pszComment=%#p cbComment=%u\n", + pDisk, nImage, pszComment, cbComment)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pszComment), + ("pszComment=%#p \"%s\"\n", pszComment, pszComment), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt(cbComment, + ("cbComment=%u\n", cbComment), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + rc = pImage->Backend->pfnGetComment(pImage->pBackendData, pszComment, + cbComment); + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc, pszComment=\"%s\"\n", rc, pszComment)); + return rc; +} + +/** + * Changes the comment line of image in HDD container. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pszComment New comment string (UTF-8). NULL is allowed to reset the comment. + */ +VBOXDDU_DECL(int) VDSetComment(PVDISK pDisk, unsigned nImage, + const char *pszComment) +{ + int rc; + int rc2; + bool fLockWrite = false; + + LogFlowFunc(("pDisk=%#p nImage=%u pszComment=%#p \"%s\"\n", + pDisk, nImage, pszComment, pszComment)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pszComment) || pszComment == NULL, + ("pszComment=%#p \"%s\"\n", pszComment, pszComment), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + rc = pImage->Backend->pfnSetComment(pImage->pBackendData, pszComment); + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * Get UUID of image in HDD container. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pUuid Where to store the image creation UUID. + */ +VBOXDDU_DECL(int) VDGetUuid(PVDISK pDisk, unsigned nImage, PRTUUID pUuid) +{ + int rc; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p\n", pDisk, nImage, pUuid)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pUuid), + ("pUuid=%#p\n", pUuid), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + rc = pImage->Backend->pfnGetUuid(pImage->pBackendData, pUuid); + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc, Uuid={%RTuuid}\n", rc, pUuid)); + return rc; +} + +/** + * Set the image's UUID. Should not be used by normal applications. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pUuid New UUID of the image. If NULL, a new UUID is created. + */ +VBOXDDU_DECL(int) VDSetUuid(PVDISK pDisk, unsigned nImage, PCRTUUID pUuid) +{ + int rc; + int rc2; + bool fLockWrite = false; + + LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p {%RTuuid}\n", + pDisk, nImage, pUuid, pUuid)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + AssertMsgBreakStmt(VALID_PTR(pUuid) || pUuid == NULL, + ("pUuid=%#p\n", pUuid), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + RTUUID Uuid; + if (!pUuid) + { + RTUuidCreate(&Uuid); + pUuid = &Uuid; + } + rc = pImage->Backend->pfnSetUuid(pImage->pBackendData, pUuid); + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Get last modification UUID of image in HDD container. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pUuid Where to store the image modification UUID. + */ +VBOXDDU_DECL(int) VDGetModificationUuid(PVDISK pDisk, unsigned nImage, PRTUUID pUuid) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p\n", pDisk, nImage, pUuid)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pUuid), + ("pUuid=%#p\n", pUuid), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + rc = pImage->Backend->pfnGetModificationUuid(pImage->pBackendData, + pUuid); + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc, Uuid={%RTuuid}\n", rc, pUuid)); + return rc; +} + +/** + * Set the image's last modification UUID. Should not be used by normal applications. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pUuid New modification UUID of the image. If NULL, a new UUID is created. + */ +VBOXDDU_DECL(int) VDSetModificationUuid(PVDISK pDisk, unsigned nImage, PCRTUUID pUuid) +{ + int rc; + int rc2; + bool fLockWrite = false; + + LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p {%RTuuid}\n", + pDisk, nImage, pUuid, pUuid)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pUuid) || pUuid == NULL, + ("pUuid=%#p\n", pUuid), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + RTUUID Uuid; + if (!pUuid) + { + RTUuidCreate(&Uuid); + pUuid = &Uuid; + } + rc = pImage->Backend->pfnSetModificationUuid(pImage->pBackendData, + pUuid); + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Get parent UUID of image in HDD container. + * + * @returns VBox status code. + * @returns VERR_VD_IMAGE_NOT_FOUND if image with specified number was not opened. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pUuid Where to store the parent image UUID. + */ +VBOXDDU_DECL(int) VDGetParentUuid(PVDISK pDisk, unsigned nImage, + PRTUUID pUuid) +{ + int rc = VINF_SUCCESS; + int rc2; + bool fLockRead = false; + + LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p\n", pDisk, nImage, pUuid)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pUuid), + ("pUuid=%#p\n", pUuid), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + rc = pImage->Backend->pfnGetParentUuid(pImage->pBackendData, pUuid); + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc, Uuid={%RTuuid}\n", rc, pUuid)); + return rc; +} + +/** + * Set the image's parent UUID. Should not be used by normal applications. + * + * @returns VBox status code. + * @param pDisk Pointer to HDD container. + * @param nImage Image number, counts from 0. 0 is always base image of container. + * @param pUuid New parent UUID of the image. If NULL, a new UUID is created. + */ +VBOXDDU_DECL(int) VDSetParentUuid(PVDISK pDisk, unsigned nImage, + PCRTUUID pUuid) +{ + int rc; + int rc2; + bool fLockWrite = false; + + LogFlowFunc(("pDisk=%#p nImage=%u pUuid=%#p {%RTuuid}\n", + pDisk, nImage, pUuid, pUuid)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(VALID_PTR(pUuid) || pUuid == NULL, + ("pUuid=%#p\n", pUuid), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + PVDIMAGE pImage = vdGetImageByNumber(pDisk, nImage); + AssertPtrBreakStmt(pImage, rc = VERR_VD_IMAGE_NOT_FOUND); + + RTUUID Uuid; + if (!pUuid) + { + RTUuidCreate(&Uuid); + pUuid = &Uuid; + } + rc = pImage->Backend->pfnSetParentUuid(pImage->pBackendData, pUuid); + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * Debug helper - dumps all opened images in HDD container into the log file. + * + * @param pDisk Pointer to HDD container. + */ +VBOXDDU_DECL(void) VDDumpImages(PVDISK pDisk) +{ + int rc2; + bool fLockRead = false; + + do + { + /* sanity check */ + AssertPtrBreak(pDisk); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + if (!pDisk->pInterfaceError || !VALID_PTR(pDisk->pInterfaceError->pfnMessage)) + pDisk->pInterfaceError->pfnMessage = vdLogMessage; + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + vdMessageWrapper(pDisk, "--- Dumping VD Disk, Images=%u\n", pDisk->cImages); + for (PVDIMAGE pImage = pDisk->pBase; pImage; pImage = pImage->pNext) + { + vdMessageWrapper(pDisk, "Dumping VD image \"%s\" (Backend=%s)\n", + pImage->pszFilename, pImage->Backend->pszBackendName); + pImage->Backend->pfnDump(pImage->pBackendData); + } + } while (0); + + if (RT_UNLIKELY(fLockRead)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } +} + + +VBOXDDU_DECL(int) VDDiscardRanges(PVDISK pDisk, PCRTRANGE paRanges, unsigned cRanges) +{ + int rc; + int rc2; + bool fLockWrite = false; + + LogFlowFunc(("pDisk=%#p paRanges=%#p cRanges=%u\n", + pDisk, paRanges, cRanges)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(cRanges, + ("cRanges=%u\n", cRanges), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt(VALID_PTR(paRanges), + ("paRanges=%#p\n", paRanges), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + AssertPtrBreakStmt(pDisk->pLast, rc = VERR_VD_NOT_OPENED); + + AssertMsgBreakStmt(pDisk->pLast->uOpenFlags & VD_OPEN_FLAGS_DISCARD, + ("Discarding not supported\n"), + rc = VERR_NOT_SUPPORTED); + + VDIOCTX IoCtx; + RTSEMEVENT hEventComplete = NIL_RTSEMEVENT; + + rc = RTSemEventCreate(&hEventComplete); + if (RT_FAILURE(rc)) + break; + + vdIoCtxDiscardInit(&IoCtx, pDisk, paRanges, cRanges, + vdIoCtxSyncComplete, pDisk, hEventComplete, NULL, + vdDiscardHelperAsync, VDIOCTX_FLAGS_SYNC | VDIOCTX_FLAGS_DONT_FREE); + rc = vdIoCtxProcessSync(&IoCtx, hEventComplete); + + RTSemEventDestroy(hEventComplete); + } while (0); + + if (RT_UNLIKELY(fLockWrite)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +VBOXDDU_DECL(int) VDAsyncRead(PVDISK pDisk, uint64_t uOffset, size_t cbRead, + PCRTSGBUF pcSgBuf, + PFNVDASYNCTRANSFERCOMPLETE pfnComplete, + void *pvUser1, void *pvUser2) +{ + int rc = VERR_VD_BLOCK_FREE; + int rc2; + bool fLockRead = false; + PVDIOCTX pIoCtx = NULL; + + LogFlowFunc(("pDisk=%#p uOffset=%llu pcSgBuf=%#p cbRead=%zu pvUser1=%#p pvUser2=%#p\n", + pDisk, uOffset, pcSgBuf, cbRead, pvUser1, pvUser2)); + + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(cbRead, + ("cbRead=%zu\n", cbRead), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt(VALID_PTR(pcSgBuf), + ("pcSgBuf=%#p\n", pcSgBuf), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartRead(pDisk); + AssertRC(rc2); + fLockRead = true; + + AssertMsgBreakStmt( uOffset < pDisk->cbSize + && cbRead <= pDisk->cbSize - uOffset, + ("uOffset=%llu cbRead=%zu pDisk->cbSize=%llu\n", + uOffset, cbRead, pDisk->cbSize), + rc = VERR_INVALID_PARAMETER); + AssertPtrBreakStmt(pDisk->pLast, rc = VERR_VD_NOT_OPENED); + + pIoCtx = vdIoCtxRootAlloc(pDisk, VDIOCTXTXDIR_READ, uOffset, + cbRead, pDisk->pLast, pcSgBuf, + pfnComplete, pvUser1, pvUser2, + NULL, vdReadHelperAsync, + VDIOCTX_FLAGS_ZERO_FREE_BLOCKS); + if (!pIoCtx) + { + rc = VERR_NO_MEMORY; + break; + } + + rc = vdIoCtxProcessTryLockDefer(pIoCtx); + if (rc == VINF_VD_ASYNC_IO_FINISHED) + { + if (ASMAtomicCmpXchgBool(&pIoCtx->fComplete, true, false)) + vdIoCtxFree(pDisk, pIoCtx); + else + rc = VERR_VD_ASYNC_IO_IN_PROGRESS; /* Let the other handler complete the request. */ + } + else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS) /* Another error */ + vdIoCtxFree(pDisk, pIoCtx); + + } while (0); + + if (RT_UNLIKELY(fLockRead) && (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)) + { + rc2 = vdThreadFinishRead(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +VBOXDDU_DECL(int) VDAsyncWrite(PVDISK pDisk, uint64_t uOffset, size_t cbWrite, + PCRTSGBUF pcSgBuf, + PFNVDASYNCTRANSFERCOMPLETE pfnComplete, + void *pvUser1, void *pvUser2) +{ + int rc; + int rc2; + bool fLockWrite = false; + PVDIOCTX pIoCtx = NULL; + + LogFlowFunc(("pDisk=%#p uOffset=%llu cSgBuf=%#p cbWrite=%zu pvUser1=%#p pvUser2=%#p\n", + pDisk, uOffset, pcSgBuf, cbWrite, pvUser1, pvUser2)); + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + /* Check arguments. */ + AssertMsgBreakStmt(cbWrite, + ("cbWrite=%zu\n", cbWrite), + rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt(VALID_PTR(pcSgBuf), + ("pcSgBuf=%#p\n", pcSgBuf), + rc = VERR_INVALID_PARAMETER); + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + AssertMsgBreakStmt( uOffset < pDisk->cbSize + && cbWrite <= pDisk->cbSize - uOffset, + ("uOffset=%llu cbWrite=%zu pDisk->cbSize=%llu\n", + uOffset, cbWrite, pDisk->cbSize), + rc = VERR_INVALID_PARAMETER); + AssertPtrBreakStmt(pDisk->pLast, rc = VERR_VD_NOT_OPENED); + + pIoCtx = vdIoCtxRootAlloc(pDisk, VDIOCTXTXDIR_WRITE, uOffset, + cbWrite, pDisk->pLast, pcSgBuf, + pfnComplete, pvUser1, pvUser2, + NULL, vdWriteHelperAsync, + VDIOCTX_FLAGS_DEFAULT); + if (!pIoCtx) + { + rc = VERR_NO_MEMORY; + break; + } + + rc = vdIoCtxProcessTryLockDefer(pIoCtx); + if (rc == VINF_VD_ASYNC_IO_FINISHED) + { + if (ASMAtomicCmpXchgBool(&pIoCtx->fComplete, true, false)) + vdIoCtxFree(pDisk, pIoCtx); + else + rc = VERR_VD_ASYNC_IO_IN_PROGRESS; /* Let the other handler complete the request. */ + } + else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS) /* Another error */ + vdIoCtxFree(pDisk, pIoCtx); + } while (0); + + if (RT_UNLIKELY(fLockWrite) && (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +VBOXDDU_DECL(int) VDAsyncFlush(PVDISK pDisk, PFNVDASYNCTRANSFERCOMPLETE pfnComplete, + void *pvUser1, void *pvUser2) +{ + int rc; + int rc2; + bool fLockWrite = false; + PVDIOCTX pIoCtx = NULL; + + LogFlowFunc(("pDisk=%#p\n", pDisk)); + + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + AssertPtrBreakStmt(pDisk->pLast, rc = VERR_VD_NOT_OPENED); + + pIoCtx = vdIoCtxRootAlloc(pDisk, VDIOCTXTXDIR_FLUSH, 0, + 0, pDisk->pLast, NULL, + pfnComplete, pvUser1, pvUser2, + NULL, vdFlushHelperAsync, + VDIOCTX_FLAGS_DEFAULT); + if (!pIoCtx) + { + rc = VERR_NO_MEMORY; + break; + } + + rc = vdIoCtxProcessTryLockDefer(pIoCtx); + if (rc == VINF_VD_ASYNC_IO_FINISHED) + { + if (ASMAtomicCmpXchgBool(&pIoCtx->fComplete, true, false)) + vdIoCtxFree(pDisk, pIoCtx); + else + rc = VERR_VD_ASYNC_IO_IN_PROGRESS; /* Let the other handler complete the request. */ + } + else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS) /* Another error */ + vdIoCtxFree(pDisk, pIoCtx); + } while (0); + + if (RT_UNLIKELY(fLockWrite) && (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +VBOXDDU_DECL(int) VDAsyncDiscardRanges(PVDISK pDisk, PCRTRANGE paRanges, unsigned cRanges, + PFNVDASYNCTRANSFERCOMPLETE pfnComplete, + void *pvUser1, void *pvUser2) +{ + int rc; + int rc2; + bool fLockWrite = false; + PVDIOCTX pIoCtx = NULL; + + LogFlowFunc(("pDisk=%#p\n", pDisk)); + + do + { + /* sanity check */ + AssertPtrBreakStmt(pDisk, rc = VERR_INVALID_PARAMETER); + AssertMsg(pDisk->u32Signature == VDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature)); + + rc2 = vdThreadStartWrite(pDisk); + AssertRC(rc2); + fLockWrite = true; + + AssertPtrBreakStmt(pDisk->pLast, rc = VERR_VD_NOT_OPENED); + + pIoCtx = vdIoCtxDiscardAlloc(pDisk, paRanges, cRanges, + pfnComplete, pvUser1, pvUser2, NULL, + vdDiscardHelperAsync, + VDIOCTX_FLAGS_DEFAULT); + if (!pIoCtx) + { + rc = VERR_NO_MEMORY; + break; + } + + rc = vdIoCtxProcessTryLockDefer(pIoCtx); + if (rc == VINF_VD_ASYNC_IO_FINISHED) + { + if (ASMAtomicCmpXchgBool(&pIoCtx->fComplete, true, false)) + vdIoCtxFree(pDisk, pIoCtx); + else + rc = VERR_VD_ASYNC_IO_IN_PROGRESS; /* Let the other handler complete the request. */ + } + else if (rc != VERR_VD_ASYNC_IO_IN_PROGRESS) /* Another error */ + vdIoCtxFree(pDisk, pIoCtx); + } while (0); + + if (RT_UNLIKELY(fLockWrite) && (rc != VERR_VD_ASYNC_IO_IN_PROGRESS)) + { + rc2 = vdThreadFinishWrite(pDisk); + AssertRC(rc2); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +VBOXDDU_DECL(int) VDRepair(PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage, + const char *pszFilename, const char *pszBackend, + uint32_t fFlags) +{ + int rc = VERR_NOT_SUPPORTED; + PCVDIMAGEBACKEND pBackend = NULL; + VDINTERFACEIOINT VDIfIoInt; + VDINTERFACEIO VDIfIoFallback; + PVDINTERFACEIO pInterfaceIo; + + LogFlowFunc(("pszFilename=\"%s\"\n", pszFilename)); + /* Check arguments. */ + AssertMsgReturn(VALID_PTR(pszFilename) && *pszFilename, + ("pszFilename=%#p \"%s\"\n", pszFilename, pszFilename), + VERR_INVALID_PARAMETER); + AssertMsgReturn(VALID_PTR(pszBackend), + ("pszBackend=%#p\n", pszBackend), + VERR_INVALID_PARAMETER); + AssertMsgReturn((fFlags & ~VD_REPAIR_FLAGS_MASK) == 0, + ("fFlags=%#x\n", fFlags), + VERR_INVALID_PARAMETER); + + pInterfaceIo = VDIfIoGet(pVDIfsImage); + if (!pInterfaceIo) + { + /* + * Caller doesn't provide an I/O interface, create our own using the + * native file API. + */ + vdIfIoFallbackCallbacksSetup(&VDIfIoFallback); + pInterfaceIo = &VDIfIoFallback; + } + + /* Set up the internal I/O interface. */ + AssertReturn(!VDIfIoIntGet(pVDIfsImage), VERR_INVALID_PARAMETER); + VDIfIoInt.pfnOpen = vdIOIntOpenLimited; + VDIfIoInt.pfnClose = vdIOIntCloseLimited; + VDIfIoInt.pfnDelete = vdIOIntDeleteLimited; + VDIfIoInt.pfnMove = vdIOIntMoveLimited; + VDIfIoInt.pfnGetFreeSpace = vdIOIntGetFreeSpaceLimited; + VDIfIoInt.pfnGetModificationTime = vdIOIntGetModificationTimeLimited; + VDIfIoInt.pfnGetSize = vdIOIntGetSizeLimited; + VDIfIoInt.pfnSetSize = vdIOIntSetSizeLimited; + VDIfIoInt.pfnReadUser = vdIOIntReadUserLimited; + VDIfIoInt.pfnWriteUser = vdIOIntWriteUserLimited; + VDIfIoInt.pfnReadMeta = vdIOIntReadMetaLimited; + VDIfIoInt.pfnWriteMeta = vdIOIntWriteMetaLimited; + VDIfIoInt.pfnFlush = vdIOIntFlushLimited; + rc = VDInterfaceAdd(&VDIfIoInt.Core, "VD_IOINT", VDINTERFACETYPE_IOINT, + pInterfaceIo, sizeof(VDINTERFACEIOINT), &pVDIfsImage); + AssertRC(rc); + + rc = vdFindImageBackend(pszBackend, &pBackend); + if (RT_SUCCESS(rc)) + { + if (pBackend->pfnRepair) + rc = pBackend->pfnRepair(pszFilename, pVDIfsDisk, pVDIfsImage, fFlags); + else + rc = VERR_VD_IMAGE_REPAIR_NOT_SUPPORTED; + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/* + * generic plugin functions + */ + +/** + * @interface_method_impl{VDIMAGEBACKEND,pfnComposeLocation} + */ +DECLCALLBACK(int) genericFileComposeLocation(PVDINTERFACE pConfig, char **pszLocation) +{ + RT_NOREF1(pConfig); + *pszLocation = NULL; + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{VDIMAGEBACKEND,pfnComposeName} + */ +DECLCALLBACK(int) genericFileComposeName(PVDINTERFACE pConfig, char **pszName) +{ + RT_NOREF1(pConfig); + *pszName = NULL; + return VINF_SUCCESS; +} + |