diff options
Diffstat (limited to 'src/VBox/Storage/Parallels.cpp')
-rw-r--r-- | src/VBox/Storage/Parallels.cpp | 1204 |
1 files changed, 1204 insertions, 0 deletions
diff --git a/src/VBox/Storage/Parallels.cpp b/src/VBox/Storage/Parallels.cpp new file mode 100644 index 00000000..039b4db8 --- /dev/null +++ b/src/VBox/Storage/Parallels.cpp @@ -0,0 +1,1204 @@ +/* $Id: Parallels.cpp $ */ +/** @file + * + * Parallels hdd disk image, core code. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#define LOG_GROUP LOG_GROUP_VD_PARALLELS +#include <VBox/vd-plugin.h> +#include <VBox/err.h> + +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/uuid.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/asm.h> + +#include "VDBackends.h" + +#define PARALLELS_HEADER_MAGIC "WithoutFreeSpace" +#define PARALLELS_DISK_VERSION 2 + +/** The header of the parallels disk. */ +#pragma pack(1) +typedef struct ParallelsHeader +{ + /** The magic header to identify a parallels hdd image. */ + char HeaderIdentifier[16]; + /** The version of the disk image. */ + uint32_t uVersion; + /** The number of heads the hdd has. */ + uint32_t cHeads; + /** Number of cylinders. */ + uint32_t cCylinders; + /** Number of sectors per track. */ + uint32_t cSectorsPerTrack; + /** Number of entries in the allocation bitmap. */ + uint32_t cEntriesInAllocationBitmap; + /** Total number of sectors. */ + uint32_t cSectors; + /** Padding. */ + char Padding[24]; +} ParallelsHeader; +#pragma pack() + +/** + * Parallels image structure. + */ +typedef struct PARALLELSIMAGE +{ + /** Image file name. */ + const char *pszFilename; + /** Opaque storage handle. */ + PVDIOSTORAGE pStorage; + + /** Pointer to the per-disk VD interface list. */ + PVDINTERFACE pVDIfsDisk; + /** Pointer to the per-image VD interface list. */ + PVDINTERFACE pVDIfsImage; + /** Error interface. */ + PVDINTERFACEERROR pIfError; + /** I/O interface. */ + PVDINTERFACEIOINT pIfIo; + + /** Open flags passed by VBoxHDD layer. */ + unsigned uOpenFlags; + /** Image flags defined during creation or determined during open. */ + unsigned uImageFlags; + /** Total size of the image. */ + uint64_t cbSize; + + /** Physical geometry of this image. */ + VDGEOMETRY PCHSGeometry; + /** Logical geometry of this image. */ + VDGEOMETRY LCHSGeometry; + + /** Pointer to the allocation bitmap. */ + uint32_t *pAllocationBitmap; + /** Entries in the allocation bitmap. */ + uint64_t cAllocationBitmapEntries; + /** Flag whether the allocation bitmap was changed. */ + bool fAllocationBitmapChanged; + /** Current file size. */ + uint64_t cbFileCurrent; + /** The static region list. */ + VDREGIONLIST RegionList; +} PARALLELSIMAGE, *PPARALLELSIMAGE; + + +/********************************************************************************************************************************* +* Static Variables * +*********************************************************************************************************************************/ + +/** NULL-terminated array of supported file extensions. */ +static const VDFILEEXTENSION s_aParallelsFileExtensions[] = +{ + {"hdd", VDTYPE_HDD}, + {NULL, VDTYPE_INVALID} +}; + +/*************************************************** + * Internal functions * + **************************************************/ + +/** + * Internal. Flush image data to disk. + */ +static int parallelsFlushImage(PPARALLELSIMAGE pImage) +{ + int rc = VINF_SUCCESS; + + if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) + return VINF_SUCCESS; + + if ( !(pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED) + && (pImage->fAllocationBitmapChanged)) + { + pImage->fAllocationBitmapChanged = false; + /* Write the allocation bitmap to the file. */ + rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, + sizeof(ParallelsHeader), pImage->pAllocationBitmap, + pImage->cAllocationBitmapEntries * sizeof(uint32_t)); + if (RT_FAILURE(rc)) + return rc; + } + + /* Flush file. */ + rc = vdIfIoIntFileFlushSync(pImage->pIfIo, pImage->pStorage); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Internal. Free all allocated space for representing an image except pImage, + * and optionally delete the image from disk. + */ +static int parallelsFreeImage(PPARALLELSIMAGE pImage, bool fDelete) +{ + int rc = VINF_SUCCESS; + + /* Freeing a never allocated image (e.g. because the open failed) is + * not signalled as an error. After all nothing bad happens. */ + if (pImage) + { + if (pImage->pStorage) + { + /* No point updating the file that is deleted anyway. */ + if (!fDelete) + parallelsFlushImage(pImage); + + rc = vdIfIoIntFileClose(pImage->pIfIo, pImage->pStorage); + pImage->pStorage = NULL; + } + + if (pImage->pAllocationBitmap) + { + RTMemFree(pImage->pAllocationBitmap); + pImage->pAllocationBitmap = NULL; + } + + if (fDelete && pImage->pszFilename) + vdIfIoIntFileDelete(pImage->pIfIo, pImage->pszFilename); + } + + return rc; +} + +static int parallelsOpenImage(PPARALLELSIMAGE pImage, unsigned uOpenFlags) +{ + pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk); + pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage); + pImage->uOpenFlags = uOpenFlags; + AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER); + + int rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename, + VDOpenFlagsToFileOpenFlags(uOpenFlags, + false /* fCreate */), + &pImage->pStorage); + if (RT_SUCCESS(rc)) + { + rc = vdIfIoIntFileGetSize(pImage->pIfIo, pImage->pStorage, &pImage->cbFileCurrent); + if (RT_SUCCESS(rc) + && !(pImage->cbFileCurrent % 512)) + { + ParallelsHeader parallelsHeader; + + rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, 0, + ¶llelsHeader, sizeof(parallelsHeader)); + if (RT_SUCCESS(rc)) + { + if (memcmp(parallelsHeader.HeaderIdentifier, PARALLELS_HEADER_MAGIC, 16)) + { + /* Check if the file has hdd as extension. It is a fixed size raw image then. */ + char *pszSuffix = RTPathSuffix(pImage->pszFilename); + if (!strcmp(pszSuffix, ".hdd")) + { + /* This is a fixed size image. */ + pImage->uImageFlags |= VD_IMAGE_FLAGS_FIXED; + pImage->cbSize = pImage->cbFileCurrent; + + pImage->PCHSGeometry.cHeads = 16; + pImage->PCHSGeometry.cSectors = 63; + uint64_t cCylinders = pImage->cbSize / (512 * pImage->PCHSGeometry.cSectors * pImage->PCHSGeometry.cHeads); + pImage->PCHSGeometry.cCylinders = (uint32_t)cCylinders; + } + else + rc = VERR_VD_PARALLELS_INVALID_HEADER; + } + else + { + if ( parallelsHeader.uVersion == PARALLELS_DISK_VERSION + && parallelsHeader.cEntriesInAllocationBitmap <= (1 << 30)) + { + Log(("cSectors=%u\n", parallelsHeader.cSectors)); + pImage->cbSize = ((uint64_t)parallelsHeader.cSectors) * 512; + pImage->uImageFlags = VD_IMAGE_FLAGS_NONE; + pImage->PCHSGeometry.cCylinders = parallelsHeader.cCylinders; + pImage->PCHSGeometry.cHeads = parallelsHeader.cHeads; + pImage->PCHSGeometry.cSectors = parallelsHeader.cSectorsPerTrack; + pImage->cAllocationBitmapEntries = parallelsHeader.cEntriesInAllocationBitmap; + pImage->pAllocationBitmap = (uint32_t *)RTMemAllocZ((uint32_t)pImage->cAllocationBitmapEntries * sizeof(uint32_t)); + if (RT_LIKELY(pImage->pAllocationBitmap)) + rc = vdIfIoIntFileReadSync(pImage->pIfIo, pImage->pStorage, + sizeof(ParallelsHeader), pImage->pAllocationBitmap, + pImage->cAllocationBitmapEntries * sizeof(uint32_t)); + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_NOT_SUPPORTED; + } + } + } + else if (RT_SUCCESS(rc)) + rc = VERR_VD_PARALLELS_INVALID_HEADER; + } + + if (RT_SUCCESS(rc)) + { + PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0]; + pImage->RegionList.fFlags = 0; + pImage->RegionList.cRegions = 1; + + pRegion->offRegion = 0; /* Disk start. */ + pRegion->cbBlock = 512; + pRegion->enmDataForm = VDREGIONDATAFORM_RAW; + pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE; + pRegion->cbData = 512; + pRegion->cbMetadata = 0; + pRegion->cRegionBlocksOrBytes = pImage->cbSize; + } + else + parallelsFreeImage(pImage, false); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Internal: Create a parallels image. + */ +static int parallelsCreateImage(PPARALLELSIMAGE pImage, uint64_t cbSize, + unsigned uImageFlags, const char *pszComment, + PCVDGEOMETRY pPCHSGeometry, + PCVDGEOMETRY pLCHSGeometry, unsigned uOpenFlags, + PFNVDPROGRESS pfnProgress, void *pvUser, + unsigned uPercentStart, unsigned uPercentSpan) +{ + RT_NOREF1(pszComment); + int rc = VINF_SUCCESS; + int32_t fOpen; + + if (!(uImageFlags & VD_IMAGE_FLAGS_FIXED)) + { + pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk); + pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage); + AssertPtrReturn(pImage->pIfIo, VERR_INVALID_PARAMETER); + + pImage->uOpenFlags = uOpenFlags & ~VD_OPEN_FLAGS_READONLY; + pImage->uImageFlags = uImageFlags; + pImage->PCHSGeometry = *pPCHSGeometry; + pImage->LCHSGeometry = *pLCHSGeometry; + if (!pImage->PCHSGeometry.cCylinders) + { + /* Set defaults. */ + pImage->PCHSGeometry.cSectors = 63; + pImage->PCHSGeometry.cHeads = 16; + pImage->PCHSGeometry.cCylinders = pImage->cbSize / (512 * pImage->PCHSGeometry.cSectors * pImage->PCHSGeometry.cHeads); + } + + /* Create image file. */ + fOpen = VDOpenFlagsToFileOpenFlags(pImage->uOpenFlags, true /* fCreate */); + rc = vdIfIoIntFileOpen(pImage->pIfIo, pImage->pszFilename, fOpen, &pImage->pStorage); + if (RT_SUCCESS(rc)) + { + if (pfnProgress) + pfnProgress(pvUser, uPercentStart + uPercentSpan * 98 / 100); + + /* Setup image state. */ + pImage->cbSize = cbSize; + pImage->cAllocationBitmapEntries = cbSize / 512 / pImage->PCHSGeometry.cSectors; + if (pImage->cAllocationBitmapEntries * pImage->PCHSGeometry.cSectors * 512 < cbSize) + pImage->cAllocationBitmapEntries++; + pImage->fAllocationBitmapChanged = true; + pImage->cbFileCurrent = sizeof(ParallelsHeader) + pImage->cAllocationBitmapEntries * sizeof(uint32_t); + /* Round to next sector boundary. */ + pImage->cbFileCurrent += 512 - pImage->cbFileCurrent % 512; + Assert(!(pImage->cbFileCurrent % 512)); + pImage->pAllocationBitmap = (uint32_t *)RTMemAllocZ(pImage->cAllocationBitmapEntries * sizeof(uint32_t)); + if (pImage->pAllocationBitmap) + { + ParallelsHeader Header; + + memcpy(Header.HeaderIdentifier, PARALLELS_HEADER_MAGIC, sizeof(Header.HeaderIdentifier)); + Header.uVersion = RT_H2LE_U32(PARALLELS_DISK_VERSION); + Header.cHeads = RT_H2LE_U32(pImage->PCHSGeometry.cHeads); + Header.cCylinders = RT_H2LE_U32(pImage->PCHSGeometry.cCylinders); + Header.cSectorsPerTrack = RT_H2LE_U32(pImage->PCHSGeometry.cSectors); + Header.cEntriesInAllocationBitmap = RT_H2LE_U32(pImage->cAllocationBitmapEntries); + Header.cSectors = RT_H2LE_U32(pImage->cbSize / 512); + memset(Header.Padding, 0, sizeof(Header.Padding)); + + /* Write header and allocation bitmap. */ + rc = vdIfIoIntFileSetSize(pImage->pIfIo, pImage->pStorage, pImage->cbFileCurrent); + if (RT_SUCCESS(rc)) + rc = vdIfIoIntFileWriteSync(pImage->pIfIo, pImage->pStorage, 0, + &Header, sizeof(Header)); + if (RT_SUCCESS(rc)) + rc = parallelsFlushImage(pImage); /* Writes the allocation bitmap. */ + } + else + rc = VERR_NO_MEMORY; + } + else + rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("Parallels: cannot create image '%s'"), pImage->pszFilename); + } + else + rc = vdIfError(pImage->pIfError, VERR_VD_INVALID_TYPE, RT_SRC_POS, N_("Parallels: cannot create fixed image '%s'. Create a raw image"), pImage->pszFilename); + + if (RT_SUCCESS(rc) && pfnProgress) + pfnProgress(pvUser, uPercentStart + uPercentSpan); + + if (RT_SUCCESS(rc)) + { + PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0]; + pImage->RegionList.fFlags = 0; + pImage->RegionList.cRegions = 1; + + pRegion->offRegion = 0; /* Disk start. */ + pRegion->cbBlock = 512; + pRegion->enmDataForm = VDREGIONDATAFORM_RAW; + pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE; + pRegion->cbData = 512; + pRegion->cbMetadata = 0; + pRegion->cRegionBlocksOrBytes = pImage->cbSize; + } + else + parallelsFreeImage(pImage, rc != VERR_ALREADY_EXISTS); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnProbe */ +static DECLCALLBACK(int) parallelsProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk, + PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType) +{ + RT_NOREF(pVDIfsDisk, enmDesiredType); + int rc; + PVDIOSTORAGE pStorage; + ParallelsHeader parallelsHeader; + + PVDINTERFACEIOINT pIfIo = VDIfIoIntGet(pVDIfsImage); + AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER); + + rc = vdIfIoIntFileOpen(pIfIo, pszFilename, + VDOpenFlagsToFileOpenFlags(VD_OPEN_FLAGS_READONLY, + false /* fCreate */), + &pStorage); + if (RT_FAILURE(rc)) + return rc; + + rc = vdIfIoIntFileReadSync(pIfIo, pStorage, 0, ¶llelsHeader, + sizeof(ParallelsHeader)); + if (RT_SUCCESS(rc)) + { + if ( !memcmp(parallelsHeader.HeaderIdentifier, PARALLELS_HEADER_MAGIC, 16) + && (parallelsHeader.uVersion == PARALLELS_DISK_VERSION)) + rc = VINF_SUCCESS; + else + { + /* + * The image may be an fixed size image. + * Unfortunately fixed sized parallels images + * are just raw files hence no magic header to + * check for. + * The code succeeds if the file is a multiple + * of 512 and if the file extensions is *.hdd + */ + uint64_t cbFile; + char *pszSuffix; + + rc = vdIfIoIntFileGetSize(pIfIo, pStorage, &cbFile); + if (RT_FAILURE(rc) || ((cbFile % 512) != 0)) + { + vdIfIoIntFileClose(pIfIo, pStorage); + return VERR_VD_PARALLELS_INVALID_HEADER; + } + + pszSuffix = RTPathSuffix(pszFilename); + if (!pszSuffix || strcmp(pszSuffix, ".hdd")) + rc = VERR_VD_PARALLELS_INVALID_HEADER; + else + rc = VINF_SUCCESS; + } + } + + if (RT_SUCCESS(rc)) + *penmType = VDTYPE_HDD; + + vdIfIoIntFileClose(pIfIo, pStorage); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnOpen */ +static DECLCALLBACK(int) parallelsOpen(const char *pszFilename, unsigned uOpenFlags, + PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage, + VDTYPE enmType, void **ppBackendData) +{ + LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p enmType=%u ppBackendData=%#p\n", pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData)); + int rc; + PPARALLELSIMAGE pImage; + + NOREF(enmType); /**< @todo r=klaus make use of the type info. */ + + /* Check parameters. */ + AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER); + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER); + + + pImage = (PPARALLELSIMAGE)RTMemAllocZ(RT_UOFFSETOF(PARALLELSIMAGE, RegionList.aRegions[1])); + if (RT_LIKELY(pImage)) + { + pImage->pszFilename = pszFilename; + pImage->pStorage = NULL; + pImage->pVDIfsDisk = pVDIfsDisk; + pImage->pVDIfsImage = pVDIfsImage; + pImage->fAllocationBitmapChanged = false; + + rc = parallelsOpenImage(pImage, uOpenFlags); + if (RT_SUCCESS(rc)) + *ppBackendData = pImage; + else + RTMemFree(pImage); + } + else + rc = VERR_NO_MEMORY; + + LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnCreate */ +static DECLCALLBACK(int) parallelsCreate(const char *pszFilename, uint64_t cbSize, + unsigned uImageFlags, const char *pszComment, + PCVDGEOMETRY pPCHSGeometry, + PCVDGEOMETRY pLCHSGeometry, PCRTUUID pUuid, + unsigned uOpenFlags, unsigned uPercentStart, + unsigned uPercentSpan, PVDINTERFACE pVDIfsDisk, + PVDINTERFACE pVDIfsImage, + PVDINTERFACE pVDIfsOperation, VDTYPE enmType, + void **ppBackendData) +{ + RT_NOREF1(pUuid); + LogFlowFunc(("pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p enmType=%u ppBackendData=%#p", + pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData)); + + /* Check the VD container type. */ + if (enmType != VDTYPE_HDD) + return VERR_VD_INVALID_TYPE; + + /* Check arguments. */ + AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER); + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER); + AssertPtrReturn(pPCHSGeometry, VERR_INVALID_POINTER); + AssertPtrReturn(pLCHSGeometry, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + PPARALLELSIMAGE pImage; + PFNVDPROGRESS pfnProgress = NULL; + void *pvUser = NULL; + PVDINTERFACEPROGRESS pIfProgress = VDIfProgressGet(pVDIfsOperation); + if (pIfProgress) + { + pfnProgress = pIfProgress->pfnProgress; + pvUser = pIfProgress->Core.pvUser; + } + + pImage = (PPARALLELSIMAGE)RTMemAllocZ(RT_UOFFSETOF(PARALLELSIMAGE, RegionList.aRegions[1])); + if (RT_LIKELY(pImage)) + { + pImage->pszFilename = pszFilename; + pImage->pStorage = NULL; + pImage->pVDIfsDisk = pVDIfsDisk; + pImage->pVDIfsImage = pVDIfsImage; + + rc = parallelsCreateImage(pImage, cbSize, uImageFlags, pszComment, + pPCHSGeometry, pLCHSGeometry, uOpenFlags, + pfnProgress, pvUser, uPercentStart, uPercentSpan); + if (RT_SUCCESS(rc)) + { + /* So far the image is opened in read/write mode. Make sure the + * image is opened in read-only mode if the caller requested that. */ + if (uOpenFlags & VD_OPEN_FLAGS_READONLY) + { + parallelsFreeImage(pImage, false); + rc = parallelsOpenImage(pImage, uOpenFlags); + } + + if (RT_SUCCESS(rc)) + *ppBackendData = pImage; + } + + if (RT_FAILURE(rc)) + RTMemFree(pImage); + } + else + rc = VERR_NO_MEMORY; + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnRename */ +static DECLCALLBACK(int) parallelsRename(void *pBackendData, const char *pszFilename) +{ + LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename)); + int rc = VINF_SUCCESS; + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + /* Check arguments. */ + AssertReturn((pImage && pszFilename && *pszFilename), VERR_INVALID_PARAMETER); + + /* Close the image. */ + rc = parallelsFreeImage(pImage, false); + if (RT_SUCCESS(rc)) + { + /* Rename the file. */ + rc = vdIfIoIntFileMove(pImage->pIfIo, pImage->pszFilename, pszFilename, 0); + if (RT_SUCCESS(rc)) + { + /* Update pImage with the new information. */ + pImage->pszFilename = pszFilename; + + /* Open the old image with new name. */ + rc = parallelsOpenImage(pImage, pImage->uOpenFlags); + } + else + { + /* The move failed, try to reopen the original image. */ + int rc2 = parallelsOpenImage(pImage, pImage->uOpenFlags); + if (RT_FAILURE(rc2)) + rc = rc2; + } + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnClose */ +static DECLCALLBACK(int) parallelsClose(void *pBackendData, bool fDelete) +{ + LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + int rc = parallelsFreeImage(pImage, fDelete); + RTMemFree(pImage); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnRead */ +static DECLCALLBACK(int) parallelsRead(void *pBackendData, uint64_t uOffset, size_t cbToRead, + PVDIOCTX pIoCtx, size_t *pcbActuallyRead) +{ + LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToRead=%zu pcbActuallyRead=%#p\n", + pBackendData, uOffset, pIoCtx, cbToRead, pcbActuallyRead)); + int rc = VINF_SUCCESS; + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + uint64_t uSector; + uint64_t uOffsetInFile; + uint32_t iIndexInAllocationTable; + + AssertPtr(pImage); + Assert(uOffset % 512 == 0); + Assert(cbToRead % 512 == 0); + + if (pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED) + rc = vdIfIoIntFileReadUser(pImage->pIfIo, pImage->pStorage, uOffset, + pIoCtx, cbToRead); + else + { + /* Calculate offset in the real file. */ + uSector = uOffset / 512; + /* One chunk in the file is always one track big. */ + iIndexInAllocationTable = (uint32_t)(uSector / pImage->PCHSGeometry.cSectors); + uSector = uSector % pImage->PCHSGeometry.cSectors; + + cbToRead = RT_MIN(cbToRead, (pImage->PCHSGeometry.cSectors - uSector)*512); + + if (pImage->pAllocationBitmap[iIndexInAllocationTable] == 0) + rc = VERR_VD_BLOCK_FREE; + else + { + uOffsetInFile = (pImage->pAllocationBitmap[iIndexInAllocationTable] + uSector) * 512; + rc = vdIfIoIntFileReadUser(pImage->pIfIo, pImage->pStorage, uOffsetInFile, + pIoCtx, cbToRead); + } + } + + *pcbActuallyRead = cbToRead; + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnWrite */ +static DECLCALLBACK(int) parallelsWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite, + PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead, + size_t *pcbPostRead, unsigned fWrite) +{ + LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToWrite=%zu pcbWriteProcess=%#p\n", + pBackendData, uOffset, pIoCtx, cbToWrite, pcbWriteProcess)); + int rc = VINF_SUCCESS; + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + uint64_t uSector; + uint64_t uOffsetInFile; + uint32_t iIndexInAllocationTable; + + AssertPtr(pImage); + Assert(uOffset % 512 == 0); + Assert(cbToWrite % 512 == 0); + + if (pImage->uImageFlags & VD_IMAGE_FLAGS_FIXED) + rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage, uOffset, + pIoCtx, cbToWrite, NULL, NULL); + else + { + /* Calculate offset in the real file. */ + uSector = uOffset / 512; + /* One chunk in the file is always one track big. */ + iIndexInAllocationTable = (uint32_t)(uSector / pImage->PCHSGeometry.cSectors); + uSector = uSector % pImage->PCHSGeometry.cSectors; + + cbToWrite = RT_MIN(cbToWrite, (pImage->PCHSGeometry.cSectors - uSector)*512); + + if (pImage->pAllocationBitmap[iIndexInAllocationTable] == 0) + { + if (fWrite & VD_WRITE_NO_ALLOC) + { + *pcbPreRead = uSector * 512; + *pcbPostRead = pImage->PCHSGeometry.cSectors * 512 - cbToWrite - *pcbPreRead; + + if (pcbWriteProcess) + *pcbWriteProcess = cbToWrite; + return VERR_VD_BLOCK_FREE; + } + + /* Allocate new chunk in the file. */ + Assert(uSector == 0); + AssertMsg(pImage->cbFileCurrent % 512 == 0, ("File size is not a multiple of 512\n")); + pImage->pAllocationBitmap[iIndexInAllocationTable] = (uint32_t)(pImage->cbFileCurrent / 512); + pImage->cbFileCurrent += pImage->PCHSGeometry.cSectors * 512; + pImage->fAllocationBitmapChanged = true; + uOffsetInFile = (uint64_t)pImage->pAllocationBitmap[iIndexInAllocationTable] * 512; + + /* + * Write the new block at the current end of the file. + */ + rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage, + uOffsetInFile, pIoCtx, cbToWrite, NULL, NULL); + if (RT_SUCCESS(rc) || (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)) + { + /* Write the changed allocation bitmap entry. */ + /** @todo Error handling. */ + rc = vdIfIoIntFileWriteMeta(pImage->pIfIo, pImage->pStorage, + sizeof(ParallelsHeader) + iIndexInAllocationTable * sizeof(uint32_t), + &pImage->pAllocationBitmap[iIndexInAllocationTable], + sizeof(uint32_t), pIoCtx, + NULL, NULL); + } + + *pcbPreRead = 0; + *pcbPostRead = 0; + } + else + { + uOffsetInFile = (pImage->pAllocationBitmap[iIndexInAllocationTable] + uSector) * 512; + rc = vdIfIoIntFileWriteUser(pImage->pIfIo, pImage->pStorage, + uOffsetInFile, pIoCtx, cbToWrite, NULL, NULL); + } + } + + if (pcbWriteProcess) + *pcbWriteProcess = cbToWrite; + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnFlush */ +static DECLCALLBACK(int) parallelsFlush(void *pBackendData, PVDIOCTX pIoCtx) +{ + int rc = VINF_SUCCESS; + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + LogFlowFunc(("pImage=#%p\n", pImage)); + + /* Flush the file, everything is up to date already. */ + rc = vdIfIoIntFileFlush(pImage->pIfIo, pImage->pStorage, pIoCtx, NULL, NULL); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetVersion */ +static DECLCALLBACK(unsigned) parallelsGetVersion(void *pBackendData) +{ + LogFlowFunc(("pBackendData=%#p\n", pBackendData)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + AssertPtrReturn(pImage, 0); + + return PARALLELS_DISK_VERSION; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetFileSize */ +static DECLCALLBACK(uint64_t) parallelsGetFileSize(void *pBackendData) +{ + LogFlowFunc(("pBackendData=%#p\n", pBackendData)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + uint64_t cb = 0; + + AssertPtrReturn(pImage, 0); + + if (pImage->pStorage) + cb = pImage->cbFileCurrent; + + LogFlowFunc(("returns %lld\n", cb)); + return cb; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetPCHSGeometry */ +static DECLCALLBACK(int) parallelsGetPCHSGeometry(void *pBackendData, + PVDGEOMETRY pPCHSGeometry) +{ + LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + int rc = VINF_SUCCESS; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + if (pImage->PCHSGeometry.cCylinders) + *pPCHSGeometry = pImage->PCHSGeometry; + else + rc = VERR_VD_GEOMETRY_NOT_SET; + + LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnSetPCHSGeometry */ +static DECLCALLBACK(int) parallelsSetPCHSGeometry(void *pBackendData, + PCVDGEOMETRY pPCHSGeometry) +{ + LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n", pBackendData, + pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + int rc = VINF_SUCCESS; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) + rc = VERR_VD_IMAGE_READ_ONLY; + else + pImage->PCHSGeometry = *pPCHSGeometry; + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetLCHSGeometry */ +static DECLCALLBACK(int) parallelsGetLCHSGeometry(void *pBackendData, + PVDGEOMETRY pLCHSGeometry) +{ + LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + int rc = VINF_SUCCESS; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + if (pImage->LCHSGeometry.cCylinders) + *pLCHSGeometry = pImage->LCHSGeometry; + else + rc = VERR_VD_GEOMETRY_NOT_SET; + + LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", rc, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnSetLCHSGeometry */ +static DECLCALLBACK(int) parallelsSetLCHSGeometry(void *pBackendData, + PCVDGEOMETRY pLCHSGeometry) +{ + LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n", pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + int rc = VINF_SUCCESS; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) + rc = VERR_VD_IMAGE_READ_ONLY; + else + pImage->LCHSGeometry = *pLCHSGeometry; + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */ +static DECLCALLBACK(int) parallelsQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList) +{ + LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList)); + PPARALLELSIMAGE pThis = (PPARALLELSIMAGE)pBackendData; + + AssertPtrReturn(pThis, VERR_VD_NOT_OPENED); + + *ppRegionList = &pThis->RegionList; + LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS)); + return VINF_SUCCESS; +} + +/** @copydoc VDIMAGEBACKEND::pfnRegionListRelease */ +static DECLCALLBACK(void) parallelsRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList) +{ + RT_NOREF1(pRegionList); + LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList)); + PPARALLELSIMAGE pThis = (PPARALLELSIMAGE)pBackendData; + AssertPtr(pThis); RT_NOREF(pThis); + + /* Nothing to do here. */ +} + +/** @copydoc VDIMAGEBACKEND::pfnGetImageFlags */ +static DECLCALLBACK(unsigned) parallelsGetImageFlags(void *pBackendData) +{ + LogFlowFunc(("pBackendData=%#p\n", pBackendData)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + AssertPtrReturn(pImage, 0); + + LogFlowFunc(("returns %#x\n", pImage->uImageFlags)); + return pImage->uImageFlags; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetOpenFlags */ +static DECLCALLBACK(unsigned) parallelsGetOpenFlags(void *pBackendData) +{ + LogFlowFunc(("pBackendData=%#p\n", pBackendData)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + AssertPtrReturn(pImage, 0); + + LogFlowFunc(("returns %#x\n", pImage->uOpenFlags)); + return pImage->uOpenFlags; +} + +/** @copydoc VDIMAGEBACKEND::pfnSetOpenFlags */ +static DECLCALLBACK(int) parallelsSetOpenFlags(void *pBackendData, unsigned uOpenFlags) +{ + LogFlowFunc(("pBackendData=%#p\n uOpenFlags=%#x", pBackendData, uOpenFlags)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + int rc = VINF_SUCCESS; + + /* Image must be opened and the new flags must be valid. */ + if (!pImage || (uOpenFlags & ~( VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO + | VD_OPEN_FLAGS_ASYNC_IO | VD_OPEN_FLAGS_SHAREABLE + | VD_OPEN_FLAGS_SEQUENTIAL | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS))) + rc = VERR_INVALID_PARAMETER; + else + { + /* Implement this operation via reopening the image. */ + parallelsFreeImage(pImage, false); + rc = parallelsOpenImage(pImage, uOpenFlags); + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetComment */ +static DECLCALLBACK(int) parallelsGetComment(void *pBackendData, char *pszComment, + size_t cbComment) +{ + RT_NOREF2(pszComment, cbComment); + LogFlowFunc(("pBackendData=%#p pszComment=%#p cbComment=%zu\n", pBackendData, pszComment, cbComment)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + LogFlowFunc(("returns %Rrc comment='%s'\n", VERR_NOT_SUPPORTED, pszComment)); + return VERR_NOT_SUPPORTED; +} + +/** @copydoc VDIMAGEBACKEND::pfnSetComment */ +static DECLCALLBACK(int) parallelsSetComment(void *pBackendData, const char *pszComment) +{ + RT_NOREF1(pszComment); + LogFlowFunc(("pBackendData=%#p pszComment=\"%s\"\n", pBackendData, pszComment)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + int rc; + if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) + rc = VERR_VD_IMAGE_READ_ONLY; + else + rc = VERR_NOT_SUPPORTED; + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetUuid */ +static DECLCALLBACK(int) parallelsGetUuid(void *pBackendData, PRTUUID pUuid) +{ + RT_NOREF1(pUuid); + LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + LogFlowFunc(("returns %Rrc (%RTuuid)\n", VERR_NOT_SUPPORTED, pUuid)); + return VERR_NOT_SUPPORTED; +} + +/** @copydoc VDIMAGEBACKEND::pfnSetUuid */ +static DECLCALLBACK(int) parallelsSetUuid(void *pBackendData, PCRTUUID pUuid) +{ + RT_NOREF1(pUuid); + LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + int rc; + if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) + rc = VERR_VD_IMAGE_READ_ONLY; + else + rc = VERR_NOT_SUPPORTED; + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */ +static DECLCALLBACK(int) parallelsGetModificationUuid(void *pBackendData, PRTUUID pUuid) +{ + RT_NOREF1(pUuid); + LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + LogFlowFunc(("returns %Rrc (%RTuuid)\n", VERR_NOT_SUPPORTED, pUuid)); + return VERR_NOT_SUPPORTED; +} + +/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */ +static DECLCALLBACK(int) parallelsSetModificationUuid(void *pBackendData, PCRTUUID pUuid) +{ + RT_NOREF1(pUuid); + LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + int rc; + if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) + rc = VERR_VD_IMAGE_READ_ONLY; + else + rc = VERR_NOT_SUPPORTED; + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */ +static DECLCALLBACK(int) parallelsGetParentUuid(void *pBackendData, PRTUUID pUuid) +{ + RT_NOREF1(pUuid); + LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + LogFlowFunc(("returns %Rrc (%RTuuid)\n", VERR_NOT_SUPPORTED, pUuid)); + return VERR_NOT_SUPPORTED; +} + +/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */ +static DECLCALLBACK(int) parallelsSetParentUuid(void *pBackendData, PCRTUUID pUuid) +{ + RT_NOREF1(pUuid); + LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + int rc; + if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) + rc = VERR_VD_IMAGE_READ_ONLY; + else + rc = VERR_NOT_SUPPORTED; + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */ +static DECLCALLBACK(int) parallelsGetParentModificationUuid(void *pBackendData, PRTUUID pUuid) +{ + RT_NOREF1(pUuid); + LogFlowFunc(("pBackendData=%#p pUuid=%#p\n", pBackendData, pUuid)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + int rc; + if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) + rc = VERR_VD_IMAGE_READ_ONLY; + else + rc = VERR_NOT_SUPPORTED; + + LogFlowFunc(("returns %Rrc (%RTuuid)\n", rc, pUuid)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */ +static DECLCALLBACK(int) parallelsSetParentModificationUuid(void *pBackendData, PCRTUUID pUuid) +{ + RT_NOREF1(pUuid); + LogFlowFunc(("pBackendData=%#p Uuid=%RTuuid\n", pBackendData, pUuid)); + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + int rc; + if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) + rc = VERR_VD_IMAGE_READ_ONLY; + else + rc = VERR_NOT_SUPPORTED; + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnDump */ +static DECLCALLBACK(void) parallelsDump(void *pBackendData) +{ + PPARALLELSIMAGE pImage = (PPARALLELSIMAGE)pBackendData; + + AssertPtrReturnVoid(pImage); + vdIfErrorMessage(pImage->pIfError, "Header: Geometry PCHS=%u/%u/%u LCHS=%u/%u/%u\n", + pImage->PCHSGeometry.cCylinders, pImage->PCHSGeometry.cHeads, pImage->PCHSGeometry.cSectors, + pImage->LCHSGeometry.cCylinders, pImage->LCHSGeometry.cHeads, pImage->LCHSGeometry.cSectors); +} + + + +const VDIMAGEBACKEND g_ParallelsBackend = +{ + /* u32Version */ + VD_IMGBACKEND_VERSION, + /* pszBackendName */ + "Parallels", + /* uBackendCaps */ + VD_CAP_FILE | VD_CAP_ASYNC | VD_CAP_VFS | VD_CAP_CREATE_DYNAMIC | VD_CAP_DIFF, + /* paFileExtensions */ + s_aParallelsFileExtensions, + /* paConfigInfo */ + NULL, + /* pfnProbe */ + parallelsProbe, + /* pfnOpen */ + parallelsOpen, + /* pfnCreate */ + parallelsCreate, + /* pfnRename */ + parallelsRename, + /* pfnClose */ + parallelsClose, + /* pfnRead */ + parallelsRead, + /* pfnWrite */ + parallelsWrite, + /* pfnFlush */ + parallelsFlush, + /* pfnDiscard */ + NULL, + /* pfnGetVersion */ + parallelsGetVersion, + /* pfnGetFileSize */ + parallelsGetFileSize, + /* pfnGetPCHSGeometry */ + parallelsGetPCHSGeometry, + /* pfnSetPCHSGeometry */ + parallelsSetPCHSGeometry, + /* pfnGetLCHSGeometry */ + parallelsGetLCHSGeometry, + /* pfnSetLCHSGeometry */ + parallelsSetLCHSGeometry, + /* pfnQueryRegions */ + parallelsQueryRegions, + /* pfnRegionListRelease */ + parallelsRegionListRelease, + /* pfnGetImageFlags */ + parallelsGetImageFlags, + /* pfnGetOpenFlags */ + parallelsGetOpenFlags, + /* pfnSetOpenFlags */ + parallelsSetOpenFlags, + /* pfnGetComment */ + parallelsGetComment, + /* pfnSetComment */ + parallelsSetComment, + /* pfnGetUuid */ + parallelsGetUuid, + /* pfnSetUuid */ + parallelsSetUuid, + /* pfnGetModificationUuid */ + parallelsGetModificationUuid, + /* pfnSetModificationUuid */ + parallelsSetModificationUuid, + /* pfnGetParentUuid */ + parallelsGetParentUuid, + /* pfnSetParentUuid */ + parallelsSetParentUuid, + /* pfnGetParentModificationUuid */ + parallelsGetParentModificationUuid, + /* pfnSetParentModificationUuid */ + parallelsSetParentModificationUuid, + /* pfnDump */ + parallelsDump, + /* pfnGetTimestamp */ + NULL, + /* pfnGetParentTimestamp */ + NULL, + /* pfnSetParentTimestamp */ + NULL, + /* pfnGetParentFilename */ + NULL, + /* pfnSetParentFilename */ + NULL, + /* pfnComposeLocation */ + genericFileComposeLocation, + /* pfnComposeName */ + genericFileComposeName, + /* pfnCompact */ + NULL, + /* pfnResize */ + NULL, + /* pfnRepair */ + NULL, + /* pfnTraverseMetadata */ + NULL, + /* u32VersionEnd */ + VD_IMGBACKEND_VERSION +}; |