/* $Id: VD.cpp 72054 2018-04-27 09:18:51Z vboxsync $ */ /** @file * VD - Virtual disk container implementation. */ /* * Copyright (C) 2006-2017 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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(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; 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); } 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_OFFSETOF(VDMETAXFER, abData[cb])); if (RT_LIKELY(pMetaXfer)) { pMetaXfer->Core.Key = uOffset; pMetaXfer->Core.KeyLast = uOffset + cb - 1; pMetaXfer->fFlags = VDMETAXFER_TXDIR_NONE; pMetaXfer->cbMeta = cb; pMetaXfer->pIoStorage = pIoStorage; pMetaXfer->cRefs = 0; 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 RTFileGetSize(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); 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); 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); 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 ppszFormat Receives pointer of the UTF-8 string which contains the format name. * The returned pointer must be freed using RTStrFree(). */ VBOXDDU_DECL(int) VDGetFormat(PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage, const char *pszFilename, 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); 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); /* 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, 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->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->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->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) { rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData, uOffset, cbThisRead, &IoCtx, &cbThisRead); } 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) { rc = pCurrImage->Backend->pfnRead(pCurrImage->pBackendData, uOffset, cbThisRead, &IoCtx, &cbThisRead); } 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); } 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; 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 + cbWrite <= pDisk->cbSize, ("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 + cbRead <= pDisk->cbSize, ("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 + cbWrite <= pDisk->cbSize, ("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; }