summaryrefslogtreecommitdiffstats
path: root/src/VBox/Storage/DMG.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Storage/DMG.cpp')
-rw-r--r--src/VBox/Storage/DMG.cpp2393
1 files changed, 2393 insertions, 0 deletions
diff --git a/src/VBox/Storage/DMG.cpp b/src/VBox/Storage/DMG.cpp
new file mode 100644
index 00000000..ee27e242
--- /dev/null
+++ b/src/VBox/Storage/DMG.cpp
@@ -0,0 +1,2393 @@
+/* $Id: DMG.cpp $ */
+/** @file
+ * VBoxDMG - Interpreter for Apple Disk Images (DMG).
+ */
+
+/*
+ * Copyright (C) 2010-2023 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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VD_DMG
+#include <VBox/vd-plugin.h>
+#include <VBox/vd-ifs.h>
+#include <VBox/log.h>
+#include <VBox/err.h>
+
+#include <iprt/asm.h>
+#include <iprt/alloca.h>
+#include <iprt/assert.h>
+#include <iprt/base64.h>
+#include <iprt/ctype.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/zip.h>
+#include <iprt/formats/xar.h>
+
+#include "VDBackends.h"
+#include "VDBackendsInline.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+#if 0
+/** @def VBOX_WITH_DIRECT_XAR_ACCESS
+ * When defined, we will use RTVfs to access the XAR file instead of going
+ * the slightly longer way thru the VFS -> VD wrapper. */
+# define VBOX_WITH_DIRECT_XAR_ACCESS
+#endif
+
+/** Sector size, multiply with all sector counts to get number of bytes. */
+#define DMG_SECTOR_SIZE 512
+
+/** Convert block number/size to byte offset/size. */
+#define DMG_BLOCK2BYTE(u) ((uint64_t)(u) << 9)
+
+/** Convert byte offset/size to block number/size. */
+#define DMG_BYTE2BLOCK(u) ((u) >> 9)
+
+/**
+ * UDIF checksum structure.
+ */
+typedef struct DMGUDIFCKSUM
+{
+ uint32_t u32Kind; /**< The kind of checksum. */
+ uint32_t cBits; /**< The size of the checksum. */
+ union
+ {
+ uint8_t au8[128]; /**< 8-bit view. */
+ uint32_t au32[32]; /**< 32-bit view. */
+ } uSum; /**< The checksum. */
+} DMGUDIFCKSUM;
+AssertCompileSize(DMGUDIFCKSUM, 8 + 128);
+typedef DMGUDIFCKSUM *PDMGUDIFCKSUM;
+typedef const DMGUDIFCKSUM *PCDMGUDIFCKSUM;
+
+/** @name Checksum Kind (DMGUDIFCKSUM::u32Kind)
+ * @{ */
+/** No checksum. */
+#define DMGUDIFCKSUM_NONE UINT32_C(0)
+/** CRC-32. */
+#define DMGUDIFCKSUM_CRC32 UINT32_C(2)
+/** @} */
+
+/**
+ * UDIF ID.
+ * This is kind of like a UUID only it isn't, but we'll use the UUID
+ * representation of it for simplicity.
+ */
+typedef RTUUID DMGUDIFID;
+AssertCompileSize(DMGUDIFID, 16);
+typedef DMGUDIFID *PDMGUDIFID;
+typedef const DMGUDIFID *PCDMGUDIFID;
+
+/**
+ * UDIF footer used by Apple Disk Images (DMG).
+ *
+ * This is a footer placed 512 bytes from the end of the file. Typically a DMG
+ * file starts with the data, which is followed by the block table and then ends
+ * with this structure.
+ *
+ * All fields are stored in big endian format.
+ */
+#pragma pack(1)
+typedef struct DMGUDIF
+{
+ uint32_t u32Magic; /**< 0x000 - Magic, 'koly' (DMGUDIF_MAGIC). (fUDIFSignature) */
+ uint32_t u32Version; /**< 0x004 - The UDIF version (DMGUDIF_VER_CURRENT). (fUDIFVersion) */
+ uint32_t cbFooter; /**< 0x008 - The size of the this structure (512). (fUDIFHeaderSize) */
+ uint32_t fFlags; /**< 0x00c - Flags. (fUDIFFlags) */
+ uint64_t offRunData; /**< 0x010 - Where the running data fork starts (usually 0). (fUDIFRunningDataForkOffset) */
+ uint64_t offData; /**< 0x018 - Where the data fork starts (usually 0). (fUDIFDataForkOffset) */
+ uint64_t cbData; /**< 0x020 - Size of the data fork (in bytes). (fUDIFDataForkLength) */
+ uint64_t offRsrc; /**< 0x028 - Where the resource fork starts (usually cbData or 0). (fUDIFRsrcForkOffset) */
+ uint64_t cbRsrc; /**< 0x030 - The size of the resource fork. (fUDIFRsrcForkLength)*/
+ uint32_t iSegment; /**< 0x038 - The segment number of this file. (fUDIFSegmentNumber) */
+ uint32_t cSegments; /**< 0x03c - The number of segments. (fUDIFSegmentCount) */
+ DMGUDIFID SegmentId; /**< 0x040 - The segment ID. (fUDIFSegmentID) */
+ DMGUDIFCKSUM DataCkSum; /**< 0x050 - The data checksum. (fUDIFDataForkChecksum) */
+ uint64_t offXml; /**< 0x0d8 - The XML offset (.plist kind of data). (fUDIFXMLOffset) */
+ uint64_t cbXml; /**< 0x0e0 - The size of the XML. (fUDIFXMLSize) */
+ uint8_t abUnknown[120]; /**< 0x0e8 - Unknown stuff, hdiutil doesn't dump it... */
+ DMGUDIFCKSUM MasterCkSum; /**< 0x160 - The master checksum. (fUDIFMasterChecksum) */
+ uint32_t u32Type; /**< 0x1e8 - The image type. (fUDIFImageVariant) */
+ uint64_t cSectors; /**< 0x1ec - The sector count. Warning! Unaligned! (fUDISectorCount) */
+ uint32_t au32Unknown[3]; /**< 0x1f4 - Unknown stuff, hdiutil doesn't dump it... */
+} DMGUDIF;
+#pragma pack()
+AssertCompileSize(DMGUDIF, 512);
+AssertCompileMemberOffset(DMGUDIF, cbRsrc, 0x030);
+AssertCompileMemberOffset(DMGUDIF, cbXml, 0x0e0);
+AssertCompileMemberOffset(DMGUDIF, cSectors, 0x1ec);
+
+typedef DMGUDIF *PDMGUDIF;
+typedef const DMGUDIF *PCDMGUDIF;
+
+/** The UDIF magic 'koly' (DMGUDIF::u32Magic). */
+#define DMGUDIF_MAGIC UINT32_C(0x6b6f6c79)
+
+/** The current UDIF version (DMGUDIF::u32Version).
+ * This is currently the only we recognizes and will create. */
+#define DMGUDIF_VER_CURRENT 4
+
+/** @name UDIF flags (DMGUDIF::fFlags).
+ * @{ */
+/** Flatten image whatever that means.
+ * (hdiutil -debug calls it kUDIFFlagsFlattened.) */
+#define DMGUDIF_FLAGS_FLATTENED RT_BIT_32(0)
+/** Internet enabled image.
+ * (hdiutil -debug calls it kUDIFFlagsInternetEnabled) */
+#define DMGUDIF_FLAGS_INET_ENABLED RT_BIT_32(2)
+/** Mask of known bits. */
+#define DMGUDIF_FLAGS_KNOWN_MASK (RT_BIT_32(0) | RT_BIT_32(2))
+/** @} */
+
+/** @name UDIF Image Types (DMGUDIF::u32Type).
+ * @{ */
+/** Device image type. (kUDIFDeviceImageType) */
+#define DMGUDIF_TYPE_DEVICE 1
+/** Device image type. (kUDIFPartitionImageType) */
+#define DMGUDIF_TYPE_PARTITION 2
+/** @} */
+
+/**
+ * BLKX data.
+ *
+ * This contains the start offset and size of raw data stored in the image.
+ *
+ * All fields are stored in big endian format.
+ */
+#pragma pack(1)
+typedef struct DMGBLKX
+{
+ uint32_t u32Magic; /**< 0x000 - Magic, 'mish' (DMGBLKX_MAGIC). */
+ uint32_t u32Version; /**< 0x004 - The BLKX version (DMGBLKX_VER_CURRENT). */
+ uint64_t cSectornumberFirst; /**< 0x008 - The first sector number the block represents in the virtual device. */
+ uint64_t cSectors; /**< 0x010 - Number of sectors this block represents. */
+ uint64_t offDataStart; /**< 0x018 - Start offset for raw data. */
+ uint32_t cSectorsDecompress; /**< 0x020 - Size of the buffer in sectors needed to decompress. */
+ uint32_t u32BlocksDescriptor; /**< 0x024 - Blocks descriptor. */
+ uint8_t abReserved[24];
+ DMGUDIFCKSUM BlkxCkSum; /**< 0x03c - Checksum for the BLKX table. */
+ uint32_t cBlocksRunCount; /**< 0x - Number of entries in the blkx run table afterwards. */
+} DMGBLKX;
+#pragma pack()
+AssertCompileSize(DMGBLKX, 204);
+
+typedef DMGBLKX *PDMGBLKX;
+typedef const DMGBLKX *PCDMGBLKX;
+
+/** The BLKX magic 'mish' (DMGBLKX::u32Magic). */
+#define DMGBLKX_MAGIC UINT32_C(0x6d697368)
+/** BLKX version (DMGBLKX::u32Version). */
+#define DMGBLKX_VERSION UINT32_C(0x00000001)
+
+/** Blocks descriptor type: entire device. */
+#define DMGBLKX_DESC_ENTIRE_DEVICE UINT32_C(0xfffffffe)
+
+/**
+ * BLKX table descriptor.
+ *
+ * All fields are stored in big endian format.
+ */
+#pragma pack(1)
+typedef struct DMGBLKXDESC
+{
+ uint32_t u32Type; /**< 0x000 - Type of the descriptor. */
+ uint32_t u32Reserved; /**< 0x004 - Reserved, but contains +beg or +end in case thisi is a comment descriptor. */
+ uint64_t u64SectorStart; /**< 0x008 - First sector number in the block this entry describes. */
+ uint64_t u64SectorCount; /**< 0x010 - Number of sectors this entry describes. */
+ uint64_t offData; /**< 0x018 - Offset in the image where the data starts. */
+ uint64_t cbData; /**< 0x020 - Number of bytes in the image. */
+} DMGBLKXDESC;
+#pragma pack()
+AssertCompileSize(DMGBLKXDESC, 40);
+
+typedef DMGBLKXDESC *PDMGBLKXDESC;
+typedef const DMGBLKXDESC *PCDMGBLKXDESC;
+
+/** Raw image data type. */
+#define DMGBLKXDESC_TYPE_RAW 1
+/** Ignore type. */
+#define DMGBLKXDESC_TYPE_IGNORE 2
+/** Compressed with zlib type. */
+#define DMGBLKXDESC_TYPE_ZLIB UINT32_C(0x80000005)
+/** Comment type. */
+#define DMGBLKXDESC_TYPE_COMMENT UINT32_C(0x7ffffffe)
+/** Terminator type. */
+#define DMGBLKXDESC_TYPE_TERMINATOR UINT32_C(0xffffffff)
+
+/**
+ * UDIF Resource Entry.
+ */
+typedef struct DMGUDIFRSRCENTRY
+{
+ /** The ID. */
+ int32_t iId;
+ /** Attributes. */
+ uint32_t fAttributes;
+ /** The name. */
+ char *pszName;
+ /** The CoreFoundation name. Can be NULL. */
+ char *pszCFName;
+ /** The size of the data. */
+ size_t cbData;
+ /** The raw data. */
+ uint8_t *pbData;
+} DMGUDIFRSRCENTRY;
+/** Pointer to an UDIF resource entry. */
+typedef DMGUDIFRSRCENTRY *PDMGUDIFRSRCENTRY;
+/** Pointer to a const UDIF resource entry. */
+typedef DMGUDIFRSRCENTRY const *PCDMGUDIFRSRCENTRY;
+
+/**
+ * UDIF Resource Array.
+ */
+typedef struct DMGUDIFRSRCARRAY
+{
+ /** The array name. */
+ char szName[12];
+ /** The number of occupied entries. */
+ uint32_t cEntries;
+ /** The array entries.
+ * A lazy bird ASSUME there are no more than 4 entries in any DMG. Increase the
+ * size if DMGs with more are found.
+ * r=aeichner: Saw one with 6 here (image of a whole DVD) */
+ DMGUDIFRSRCENTRY aEntries[10];
+} DMGUDIFRSRCARRAY;
+/** Pointer to a UDIF resource array. */
+typedef DMGUDIFRSRCARRAY *PDMGUDIFRSRCARRAY;
+/** Pointer to a const UDIF resource array. */
+typedef DMGUDIFRSRCARRAY const *PCDMGUDIFRSRCARRAY;
+
+/**
+ * DMG extent types.
+ */
+typedef enum DMGEXTENTTYPE
+{
+ /** Null, never used. */
+ DMGEXTENTTYPE_NULL = 0,
+ /** Raw image data. */
+ DMGEXTENTTYPE_RAW,
+ /** Zero extent, reads return 0 and writes have no effect. */
+ DMGEXTENTTYPE_ZERO,
+ /** Compressed extent - compression method ZLIB. */
+ DMGEXTENTTYPE_COMP_ZLIB,
+ /** 32bit hack. */
+ DMGEXTENTTYPE_32BIT_HACK = 0x7fffffff
+} DMGEXTENTTYPE, *PDMGEXTENTTYPE;
+
+/**
+ * DMG extent mapping a virtual image block to real file offsets.
+ */
+typedef struct DMGEXTENT
+{
+ /** Extent type. */
+ DMGEXTENTTYPE enmType;
+ /** First sector this extent describes. */
+ uint64_t uSectorExtent;
+ /** Number of sectors this extent describes. */
+ uint64_t cSectorsExtent;
+ /** Start offset in the real file. */
+ uint64_t offFileStart;
+ /** Number of bytes for the extent data in the file. */
+ uint64_t cbFile;
+} DMGEXTENT;
+/** Pointer to an DMG extent. */
+typedef DMGEXTENT *PDMGEXTENT;
+
+/**
+ * VirtualBox Apple Disk Image (DMG) interpreter instance data.
+ */
+typedef struct DMGIMAGE
+{
+ /** Image name.
+ * Kept around for logging and delete-on-close purposes. */
+ const char *pszFilename;
+ /** 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 - careful accessing this because of hDmgFileInXar. */
+ PVDINTERFACEIOINT pIfIoXxx;
+
+
+ /** The VFS file handle for a DMG within a XAR archive. */
+ RTVFSFILE hDmgFileInXar;
+ /** XAR file system stream handle.
+ * Sitting on this isn't really necessary, but insurance against the XAR code
+ * changes making back references from child objects to the stream itself. */
+ RTVFSFSSTREAM hXarFss;
+
+ /** Flags the image was opened with. */
+ uint32_t uOpenFlags;
+ /** Image flags. */
+ unsigned uImageFlags;
+ /** Total size of the virtual image. */
+ uint64_t cbSize;
+ /** Size of the image. */
+ uint64_t cbFile;
+ /** Physical geometry of this image. */
+ VDGEOMETRY PCHSGeometry;
+ /** Logical geometry of this image. */
+ VDGEOMETRY LCHSGeometry;
+
+ /** The resources.
+ * A lazy bird ASSUME there are only two arrays in the resource-fork section in
+ * the XML, namely 'blkx' and 'plst'. These have been assigned fixed indexes. */
+ DMGUDIFRSRCARRAY aRsrcs[2];
+ /** The UDIF footer. */
+ DMGUDIF Ftr;
+
+ /** Number of valid extents in the array. */
+ unsigned cExtents;
+ /** Number of entries the array can hold. */
+ unsigned cExtentsMax;
+ /** Pointer to the extent array. */
+ PDMGEXTENT paExtents;
+ /** Index of the last accessed extent. */
+ unsigned idxExtentLast;
+
+ /** Extent which owns the data in the buffer. */
+ PDMGEXTENT pExtentDecomp;
+ /** Buffer holding the decompressed data for a extent. */
+ void *pvDecompExtent;
+ /** Size of the buffer. */
+ size_t cbDecompExtent;
+ /** The static region list. */
+ VDREGIONLIST RegionList;
+} DMGIMAGE;
+/** Pointer to an instance of the DMG Image Interpreter. */
+typedef DMGIMAGE *PDMGIMAGE;
+
+/** @name Resources indexes (into DMG::aRsrcs).
+ * @{ */
+#define DMG_RSRC_IDX_BLKX 0
+#define DMG_RSRC_IDX_PLST 1
+/** @} */
+
+/** State for the input callout of the inflate reader. */
+typedef struct DMGINFLATESTATE
+{
+ /* Image this operation relates to. */
+ PDMGIMAGE pImage;
+ /* Total size of the data to read. */
+ size_t cbSize;
+ /* Offset in the file to read. */
+ uint64_t uFileOffset;
+ /* Current read position. */
+ ssize_t iOffset;
+} DMGINFLATESTATE;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @def DMG_PRINTF
+ * Wrapper for LogRel.
+ */
+#define DMG_PRINTF(a) LogRel(a)
+
+/** @def DMG_VALIDATE
+ * For validating a struct thing and log/print what's wrong.
+ */
+# define DMG_VALIDATE(expr, logstuff) \
+ do { \
+ if (!(expr)) \
+ { \
+ LogRel(("DMG: validation failed: %s\nDMG: ", #expr)); \
+ LogRel(logstuff); \
+ fRc = false; \
+ } \
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/** NULL-terminated array of supported file extensions. */
+static const VDFILEEXTENSION s_aDmgFileExtensions[] =
+{
+ {"dmg", VDTYPE_OPTICAL_DISC},
+ {NULL, VDTYPE_INVALID}
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#if 0 /* unused */
+static void dmgUdifFtrHost2FileEndian(PDMGUDIF pUdif);
+#endif
+static void dmgUdifFtrFile2HostEndian(PDMGUDIF pUdif);
+
+static void dmgUdifIdHost2FileEndian(PDMGUDIFID pId);
+static void dmgUdifIdFile2HostEndian(PDMGUDIFID pId);
+
+#if 0 /* unused */
+static void dmgUdifCkSumHost2FileEndian(PDMGUDIFCKSUM pCkSum);
+#endif
+static void dmgUdifCkSumFile2HostEndian(PDMGUDIFCKSUM pCkSum);
+static bool dmgUdifCkSumIsValid(PCDMGUDIFCKSUM pCkSum, const char *pszPrefix);
+
+
+
+/**
+ * vdIfIoIntFileReadSync / RTVfsFileReadAt wrapper.
+ */
+static int dmgWrapFileReadSync(PDMGIMAGE pThis, RTFOFF off, void *pvBuf, size_t cbToRead)
+{
+ int rc;
+ if (pThis->hDmgFileInXar == NIL_RTVFSFILE)
+ rc = vdIfIoIntFileReadSync(pThis->pIfIoXxx, pThis->pStorage, off, pvBuf, cbToRead);
+ else
+ rc = RTVfsFileReadAt(pThis->hDmgFileInXar, off, pvBuf, cbToRead, NULL);
+ return rc;
+}
+
+/**
+ * vdIfIoIntFileReadUser / RTVfsFileReadAt wrapper.
+ */
+static int dmgWrapFileReadUser(PDMGIMAGE pThis, RTFOFF off, PVDIOCTX pIoCtx, size_t cbToRead)
+{
+ int rc;
+ if (pThis->hDmgFileInXar == NIL_RTVFSFILE)
+ rc = vdIfIoIntFileReadUser(pThis->pIfIoXxx, pThis->pStorage, off, pIoCtx, cbToRead);
+ else
+ {
+ /*
+ * Alloate a temporary buffer on the stack or heap and use
+ * vdIfIoIntIoCtxCopyTo to work the context.
+ *
+ * The I/O context stuff seems too complicated and undocument that I'm
+ * not going to bother trying to implement this efficiently right now.
+ */
+ void *pvFree = NULL;
+ void *pvBuf;
+ if (cbToRead < _32K)
+ pvBuf = alloca(cbToRead);
+ else
+ pvFree = pvBuf = RTMemTmpAlloc(cbToRead);
+ if (pvBuf)
+ {
+ rc = RTVfsFileReadAt(pThis->hDmgFileInXar, off, pvBuf, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ vdIfIoIntIoCtxCopyTo(pThis->pIfIoXxx, pIoCtx, pvBuf, cbToRead);
+ if (pvFree)
+ RTMemTmpFree(pvFree);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ }
+ return rc;
+}
+
+/**
+ * vdIfIoIntFileGetSize / RTVfsFileQuerySize wrapper.
+ */
+static int dmgWrapFileGetSize(PDMGIMAGE pThis, uint64_t *pcbFile)
+{
+ int rc;
+ if (pThis->hDmgFileInXar == NIL_RTVFSFILE)
+ rc = vdIfIoIntFileGetSize(pThis->pIfIoXxx, pThis->pStorage, pcbFile);
+ else
+ rc = RTVfsFileQuerySize(pThis->hDmgFileInXar, pcbFile);
+ return rc;
+}
+
+
+
+static DECLCALLBACK(int) dmgFileInflateHelper(void *pvUser, void *pvBuf, size_t cbBuf, size_t *pcbBuf)
+{
+ DMGINFLATESTATE *pInflateState = (DMGINFLATESTATE *)pvUser;
+
+ Assert(cbBuf);
+ if (pInflateState->iOffset < 0)
+ {
+ *(uint8_t *)pvBuf = RTZIPTYPE_ZLIB;
+ if (pcbBuf)
+ *pcbBuf = 1;
+ pInflateState->iOffset = 0;
+ return VINF_SUCCESS;
+ }
+ cbBuf = RT_MIN(cbBuf, pInflateState->cbSize);
+ int rc = dmgWrapFileReadSync(pInflateState->pImage, pInflateState->uFileOffset, pvBuf, cbBuf);
+ if (RT_FAILURE(rc))
+ return rc;
+ pInflateState->uFileOffset += cbBuf;
+ pInflateState->iOffset += cbBuf;
+ pInflateState->cbSize -= cbBuf;
+ Assert(pcbBuf);
+ *pcbBuf = cbBuf;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Internal: read from a file and inflate the compressed data,
+ * distinguishing between async and normal operation
+ */
+DECLINLINE(int) dmgFileInflateSync(PDMGIMAGE pImage, uint64_t uOffset, size_t cbToRead,
+ void *pvBuf, size_t cbBuf)
+{
+ int rc;
+ PRTZIPDECOMP pZip = NULL;
+ DMGINFLATESTATE InflateState;
+ size_t cbActuallyRead;
+
+ InflateState.pImage = pImage;
+ InflateState.cbSize = cbToRead;
+ InflateState.uFileOffset = uOffset;
+ InflateState.iOffset = -1;
+
+ rc = RTZipDecompCreate(&pZip, &InflateState, dmgFileInflateHelper);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = RTZipDecompress(pZip, pvBuf, cbBuf, &cbActuallyRead);
+ RTZipDecompDestroy(pZip);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (cbActuallyRead != cbBuf)
+ rc = VERR_VD_VMDK_INVALID_FORMAT;
+ return rc;
+}
+
+/**
+ * Swaps endian.
+ * @param pUdif The structure.
+ */
+static void dmgSwapEndianUdif(PDMGUDIF pUdif)
+{
+#ifndef RT_BIG_ENDIAN
+ pUdif->u32Magic = RT_BSWAP_U32(pUdif->u32Magic);
+ pUdif->u32Version = RT_BSWAP_U32(pUdif->u32Version);
+ pUdif->cbFooter = RT_BSWAP_U32(pUdif->cbFooter);
+ pUdif->fFlags = RT_BSWAP_U32(pUdif->fFlags);
+ pUdif->offRunData = RT_BSWAP_U64(pUdif->offRunData);
+ pUdif->offData = RT_BSWAP_U64(pUdif->offData);
+ pUdif->cbData = RT_BSWAP_U64(pUdif->cbData);
+ pUdif->offRsrc = RT_BSWAP_U64(pUdif->offRsrc);
+ pUdif->cbRsrc = RT_BSWAP_U64(pUdif->cbRsrc);
+ pUdif->iSegment = RT_BSWAP_U32(pUdif->iSegment);
+ pUdif->cSegments = RT_BSWAP_U32(pUdif->cSegments);
+ pUdif->offXml = RT_BSWAP_U64(pUdif->offXml);
+ pUdif->cbXml = RT_BSWAP_U64(pUdif->cbXml);
+ pUdif->u32Type = RT_BSWAP_U32(pUdif->u32Type);
+ pUdif->cSectors = RT_BSWAP_U64(pUdif->cSectors);
+#endif
+}
+
+
+#if 0 /* unused */
+/**
+ * Swaps endian from host cpu to file.
+ * @param pUdif The structure.
+ */
+static void dmgUdifFtrHost2FileEndian(PDMGUDIF pUdif)
+{
+ dmgSwapEndianUdif(pUdif);
+ dmgUdifIdHost2FileEndian(&pUdif->SegmentId);
+ dmgUdifCkSumHost2FileEndian(&pUdif->DataCkSum);
+ dmgUdifCkSumHost2FileEndian(&pUdif->MasterCkSum);
+}
+#endif
+
+
+/**
+ * Swaps endian from file to host cpu.
+ * @param pUdif The structure.
+ */
+static void dmgUdifFtrFile2HostEndian(PDMGUDIF pUdif)
+{
+ dmgSwapEndianUdif(pUdif);
+ dmgUdifIdFile2HostEndian(&pUdif->SegmentId);
+ dmgUdifCkSumFile2HostEndian(&pUdif->DataCkSum);
+ dmgUdifCkSumFile2HostEndian(&pUdif->MasterCkSum);
+}
+
+/**
+ * Swaps endian from file to host cpu.
+ * @param pBlkx The blkx structure.
+ */
+static void dmgBlkxFile2HostEndian(PDMGBLKX pBlkx)
+{
+ pBlkx->u32Magic = RT_BE2H_U32(pBlkx->u32Magic);
+ pBlkx->u32Version = RT_BE2H_U32(pBlkx->u32Version);
+ pBlkx->cSectornumberFirst = RT_BE2H_U64(pBlkx->cSectornumberFirst);
+ pBlkx->cSectors = RT_BE2H_U64(pBlkx->cSectors);
+ pBlkx->offDataStart = RT_BE2H_U64(pBlkx->offDataStart);
+ pBlkx->cSectorsDecompress = RT_BE2H_U32(pBlkx->cSectorsDecompress);
+ pBlkx->u32BlocksDescriptor = RT_BE2H_U32(pBlkx->u32BlocksDescriptor);
+ pBlkx->cBlocksRunCount = RT_BE2H_U32(pBlkx->cBlocksRunCount);
+ dmgUdifCkSumFile2HostEndian(&pBlkx->BlkxCkSum);
+}
+
+/**
+ * Swaps endian from file to host cpu.
+ * @param pBlkxDesc The blkx descriptor structure.
+ */
+static void dmgBlkxDescFile2HostEndian(PDMGBLKXDESC pBlkxDesc)
+{
+ pBlkxDesc->u32Type = RT_BE2H_U32(pBlkxDesc->u32Type);
+ pBlkxDesc->u32Reserved = RT_BE2H_U32(pBlkxDesc->u32Reserved);
+ pBlkxDesc->u64SectorStart = RT_BE2H_U64(pBlkxDesc->u64SectorStart);
+ pBlkxDesc->u64SectorCount = RT_BE2H_U64(pBlkxDesc->u64SectorCount);
+ pBlkxDesc->offData = RT_BE2H_U64(pBlkxDesc->offData);
+ pBlkxDesc->cbData = RT_BE2H_U64(pBlkxDesc->cbData);
+}
+
+/**
+ * Validates an UDIF footer structure.
+ *
+ * @returns true if valid, false and LogRel()s on failure.
+ * @param pFtr The UDIF footer to validate.
+ * @param offFtr The offset of the structure.
+ */
+static bool dmgUdifFtrIsValid(PCDMGUDIF pFtr, uint64_t offFtr)
+{
+ bool fRc = true;
+
+ DMG_VALIDATE(!(pFtr->fFlags & ~DMGUDIF_FLAGS_KNOWN_MASK), ("fFlags=%#RX32 fKnown=%RX32\n", pFtr->fFlags, DMGUDIF_FLAGS_KNOWN_MASK));
+ DMG_VALIDATE(pFtr->offRunData < offFtr, ("offRunData=%#RX64\n", pFtr->offRunData));
+ DMG_VALIDATE(pFtr->cbData <= offFtr && pFtr->offData + pFtr->cbData <= offFtr, ("cbData=%#RX64 offData=%#RX64 offFtr=%#RX64\n", pFtr->cbData, pFtr->offData, offFtr));
+ DMG_VALIDATE(pFtr->offData < offFtr, ("offData=%#RX64\n", pFtr->offData));
+ DMG_VALIDATE(pFtr->cbRsrc <= offFtr && pFtr->offRsrc + pFtr->cbRsrc <= offFtr, ("cbRsrc=%#RX64 offRsrc=%#RX64 offFtr=%#RX64\n", pFtr->cbRsrc, pFtr->offRsrc, offFtr));
+ DMG_VALIDATE(pFtr->offRsrc < offFtr, ("offRsrc=%#RX64\n", pFtr->offRsrc));
+ DMG_VALIDATE(pFtr->cSegments <= 1, ("cSegments=%RU32\n", pFtr->cSegments));
+ DMG_VALIDATE(pFtr->iSegment == 0 || pFtr->iSegment == 1, ("iSegment=%RU32 cSegments=%RU32\n", pFtr->iSegment, pFtr->cSegments));
+ DMG_VALIDATE(pFtr->cbXml <= offFtr && pFtr->offXml + pFtr->cbXml <= offFtr, ("cbXml=%#RX64 offXml=%#RX64 offFtr=%#RX64\n", pFtr->cbXml, pFtr->offXml, offFtr));
+ DMG_VALIDATE(pFtr->offXml < offFtr, ("offXml=%#RX64\n", pFtr->offXml));
+ DMG_VALIDATE(pFtr->cbXml > 128, ("cbXml=%#RX64\n", pFtr->cbXml));
+ DMG_VALIDATE(pFtr->cbXml < 10 * _1M, ("cbXml=%#RX64\n", pFtr->cbXml));
+ DMG_VALIDATE(pFtr->u32Type == DMGUDIF_TYPE_DEVICE || pFtr->u32Type == DMGUDIF_TYPE_PARTITION, ("u32Type=%RU32\n", pFtr->u32Type));
+ DMG_VALIDATE(pFtr->cSectors != 0, ("cSectors=%#RX64\n", pFtr->cSectors));
+ fRc &= dmgUdifCkSumIsValid(&pFtr->DataCkSum, "DataCkSum");
+ fRc &= dmgUdifCkSumIsValid(&pFtr->MasterCkSum, "MasterCkSum");
+
+ return fRc;
+}
+
+
+static bool dmgBlkxIsValid(PCDMGBLKX pBlkx)
+{
+ bool fRc = true;
+
+ fRc &= dmgUdifCkSumIsValid(&pBlkx->BlkxCkSum, "BlkxCkSum");
+ DMG_VALIDATE(pBlkx->u32Magic == DMGBLKX_MAGIC, ("u32Magic=%#RX32 u32MagicExpected=%#RX32\n", pBlkx->u32Magic, DMGBLKX_MAGIC));
+ DMG_VALIDATE(pBlkx->u32Version == DMGBLKX_VERSION, ("u32Version=%#RX32 u32VersionExpected=%#RX32\n", pBlkx->u32Magic, DMGBLKX_VERSION));
+
+ return fRc;
+}
+
+/**
+ * Swaps endian from host cpu to file.
+ * @param pId The structure.
+ */
+static void dmgUdifIdHost2FileEndian(PDMGUDIFID pId)
+{
+ NOREF(pId);
+}
+
+
+/**
+ * Swaps endian from file to host cpu.
+ * @param pId The structure.
+ */
+static void dmgUdifIdFile2HostEndian(PDMGUDIFID pId)
+{
+ dmgUdifIdHost2FileEndian(pId);
+}
+
+
+/**
+ * Swaps endian.
+ * @param pCkSum The structure.
+ * @param u32Kind Kind of the checksum (CRC32, none)
+ * @param cBits Size of the checksum in bits.
+ */
+static void dmgSwapEndianUdifCkSum(PDMGUDIFCKSUM pCkSum, uint32_t u32Kind, uint32_t cBits)
+{
+#ifdef RT_BIG_ENDIAN
+ NOREF(pCkSum);
+ NOREF(u32Kind);
+ NOREF(cBits);
+#else
+ switch (u32Kind)
+ {
+ case DMGUDIFCKSUM_NONE:
+ /* nothing to do here */
+ break;
+
+ case DMGUDIFCKSUM_CRC32:
+ Assert(cBits == 32);
+ pCkSum->u32Kind = RT_BSWAP_U32(pCkSum->u32Kind);
+ pCkSum->cBits = RT_BSWAP_U32(pCkSum->cBits);
+ pCkSum->uSum.au32[0] = RT_BSWAP_U32(pCkSum->uSum.au32[0]);
+ break;
+
+ default:
+ AssertMsgFailed(("%x\n", u32Kind));
+ break;
+ }
+ NOREF(cBits);
+#endif
+}
+
+
+#if 0 /* unused */
+/**
+ * Swaps endian from host cpu to file.
+ * @param pCkSum The structure.
+ */
+static void dmgUdifCkSumHost2FileEndian(PDMGUDIFCKSUM pCkSum)
+{
+ dmgSwapEndianUdifCkSum(pCkSum, pCkSum->u32Kind, pCkSum->cBits);
+}
+#endif
+
+
+/**
+ * Swaps endian from file to host cpu.
+ * @param pCkSum The structure.
+ */
+static void dmgUdifCkSumFile2HostEndian(PDMGUDIFCKSUM pCkSum)
+{
+ dmgSwapEndianUdifCkSum(pCkSum, RT_BE2H_U32(pCkSum->u32Kind), RT_BE2H_U32(pCkSum->cBits));
+}
+
+
+/**
+ * Validates an UDIF checksum structure.
+ *
+ * @returns true if valid, false and LogRel()s on failure.
+ * @param pCkSum The checksum structure.
+ * @param pszPrefix The message prefix.
+ * @remarks This does not check the checksummed data.
+ */
+static bool dmgUdifCkSumIsValid(PCDMGUDIFCKSUM pCkSum, const char *pszPrefix)
+{
+ bool fRc = true;
+
+ switch (pCkSum->u32Kind)
+ {
+ case DMGUDIFCKSUM_NONE:
+ DMG_VALIDATE(pCkSum->cBits == 0, ("%s/NONE: cBits=%d\n", pszPrefix, pCkSum->cBits));
+ break;
+
+ case DMGUDIFCKSUM_CRC32:
+ DMG_VALIDATE(pCkSum->cBits == 32, ("%s/NONE: cBits=%d\n", pszPrefix, pCkSum->cBits));
+ break;
+
+ default:
+ DMG_VALIDATE(0, ("%s: u32Kind=%#RX32\n", pszPrefix, pCkSum->u32Kind));
+ break;
+ }
+ return fRc;
+}
+
+
+/**
+ * Internal. Flush image data to disk.
+ */
+static int dmgFlushImage(PDMGIMAGE pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ if ( pThis
+ && (pThis->pStorage || pThis->hDmgFileInXar != NIL_RTVFSFILE)
+ && !(pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ {
+ /** @todo handle writable files, update checksums etc. */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Internal. Free all allocated space for representing an image except pThis,
+ * and optionally delete the image from disk.
+ */
+static int dmgFreeImage(PDMGIMAGE pThis, 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 (pThis)
+ {
+ RTVfsFileRelease(pThis->hDmgFileInXar);
+ pThis->hDmgFileInXar = NIL_RTVFSFILE;
+
+ RTVfsFsStrmRelease(pThis->hXarFss);
+ pThis->hXarFss = NIL_RTVFSFSSTREAM;
+
+ if (pThis->pStorage)
+ {
+ /* No point updating the file that is deleted anyway. */
+ if (!fDelete)
+ dmgFlushImage(pThis);
+
+ rc = vdIfIoIntFileClose(pThis->pIfIoXxx, pThis->pStorage);
+ pThis->pStorage = NULL;
+ }
+
+ for (unsigned iRsrc = 0; iRsrc < RT_ELEMENTS(pThis->aRsrcs); iRsrc++)
+ for (unsigned i = 0; i < pThis->aRsrcs[iRsrc].cEntries; i++)
+ {
+ if (pThis->aRsrcs[iRsrc].aEntries[i].pbData)
+ {
+ RTMemFree(pThis->aRsrcs[iRsrc].aEntries[i].pbData);
+ pThis->aRsrcs[iRsrc].aEntries[i].pbData = NULL;
+ }
+ if (pThis->aRsrcs[iRsrc].aEntries[i].pszName)
+ {
+ RTMemFree(pThis->aRsrcs[iRsrc].aEntries[i].pszName);
+ pThis->aRsrcs[iRsrc].aEntries[i].pszName = NULL;
+ }
+ if (pThis->aRsrcs[iRsrc].aEntries[i].pszCFName)
+ {
+ RTMemFree(pThis->aRsrcs[iRsrc].aEntries[i].pszCFName);
+ pThis->aRsrcs[iRsrc].aEntries[i].pszCFName = NULL;
+ }
+ }
+
+ if (fDelete && pThis->pszFilename)
+ vdIfIoIntFileDelete(pThis->pIfIoXxx, pThis->pszFilename);
+
+ if (pThis->pvDecompExtent)
+ {
+ RTMemFree(pThis->pvDecompExtent);
+ pThis->pvDecompExtent = NULL;
+ pThis->cbDecompExtent = 0;
+ }
+
+ if (pThis->paExtents)
+ {
+ RTMemFree(pThis->paExtents);
+ pThis->paExtents = NULL;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+#define STARTS_WITH(pszString, szStart) \
+ ( strncmp(pszString, szStart, sizeof(szStart) - 1) == 0 )
+
+#define STARTS_WITH_WORD(pszString, szWord) \
+ ( STARTS_WITH(pszString, szWord) \
+ && !RT_C_IS_ALNUM((pszString)[sizeof(szWord) - 1]) )
+
+#define SKIP_AHEAD(psz, szWord) \
+ do { \
+ (psz) = RTStrStripL((psz) + sizeof(szWord) - 1); \
+ } while (0)
+
+#define REQUIRE_WORD(psz, szWord) \
+ do { \
+ if (!STARTS_WITH_WORD(psz, szWord)) \
+ return psz; \
+ (psz) = RTStrStripL((psz) + sizeof(szWord) - 1); \
+ } while (0)
+
+#define REQUIRE_TAG(psz, szTag) \
+ do { \
+ if (!STARTS_WITH(psz, "<" szTag ">")) \
+ return psz; \
+ (psz) = RTStrStripL((psz) + sizeof("<" szTag ">") - 1); \
+ } while (0)
+
+#define REQUIRE_TAG_NO_STRIP(psz, szTag) \
+ do { \
+ if (!STARTS_WITH(psz, "<" szTag ">")) \
+ return psz; \
+ (psz) += sizeof("<" szTag ">") - 1; \
+ } while (0)
+
+#define REQUIRE_END_TAG(psz, szTag) \
+ do { \
+ if (!STARTS_WITH(psz, "</" szTag ">")) \
+ return psz; \
+ (psz) = RTStrStripL((psz) + sizeof("</" szTag ">") - 1); \
+ } while (0)
+
+
+/**
+ * Finds the next tag end.
+ *
+ * @returns Pointer to a '>' or '\0'.
+ * @param pszCur The current position.
+ */
+static const char *dmgXmlFindTagEnd(const char *pszCur)
+{
+ /* Might want to take quoted '>' into account? */
+ char ch;
+ while ((ch = *pszCur) != '\0' && ch != '>')
+ pszCur++;
+ return pszCur;
+}
+
+
+/**
+ * Finds the end tag.
+ *
+ * Does not deal with @verbatim<tag attr="1"/>@endverbatim style tags.
+ *
+ * @returns Pointer to the first char in the end tag. NULL if another tag
+ * was encountered first or if we hit the end of the file.
+ * @param ppszCur The current position (IN/OUT).
+ * @param pszTag The tag name.
+ */
+static const char *dmgXmlFindEndTag(const char **ppszCur, const char *pszTag)
+{
+ const char *psz = *ppszCur;
+ char ch;
+ while ((ch = *psz))
+ {
+ if (ch == '<')
+ {
+ size_t const cchTag = strlen(pszTag);
+ if ( psz[1] == '/'
+ && !memcmp(&psz[2], pszTag, cchTag)
+ && psz[2 + cchTag] == '>')
+ {
+ *ppszCur = psz + 2 + cchTag + 1;
+ return psz;
+ }
+ break;
+ }
+ psz++;
+ }
+ return NULL;
+}
+
+
+/**
+ * Reads a signed 32-bit value.
+ *
+ * @returns NULL on success, pointer to the offending text on failure.
+ * @param ppszCur The text position (IN/OUT).
+ * @param pi32 Where to store the value.
+ */
+static const char *dmgXmlParseS32(const char **ppszCur, int32_t *pi32)
+{
+ const char *psz = *ppszCur;
+
+ /*
+ * <string>-1</string>
+ */
+ REQUIRE_TAG_NO_STRIP(psz, "string");
+
+ char *pszNext;
+ int rc = RTStrToInt32Ex(psz, &pszNext, 0, pi32);
+ if (rc != VWRN_TRAILING_CHARS)
+ return *ppszCur;
+ psz = pszNext;
+
+ REQUIRE_END_TAG(psz, "string");
+ *ppszCur = psz;
+ return NULL;
+}
+
+
+/**
+ * Reads an unsigned 32-bit value.
+ *
+ * @returns NULL on success, pointer to the offending text on failure.
+ * @param ppszCur The text position (IN/OUT).
+ * @param pu32 Where to store the value.
+ */
+static const char *dmgXmlParseU32(const char **ppszCur, uint32_t *pu32)
+{
+ const char *psz = *ppszCur;
+
+ /*
+ * <string>0x00ff</string>
+ */
+ REQUIRE_TAG_NO_STRIP(psz, "string");
+
+ char *pszNext;
+ int rc = RTStrToUInt32Ex(psz, &pszNext, 0, pu32);
+ if (rc != VWRN_TRAILING_CHARS)
+ return *ppszCur;
+ psz = pszNext;
+
+ REQUIRE_END_TAG(psz, "string");
+ *ppszCur = psz;
+ return NULL;
+}
+
+
+/**
+ * Reads a string value.
+ *
+ * @returns NULL on success, pointer to the offending text on failure.
+ * @param ppszCur The text position (IN/OUT).
+ * @param ppszString Where to store the pointer to the string. The caller
+ * must free this using RTMemFree.
+ */
+static const char *dmgXmlParseString(const char **ppszCur, char **ppszString)
+{
+ const char *psz = *ppszCur;
+
+ /*
+ * <string>Driver Descriptor Map (DDM : 0)</string>
+ */
+ REQUIRE_TAG_NO_STRIP(psz, "string");
+
+ const char *pszStart = psz;
+ const char *pszEnd = dmgXmlFindEndTag(&psz, "string");
+ if (!pszEnd)
+ return *ppszCur;
+ psz = RTStrStripL(psz);
+
+ *ppszString = (char *)RTMemDupEx(pszStart, pszEnd - pszStart, 1);
+ if (!*ppszString)
+ return *ppszCur;
+
+ *ppszCur = psz;
+ return NULL;
+}
+
+
+/**
+ * Parses the BASE-64 coded data tags.
+ *
+ * @returns NULL on success, pointer to the offending text on failure.
+ * @param ppszCur The text position (IN/OUT).
+ * @param ppbData Where to store the pointer to the data we've read. The
+ * caller must free this using RTMemFree.
+ * @param pcbData The number of bytes we're returning.
+ */
+static const char *dmgXmlParseData(const char **ppszCur, uint8_t **ppbData, size_t *pcbData)
+{
+ const char *psz = *ppszCur;
+
+ /*
+ * <data> AAAAA... </data>
+ */
+ REQUIRE_TAG(psz, "data");
+
+ const char *pszStart = psz;
+ ssize_t cbData = RTBase64DecodedSize(pszStart, (char **)&psz);
+ if (cbData == -1)
+ return *ppszCur;
+
+ REQUIRE_END_TAG(psz, "data");
+
+ *ppbData = (uint8_t *)RTMemAlloc(cbData);
+ if (!*ppbData)
+ return *ppszCur;
+ char *pszIgnored;
+ int rc = RTBase64Decode(pszStart, *ppbData, cbData, pcbData, &pszIgnored);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(*ppbData);
+ *ppbData = NULL;
+ return *ppszCur;
+ }
+
+ *ppszCur = psz;
+ return NULL;
+}
+
+
+/**
+ * Parses the XML resource-fork in a rather presumptive manner.
+ *
+ * This function is supposed to construct the DMG::aRsrcs instance data
+ * parts.
+ *
+ * @returns NULL on success, pointer to the problematic text on failure.
+ * @param pThis The DMG instance data.
+ * @param pszXml The XML text to parse, UTF-8.
+ */
+static const char *dmgOpenXmlToRsrc(PDMGIMAGE pThis, char const *pszXml)
+{
+ const char *psz = pszXml;
+
+ /*
+ * Verify the ?xml, !DOCTYPE and plist tags.
+ */
+ SKIP_AHEAD(psz, "");
+
+ /* <?xml version="1.0" encoding="UTF-8"?> */
+ REQUIRE_WORD(psz, "<?xml");
+ while (*psz != '?')
+ {
+ if (!*psz)
+ return psz;
+ if (STARTS_WITH_WORD(psz, "version="))
+ {
+ SKIP_AHEAD(psz, "version=");
+ REQUIRE_WORD(psz, "\"1.0\"");
+ }
+ else if (STARTS_WITH_WORD(psz, "encoding="))
+ {
+ SKIP_AHEAD(psz, "encoding=");
+ REQUIRE_WORD(psz, "\"UTF-8\"");
+ }
+ else
+ return psz;
+ }
+ SKIP_AHEAD(psz, "?>");
+
+ /* <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> */
+ REQUIRE_WORD(psz, "<!DOCTYPE");
+ REQUIRE_WORD(psz, "plist");
+ REQUIRE_WORD(psz, "PUBLIC");
+ psz = dmgXmlFindTagEnd(psz);
+ REQUIRE_WORD(psz, ">");
+
+ /* <plist version="1.0"> */
+ REQUIRE_WORD(psz, "<plist");
+ REQUIRE_WORD(psz, "version=");
+ REQUIRE_WORD(psz, "\"1.0\"");
+ REQUIRE_WORD(psz, ">");
+
+ /*
+ * Descend down to the 'resource-fork' dictionary.
+ * ASSUME it's the only top level dictionary.
+ */
+ /* <dict> <key>resource-fork</key> */
+ REQUIRE_TAG(psz, "dict");
+ REQUIRE_WORD(psz, "<key>resource-fork</key>");
+
+ /*
+ * Parse the keys in the resource-fork dictionary.
+ * ASSUME that there are just two, 'blkx' and 'plst'.
+ */
+ REQUIRE_TAG(psz, "dict");
+ while (!STARTS_WITH_WORD(psz, "</dict>"))
+ {
+ /*
+ * Parse the key and Create the resource-fork entry.
+ */
+ unsigned iRsrc;
+ if (STARTS_WITH_WORD(psz, "<key>blkx</key>"))
+ {
+ REQUIRE_WORD(psz, "<key>blkx</key>");
+ iRsrc = DMG_RSRC_IDX_BLKX;
+ strcpy(&pThis->aRsrcs[iRsrc].szName[0], "blkx");
+ }
+ else if (STARTS_WITH_WORD(psz, "<key>plst</key>"))
+ {
+ REQUIRE_WORD(psz, "<key>plst</key>");
+ iRsrc = DMG_RSRC_IDX_PLST;
+ strcpy(&pThis->aRsrcs[iRsrc].szName[0], "plst");
+ }
+ else
+ {
+ SKIP_AHEAD(psz, "</array>");
+ continue;
+ }
+
+
+ /*
+ * Descend into the array and add the elements to the resource entry.
+ */
+ /* <array> */
+ REQUIRE_TAG(psz, "array");
+ while (!STARTS_WITH_WORD(psz, "</array>"))
+ {
+ REQUIRE_TAG(psz, "dict");
+ uint32_t i = pThis->aRsrcs[iRsrc].cEntries;
+ if (i == RT_ELEMENTS(pThis->aRsrcs[iRsrc].aEntries))
+ return psz;
+
+ while (!STARTS_WITH_WORD(psz, "</dict>"))
+ {
+
+ /* switch on the key. */
+ const char *pszErr;
+ if (STARTS_WITH_WORD(psz, "<key>Attributes</key>"))
+ {
+ REQUIRE_WORD(psz, "<key>Attributes</key>");
+ pszErr = dmgXmlParseU32(&psz, &pThis->aRsrcs[iRsrc].aEntries[i].fAttributes);
+ }
+ else if (STARTS_WITH_WORD(psz, "<key>ID</key>"))
+ {
+ REQUIRE_WORD(psz, "<key>ID</key>");
+ pszErr = dmgXmlParseS32(&psz, &pThis->aRsrcs[iRsrc].aEntries[i].iId);
+ }
+ else if (STARTS_WITH_WORD(psz, "<key>Name</key>"))
+ {
+ REQUIRE_WORD(psz, "<key>Name</key>");
+ pszErr = dmgXmlParseString(&psz, &pThis->aRsrcs[iRsrc].aEntries[i].pszName);
+ }
+ else if (STARTS_WITH_WORD(psz, "<key>CFName</key>"))
+ {
+ REQUIRE_WORD(psz, "<key>CFName</key>");
+ pszErr = dmgXmlParseString(&psz, &pThis->aRsrcs[iRsrc].aEntries[i].pszCFName);
+ }
+ else if (STARTS_WITH_WORD(psz, "<key>Data</key>"))
+ {
+ REQUIRE_WORD(psz, "<key>Data</key>");
+ pszErr = dmgXmlParseData(&psz, &pThis->aRsrcs[iRsrc].aEntries[i].pbData, &pThis->aRsrcs[iRsrc].aEntries[i].cbData);
+ }
+ else
+ pszErr = psz;
+ if (pszErr)
+ return pszErr;
+ } /* while not </dict> */
+ REQUIRE_END_TAG(psz, "dict");
+
+ pThis->aRsrcs[iRsrc].cEntries++;
+ } /* while not </array> */
+ REQUIRE_END_TAG(psz, "array");
+
+ } /* while not </dict> */
+ REQUIRE_END_TAG(psz, "dict");
+
+ /*
+ * ASSUMING there is only the 'resource-fork', we'll now see the end of
+ * the outer dict, plist and text.
+ */
+ /* </dict> </plist> */
+ REQUIRE_END_TAG(psz, "dict");
+ REQUIRE_END_TAG(psz, "plist");
+
+ /* the end */
+ if (*psz)
+ return psz;
+
+ return NULL;
+}
+
+#undef REQUIRE_END_TAG
+#undef REQUIRE_TAG_NO_STRIP
+#undef REQUIRE_TAG
+#undef REQUIRE_WORD
+#undef SKIP_AHEAD
+#undef STARTS_WITH_WORD
+#undef STARTS_WITH
+
+/**
+ * Returns the data attached to a resource.
+ *
+ * @returns VBox status code.
+ * @param pThis The DMG instance data.
+ * @param pcszRsrcName Name of the resource to get.
+ * @param ppcRsrc Where to store the pointer to the resource data on success.
+ */
+static int dmgGetRsrcData(PDMGIMAGE pThis, const char *pcszRsrcName,
+ PCDMGUDIFRSRCARRAY *ppcRsrc)
+{
+ int rc = VERR_NOT_FOUND;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aRsrcs); i++)
+ {
+ if (!strcmp(pThis->aRsrcs[i].szName, pcszRsrcName))
+ {
+ *ppcRsrc = &pThis->aRsrcs[i];
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Creates a new extent from the given blkx descriptor.
+ *
+ * @returns VBox status code.
+ * @param pThis DMG instance data.
+ * @param uSectorPart First sector the partition owning the blkx descriptor has.
+ * @param pBlkxDesc The blkx descriptor.
+ */
+static int dmgExtentCreateFromBlkxDesc(PDMGIMAGE pThis, uint64_t uSectorPart, PDMGBLKXDESC pBlkxDesc)
+{
+ int rc = VINF_SUCCESS;
+ DMGEXTENTTYPE enmExtentTypeNew;
+ PDMGEXTENT pExtentNew = NULL;
+
+ if (pBlkxDesc->u32Type == DMGBLKXDESC_TYPE_RAW)
+ enmExtentTypeNew = DMGEXTENTTYPE_RAW;
+ else if (pBlkxDesc->u32Type == DMGBLKXDESC_TYPE_IGNORE)
+ enmExtentTypeNew = DMGEXTENTTYPE_ZERO;
+ else if (pBlkxDesc->u32Type == DMGBLKXDESC_TYPE_ZLIB)
+ enmExtentTypeNew = DMGEXTENTTYPE_COMP_ZLIB;
+ else
+ {
+ AssertMsgFailed(("This method supports only raw or zero extents!\n"));
+ return VERR_NOT_SUPPORTED;
+ }
+
+ /** @todo Merge raw extents if possible to save memory. */
+#if 0
+ pExtentNew = pThis->pExtentLast;
+ if ( pExtentNew
+ && pExtentNew->enmType == enmExtentTypeNew
+ && enmExtentTypeNew == DMGEXTENTTYPE_RAW
+ && pExtentNew->uSectorExtent + pExtentNew->cSectorsExtent == offDevice + pBlkxDesc->u64SectorStart * DMG_SECTOR_SIZE;
+ && pExtentNew->offFileStart + pExtentNew->cbExtent == pBlkxDesc->offData)
+ {
+ /* Increase the last extent. */
+ pExtentNew->cbExtent += pBlkxDesc->cbData;
+ }
+ else
+#endif
+ {
+ if (pThis->cExtentsMax == pThis->cExtents)
+ {
+ pThis->cExtentsMax += 64;
+
+ /* Increase the array. */
+ PDMGEXTENT paExtentsNew = (PDMGEXTENT)RTMemRealloc(pThis->paExtents, sizeof(DMGEXTENT) * pThis->cExtentsMax);
+ if (!paExtentsNew)
+ {
+ rc = VERR_NO_MEMORY;
+ pThis->cExtentsMax -= 64;
+ }
+ else
+ pThis->paExtents = paExtentsNew;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pExtentNew = &pThis->paExtents[pThis->cExtents++];
+
+ pExtentNew->enmType = enmExtentTypeNew;
+ pExtentNew->uSectorExtent = uSectorPart + pBlkxDesc->u64SectorStart;
+ pExtentNew->cSectorsExtent = pBlkxDesc->u64SectorCount;
+ pExtentNew->offFileStart = pBlkxDesc->offData;
+ pExtentNew->cbFile = pBlkxDesc->cbData;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Find the extent for the given sector number.
+ */
+static PDMGEXTENT dmgExtentGetFromOffset(PDMGIMAGE pThis, uint64_t uSector)
+{
+ /*
+ * We assume that the array is ordered from lower to higher sector
+ * numbers.
+ * This makes it possible to bisect the array to find the extent
+ * faster than using a linked list.
+ */
+ PDMGEXTENT pExtent = NULL;
+ unsigned idxCur = pThis->idxExtentLast;
+ unsigned idxMax = pThis->cExtents;
+ unsigned idxMin = 0;
+
+ while (idxMin < idxMax)
+ {
+ PDMGEXTENT pExtentCur = &pThis->paExtents[idxCur];
+
+ /* Determine the search direction. */
+ if (uSector < pExtentCur->uSectorExtent)
+ {
+ /* Search left from the current extent. */
+ idxMax = idxCur;
+ }
+ else if (uSector >= pExtentCur->uSectorExtent + pExtentCur->cSectorsExtent)
+ {
+ /* Search right from the current extent. */
+ idxMin = idxCur;
+ }
+ else
+ {
+ /* The sector lies in the extent, stop searching. */
+ pExtent = pExtentCur;
+ break;
+ }
+
+ idxCur = idxMin + (idxMax - idxMin) / 2;
+ }
+
+ if (pExtent)
+ pThis->idxExtentLast = idxCur;
+
+ return pExtent;
+}
+
+/**
+ * Goes through the BLKX structure and creates the necessary extents.
+ */
+static int dmgBlkxParse(PDMGIMAGE pThis, PDMGBLKX pBlkx)
+{
+ int rc = VINF_SUCCESS;
+ PDMGBLKXDESC pBlkxDesc = (PDMGBLKXDESC)(pBlkx + 1);
+
+ for (unsigned i = 0; i < pBlkx->cBlocksRunCount; i++)
+ {
+ dmgBlkxDescFile2HostEndian(pBlkxDesc);
+
+ switch (pBlkxDesc->u32Type)
+ {
+ case DMGBLKXDESC_TYPE_RAW:
+ case DMGBLKXDESC_TYPE_IGNORE:
+ case DMGBLKXDESC_TYPE_ZLIB:
+ {
+ rc = dmgExtentCreateFromBlkxDesc(pThis, pBlkx->cSectornumberFirst, pBlkxDesc);
+ break;
+ }
+ case DMGBLKXDESC_TYPE_COMMENT:
+ case DMGBLKXDESC_TYPE_TERMINATOR:
+ break;
+ default:
+ rc = VERR_VD_DMG_INVALID_HEADER;
+ break;
+ }
+
+ if ( pBlkxDesc->u32Type == DMGBLKXDESC_TYPE_TERMINATOR
+ || RT_FAILURE(rc))
+ break;
+
+ pBlkxDesc++;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Worker for dmgOpenImage that tries to open a DMG inside a XAR file.
+ *
+ * We'll select the first .dmg inside the archive that we can get a file
+ * interface to.
+ *
+ * @returns VBox status code.
+ * @param fOpen Flags for defining the open type.
+ * @param pVDIfIoInt The internal VD I/O interface to use.
+ * @param pvStorage The storage pointer that goes with @a pVDIfsIo.
+ * @param pszFilename The input filename, optional.
+ * @param phXarFss Where to return the XAR file system stream handle on
+ * success
+ * @param phDmgFileInXar Where to return the VFS handle to the DMG file
+ * within the XAR image on success.
+ *
+ * @remarks Not using the PDMGIMAGE structure directly here because the function
+ * is being in serveral places.
+ */
+static int dmgOpenImageWithinXar(uint32_t fOpen, PVDINTERFACEIOINT pVDIfIoInt, void *pvStorage, const char *pszFilename,
+ PRTVFSFSSTREAM phXarFss, PRTVFSFILE phDmgFileInXar)
+{
+ /*
+ * Open the XAR file stream.
+ */
+ RTVFSFILE hVfsFile;
+#ifdef VBOX_WITH_DIRECT_XAR_ACCESS
+ int rc = RTVfsFileOpenNormal(pszFilename, fOpen, &hVfsFile);
+#else
+ int rc = VDIfCreateVfsFile(NULL, pVDIfIoInt, pvStorage, fOpen, &hVfsFile);
+#endif
+ if (RT_FAILURE(rc))
+ return rc;
+
+ RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+
+ RTVFSFSSTREAM hXarFss;
+ rc = RTZipXarFsStreamFromIoStream(hVfsIos, 0 /*fFlags*/, &hXarFss);
+ RTVfsIoStrmRelease(hVfsIos);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Look for a DMG in the stream that we can use.
+ */
+ for (;;)
+ {
+ char *pszName;
+ RTVFSOBJTYPE enmType;
+ RTVFSOBJ hVfsObj;
+ rc = RTVfsFsStrmNext(hXarFss, &pszName, &enmType, &hVfsObj);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* It must be a file object so it can be seeked, this also implies that
+ it's uncompressed. Then it must have the .dmg suffix. */
+ if (enmType == RTVFSOBJTYPE_FILE)
+ {
+ size_t cchName = strlen(pszName);
+ const char *pszSuff = pszName + cchName - 4;
+ if ( cchName >= 4
+ && pszSuff[0] == '.'
+ && (pszSuff[1] == 'd' || pszSuff[1] == 'D')
+ && (pszSuff[2] == 'm' || pszSuff[2] == 'M')
+ && (pszSuff[3] == 'g' || pszSuff[3] == 'G'))
+ {
+ RTVFSFILE hDmgFileInXar = RTVfsObjToFile(hVfsObj);
+ AssertBreakStmt(hDmgFileInXar != NIL_RTVFSFILE, rc = VERR_INTERNAL_ERROR_3);
+
+ if (pszFilename)
+ DMG_PRINTF(("DMG: Using '%s' within XAR file '%s'...\n", pszName, pszFilename));
+ *phXarFss = hXarFss;
+ *phDmgFileInXar = hDmgFileInXar;
+
+ RTStrFree(pszName);
+ RTVfsObjRelease(hVfsObj);
+
+ return VINF_SUCCESS;
+ }
+ }
+
+ /* Release the current return values. */
+ RTStrFree(pszName);
+ RTVfsObjRelease(hVfsObj);
+ }
+
+ /* Not found or some kind of error. */
+ RTVfsFsStrmRelease(hXarFss);
+ if (rc == VERR_EOF)
+ rc = VERR_VD_DMG_NOT_FOUND_INSIDE_XAR;
+ AssertStmt(RT_FAILURE_NP(rc), rc = VERR_INTERNAL_ERROR_4);
+ return rc;
+}
+
+
+/**
+ * Worker for dmgOpen that reads in and validates all the necessary
+ * structures from the image.
+ *
+ * @returns VBox status code.
+ * @param pThis The DMG instance data.
+ * @param uOpenFlags Flags for defining the open type.
+ */
+static DECLCALLBACK(int) dmgOpenImage(PDMGIMAGE pThis, unsigned uOpenFlags)
+{
+ pThis->uOpenFlags = uOpenFlags;
+
+ pThis->pIfError = VDIfErrorGet(pThis->pVDIfsDisk);
+ pThis->pIfIoXxx = VDIfIoIntGet(pThis->pVDIfsImage);
+ pThis->hDmgFileInXar = NIL_RTVFSFILE;
+ pThis->hXarFss = NIL_RTVFSFSSTREAM;
+ AssertPtrReturn(pThis->pIfIoXxx, VERR_INVALID_PARAMETER);
+
+ int rc = vdIfIoIntFileOpen(pThis->pIfIoXxx, pThis->pszFilename,
+ VDOpenFlagsToFileOpenFlags(uOpenFlags, false /* fCreate */),
+ &pThis->pStorage);
+ if (RT_FAILURE(rc))
+ {
+ /* Do NOT signal an appropriate error here, as the VD layer has the
+ * choice of retrying the open if it failed. */
+ return rc;
+ }
+
+ /*
+ * Check for XAR archive.
+ */
+ uint32_t u32XarMagic;
+ rc = dmgWrapFileReadSync(pThis, 0, &u32XarMagic, sizeof(u32XarMagic));
+ if (RT_FAILURE(rc))
+ return rc;
+ if (u32XarMagic == XAR_HEADER_MAGIC)
+ {
+ rc = dmgOpenImageWithinXar(VDOpenFlagsToFileOpenFlags(uOpenFlags, false /* fCreate */),
+ pThis->pIfIoXxx,
+ pThis->pStorage,
+ pThis->pszFilename,
+ &pThis->hXarFss, &pThis->hDmgFileInXar);
+ if (RT_FAILURE(rc))
+ return rc;
+#ifdef VBOX_WITH_DIRECT_XAR_ACCESS
+ vdIfIoIntFileClose(pThis->pIfIoXxx, pThis->pStorage);
+ pThis->pStorage = NULL;
+#endif
+ }
+#if 0 /* This is for testing whether the VFS wrappers actually works. */
+ else
+ {
+ rc = RTVfsFileOpenNormal(pThis->pszFilename, VDOpenFlagsToFileOpenFlags(uOpenFlags, false /* fCreate */),
+ &pThis->hDmgFileInXar);
+ if (RT_FAILURE(rc))
+ return rc;
+ vdIfIoIntFileClose(pThis->pIfIoXxx, pThis->pStorage);
+ pThis->pStorage = NULL;
+ }
+#endif
+
+ /*
+ * Read the footer.
+ */
+ rc = dmgWrapFileGetSize(pThis, &pThis->cbFile);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (pThis->cbFile < 1024)
+ return VERR_VD_DMG_INVALID_HEADER;
+ rc = dmgWrapFileReadSync(pThis, pThis->cbFile - sizeof(pThis->Ftr), &pThis->Ftr, sizeof(pThis->Ftr));
+ if (RT_FAILURE(rc))
+ return rc;
+ dmgUdifFtrFile2HostEndian(&pThis->Ftr);
+
+ /*
+ * Do we recognize the footer structure? If so, is it valid?
+ */
+ if (pThis->Ftr.u32Magic != DMGUDIF_MAGIC)
+ return VERR_VD_DMG_INVALID_HEADER;
+ if (pThis->Ftr.u32Version != DMGUDIF_VER_CURRENT)
+ return VERR_VD_DMG_INVALID_HEADER;
+ if (pThis->Ftr.cbFooter != sizeof(pThis->Ftr))
+ return VERR_VD_DMG_INVALID_HEADER;
+
+ if (!dmgUdifFtrIsValid(&pThis->Ftr, pThis->cbFile - sizeof(pThis->Ftr)))
+ {
+ DMG_PRINTF(("Bad DMG: '%s' cbFile=%RTfoff\n", pThis->pszFilename, pThis->cbFile));
+ return VERR_VD_DMG_INVALID_HEADER;
+ }
+
+ pThis->cbSize = pThis->Ftr.cSectors * DMG_SECTOR_SIZE;
+
+ /*
+ * Read and parse the XML portion.
+ */
+ size_t cchXml = (size_t)pThis->Ftr.cbXml;
+ char *pszXml = (char *)RTMemAlloc(cchXml + 1);
+ if (!pszXml)
+ return VERR_NO_MEMORY;
+ rc = dmgWrapFileReadSync(pThis, pThis->Ftr.offXml, pszXml, cchXml);
+ if (RT_SUCCESS(rc))
+ {
+ pszXml[cchXml] = '\0';
+ const char *pszError = dmgOpenXmlToRsrc(pThis, pszXml);
+ if (!pszError)
+ {
+ PCDMGUDIFRSRCARRAY pRsrcBlkx = NULL;
+
+ rc = dmgGetRsrcData(pThis, "blkx", &pRsrcBlkx);
+ if (RT_SUCCESS(rc))
+ {
+ for (unsigned idxBlkx = 0; idxBlkx < pRsrcBlkx->cEntries; idxBlkx++)
+ {
+ PDMGBLKX pBlkx = NULL;
+
+ if (pRsrcBlkx->aEntries[idxBlkx].cbData < sizeof(DMGBLKX))
+ {
+ rc = VERR_VD_DMG_INVALID_HEADER;
+ break;
+ }
+
+ pBlkx = (PDMGBLKX)RTMemAllocZ(pRsrcBlkx->aEntries[idxBlkx].cbData);
+ if (!pBlkx)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ memcpy(pBlkx, pRsrcBlkx->aEntries[idxBlkx].pbData, pRsrcBlkx->aEntries[idxBlkx].cbData);
+
+ dmgBlkxFile2HostEndian(pBlkx);
+
+ if ( dmgBlkxIsValid(pBlkx)
+ && pRsrcBlkx->aEntries[idxBlkx].cbData == pBlkx->cBlocksRunCount * sizeof(DMGBLKXDESC) + sizeof(DMGBLKX))
+ rc = dmgBlkxParse(pThis, pBlkx);
+ else
+ rc = VERR_VD_DMG_INVALID_HEADER;
+
+ RTMemFree(pBlkx);
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ else
+ rc = VERR_VD_DMG_INVALID_HEADER;
+ }
+ else
+ {
+ DMG_PRINTF(("**** XML DUMP BEGIN ***\n%s\n**** XML DUMP END ****\n", pszXml));
+ DMG_PRINTF(("**** Bad XML at %#lx (%lu) ***\n%.256s\n**** Bad XML END ****\n",
+ (unsigned long)(pszError - pszXml), (unsigned long)(pszError - pszXml), pszError));
+ rc = VERR_VD_DMG_XML_PARSE_ERROR;
+ }
+ }
+ RTMemFree(pszXml);
+
+ if (RT_SUCCESS(rc))
+ {
+ PVDREGIONDESC pRegion = &pThis->RegionList.aRegions[0];
+ pThis->RegionList.fFlags = 0;
+ pThis->RegionList.cRegions = 1;
+
+ pRegion->offRegion = 0; /* Disk start. */
+ pRegion->cbBlock = 2048;
+ pRegion->enmDataForm = VDREGIONDATAFORM_RAW;
+ pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
+ pRegion->cbData = 2048;
+ pRegion->cbMetadata = 0;
+ pRegion->cRegionBlocksOrBytes = pThis->cbSize;
+ }
+ else
+ dmgFreeImage(pThis, false);
+ return rc;
+}
+
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnProbe} */
+static DECLCALLBACK(int) dmgProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
+ PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType)
+{
+ RT_NOREF(pVDIfsDisk, enmDesiredType);
+ LogFlowFunc(("pszFilename=\"%s\" pVDIfsDisk=%#p pVDIfsImage=%#p penmType=%#p\n",
+ pszFilename, pVDIfsDisk, pVDIfsImage, penmType));
+
+ PVDINTERFACEIOINT pIfIo = VDIfIoIntGet(pVDIfsImage);
+ AssertPtrReturn(pIfIo, VERR_INVALID_PARAMETER);
+
+ /*
+ * Open the file and check for XAR.
+ */
+ PVDIOSTORAGE pStorage = NULL;
+ int rc = vdIfIoIntFileOpen(pIfIo, pszFilename,
+ VDOpenFlagsToFileOpenFlags(VD_OPEN_FLAGS_READONLY, false /* fCreate */),
+ &pStorage);
+ if (RT_FAILURE(rc))
+ {
+ LogFlowFunc(("returns %Rrc (error opening file)\n", rc));
+ return rc;
+ }
+
+ /*
+ * Check for XAR file.
+ */
+ RTVFSFSSTREAM hXarFss = NIL_RTVFSFSSTREAM;
+ RTVFSFILE hDmgFileInXar = NIL_RTVFSFILE;
+ uint32_t u32XarMagic;
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, 0, &u32XarMagic, sizeof(u32XarMagic));
+ if ( RT_SUCCESS(rc)
+ && u32XarMagic == XAR_HEADER_MAGIC)
+ {
+ rc = dmgOpenImageWithinXar(RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE,
+ pIfIo, pStorage, pszFilename,
+ &hXarFss, &hDmgFileInXar);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Read the DMG footer.
+ */
+ uint64_t cbFile;
+ if (hDmgFileInXar == NIL_RTVFSFILE)
+ rc = vdIfIoIntFileGetSize(pIfIo, pStorage, &cbFile);
+ else
+ rc = RTVfsFileQuerySize(hDmgFileInXar, &cbFile);
+ if ( RT_SUCCESS(rc)
+ && cbFile >= sizeof(DMGUDIF))
+ {
+ DMGUDIF Ftr;
+ uint64_t offFtr = cbFile - sizeof(Ftr);
+ if (hDmgFileInXar == NIL_RTVFSFILE)
+ rc = vdIfIoIntFileReadSync(pIfIo, pStorage, offFtr, &Ftr, sizeof(Ftr));
+ else
+ rc = RTVfsFileReadAt(hDmgFileInXar, offFtr, &Ftr, sizeof(Ftr), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do we recognize this stuff? Does it look valid?
+ */
+ if ( Ftr.u32Magic == RT_H2BE_U32_C(DMGUDIF_MAGIC)
+ && Ftr.u32Version == RT_H2BE_U32_C(DMGUDIF_VER_CURRENT)
+ && Ftr.cbFooter == RT_H2BE_U32_C(sizeof(Ftr)))
+ {
+ dmgUdifFtrFile2HostEndian(&Ftr);
+ if (dmgUdifFtrIsValid(&Ftr, offFtr))
+ {
+ rc = VINF_SUCCESS;
+ *penmType = VDTYPE_OPTICAL_DISC;
+ }
+ else
+ {
+ DMG_PRINTF(("Bad DMG: '%s' offFtr=%RTfoff\n", pszFilename, offFtr));
+ rc = VERR_VD_DMG_INVALID_HEADER;
+ }
+ }
+ else
+ rc = VERR_VD_DMG_INVALID_HEADER;
+ }
+ else
+ rc = VERR_VD_DMG_INVALID_HEADER;
+ }
+ else
+ rc = VERR_VD_DMG_INVALID_HEADER;
+
+ /* Clean up. */
+ RTVfsFileRelease(hDmgFileInXar);
+ RTVfsFsStrmRelease(hXarFss);
+ vdIfIoIntFileClose(pIfIo, pStorage);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnOpen} */
+static DECLCALLBACK(int) dmgOpen(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));
+
+ NOREF(enmType); /**< @todo r=klaus make use of the type info. */
+
+ /* Check open flags. All valid flags are (in principle) supported. */
+ AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
+
+ /* Check remaining arguments. */
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename, VERR_INVALID_PARAMETER);
+
+ /*
+ * Reject combinations we don't currently support.
+ *
+ * There is no point in being paranoid about the input here as we're just a
+ * simple backend and can expect the caller to be the only user and already
+ * have validate what it passes thru to us.
+ */
+ if ( !(uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ || (uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO))
+ {
+ LogFlowFunc(("Unsupported flag(s): %#x\n", uOpenFlags));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * Create the basic instance data structure and open the file,
+ * then hand it over to a worker function that does all the rest.
+ */
+ int rc = VERR_NO_MEMORY;
+ PDMGIMAGE pThis = (PDMGIMAGE)RTMemAllocZ(RT_UOFFSETOF(DMGIMAGE, RegionList.aRegions[1]));
+ if (pThis)
+ {
+ pThis->pszFilename = pszFilename;
+ pThis->pStorage = NULL;
+ pThis->pVDIfsDisk = pVDIfsDisk;
+ pThis->pVDIfsImage = pVDIfsImage;
+
+ rc = dmgOpenImage(pThis, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ *ppBackendData = pThis;
+ else
+ RTMemFree(pThis);
+ }
+
+ LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnCreate} */
+static DECLCALLBACK(int) dmgCreate(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_NOREF8(pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags);
+ RT_NOREF7(uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData);
+ 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));
+ int rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnRename} */
+static DECLCALLBACK(int) dmgRename(void *pBackendData, const char *pszFilename)
+{
+ RT_NOREF2(pBackendData, pszFilename);
+ LogFlowFunc(("pBackendData=%#p pszFilename=%#p\n", pBackendData, pszFilename));
+ int rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnClose} */
+static DECLCALLBACK(int) dmgClose(void *pBackendData, bool fDelete)
+{
+ LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+
+ int rc = dmgFreeImage(pThis, fDelete);
+ RTMemFree(pThis);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnRead} */
+static DECLCALLBACK(int) dmgRead(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));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ PDMGEXTENT pExtent = NULL;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pThis);
+ Assert(uOffset % DMG_SECTOR_SIZE == 0);
+ Assert(cbToRead % DMG_SECTOR_SIZE == 0);
+
+ if ( uOffset + cbToRead > pThis->cbSize
+ || cbToRead == 0)
+ {
+ LogFlowFunc(("returns VERR_INVALID_PARAMETER\n"));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ pExtent = dmgExtentGetFromOffset(pThis, DMG_BYTE2BLOCK(uOffset));
+
+ if (pExtent)
+ {
+ uint64_t uExtentRel = DMG_BYTE2BLOCK(uOffset) - pExtent->uSectorExtent;
+
+ /* Remain in this extent. */
+ cbToRead = RT_MIN(cbToRead, DMG_BLOCK2BYTE(pExtent->cSectorsExtent - uExtentRel));
+
+ switch (pExtent->enmType)
+ {
+ case DMGEXTENTTYPE_RAW:
+ {
+ rc = dmgWrapFileReadUser(pThis, pExtent->offFileStart + DMG_BLOCK2BYTE(uExtentRel), pIoCtx, cbToRead);
+ break;
+ }
+ case DMGEXTENTTYPE_ZERO:
+ {
+ vdIfIoIntIoCtxSet(pThis->pIfIoXxx, pIoCtx, 0, cbToRead);
+ break;
+ }
+ case DMGEXTENTTYPE_COMP_ZLIB:
+ {
+ if (pThis->pExtentDecomp != pExtent)
+ {
+ if (DMG_BLOCK2BYTE(pExtent->cSectorsExtent) > pThis->cbDecompExtent)
+ {
+ if (RT_LIKELY(pThis->pvDecompExtent))
+ RTMemFree(pThis->pvDecompExtent);
+
+ pThis->pvDecompExtent = RTMemAllocZ(DMG_BLOCK2BYTE(pExtent->cSectorsExtent));
+ if (!pThis->pvDecompExtent)
+ rc = VERR_NO_MEMORY;
+ else
+ pThis->cbDecompExtent = DMG_BLOCK2BYTE(pExtent->cSectorsExtent);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = dmgFileInflateSync(pThis, pExtent->offFileStart, pExtent->cbFile,
+ pThis->pvDecompExtent,
+ RT_MIN(pThis->cbDecompExtent, DMG_BLOCK2BYTE(pExtent->cSectorsExtent)));
+ if (RT_SUCCESS(rc))
+ pThis->pExtentDecomp = pExtent;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ vdIfIoIntIoCtxCopyTo(pThis->pIfIoXxx, pIoCtx,
+ (uint8_t *)pThis->pvDecompExtent + DMG_BLOCK2BYTE(uExtentRel),
+ cbToRead);
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid extent type\n"));
+ }
+
+ if (RT_SUCCESS(rc))
+ *pcbActuallyRead = cbToRead;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnWrite} */
+static DECLCALLBACK(int) dmgWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
+ PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead,
+ size_t *pcbPostRead, unsigned fWrite)
+{
+ RT_NOREF7(uOffset, cbToWrite, pIoCtx, pcbWriteProcess, pcbPreRead, pcbPostRead, fWrite);
+ LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToWrite=%zu pcbWriteProcess=%#p pcbPreRead=%#p pcbPostRead=%#p\n",
+ pBackendData, uOffset, pIoCtx, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ int rc = VERR_NOT_IMPLEMENTED;
+
+ AssertPtr(pThis);
+ Assert(uOffset % 512 == 0);
+ Assert(cbToWrite % 512 == 0);
+
+ if (!(pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY))
+ AssertMsgFailed(("Not implemented\n"));
+ else
+ rc = VERR_VD_IMAGE_READ_ONLY;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnFlush} */
+static DECLCALLBACK(int) dmgFlush(void *pBackendData, PVDIOCTX pIoCtx)
+{
+ RT_NOREF1(pIoCtx);
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ int rc;
+
+ AssertPtr(pThis);
+
+ rc = dmgFlushImage(pThis);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetVersion} */
+static DECLCALLBACK(unsigned) dmgGetVersion(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, 0);
+
+ return 1;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetFileSize} */
+static DECLCALLBACK(uint64_t) dmgGetFileSize(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, 0);
+
+ uint64_t cbFile = 0;
+ if (pThis->pStorage || pThis->hDmgFileInXar != NIL_RTVFSFILE)
+ {
+ int rc = dmgWrapFileGetSize(pThis, &cbFile);
+ if (RT_FAILURE(rc))
+ cbFile = 0; /* Make sure it is 0 */
+ }
+
+ LogFlowFunc(("returns %lld\n", cbFile));
+ return cbFile;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetPCHSGeometry} */
+static DECLCALLBACK(int) dmgGetPCHSGeometry(void *pBackendData, PVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ if (pThis->PCHSGeometry.cCylinders)
+ *pPCHSGeometry = pThis->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;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetPCHSGeometry} */
+static DECLCALLBACK(int) dmgSetPCHSGeometry(void *pBackendData, PCVDGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n",
+ pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ if (pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pThis->PCHSGeometry = *pPCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetLCHSGeometry} */
+static DECLCALLBACK(int) dmgGetLCHSGeometry(void *pBackendData, PVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ if (pThis->LCHSGeometry.cCylinders)
+ *pLCHSGeometry = pThis->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;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetLCHSGeometry} */
+static DECLCALLBACK(int) dmgSetLCHSGeometry(void *pBackendData, PCVDGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n",
+ pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
+
+ if (pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ rc = VERR_VD_IMAGE_READ_ONLY;
+ else
+ pThis->LCHSGeometry = *pLCHSGeometry;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */
+static DECLCALLBACK(int) dmgQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
+{
+ LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList));
+ PDMGIMAGE pThis = (PDMGIMAGE)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) dmgRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
+{
+ RT_NOREF1(pRegionList);
+ LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ AssertPtr(pThis); RT_NOREF(pThis);
+
+ /* Nothing to do here. */
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetImageFlags} */
+static DECLCALLBACK(unsigned) dmgGetImageFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ AssertPtrReturn(pThis, 0);
+
+ LogFlowFunc(("returns %#x\n", pThis->uImageFlags));
+ return pThis->uImageFlags;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnGetOpenFlags} */
+static DECLCALLBACK(unsigned) dmgGetOpenFlags(void *pBackendData)
+{
+ LogFlowFunc(("pBackendData=%#p\n", pBackendData));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+
+ AssertPtrReturn(pThis, 0);
+
+ LogFlowFunc(("returns %#x\n", pThis->uOpenFlags));
+ return pThis->uOpenFlags;
+}
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnSetOpenFlags} */
+static DECLCALLBACK(int) dmgSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
+{
+ LogFlowFunc(("pBackendData=%#p\n uOpenFlags=%#x", pBackendData, uOpenFlags));
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+ int rc = VINF_SUCCESS;
+
+ /* Image must be opened and the new flags must be valid. */
+ if (!pThis || (uOpenFlags & ~( VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO
+ | 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. */
+ rc = dmgFreeImage(pThis, false);
+ if (RT_SUCCESS(rc))
+ rc = dmgOpenImage(pThis, uOpenFlags);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @copydoc VDIMAGEBACKEND::pfnGetComment */
+VD_BACKEND_CALLBACK_GET_COMMENT_DEF_NOT_SUPPORTED(dmgGetComment);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetComment */
+VD_BACKEND_CALLBACK_SET_COMMENT_DEF_NOT_SUPPORTED(dmgSetComment, PDMGIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(dmgGetUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(dmgSetUuid, PDMGIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(dmgGetModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(dmgSetModificationUuid, PDMGIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(dmgGetParentUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(dmgSetParentUuid, PDMGIMAGE);
+
+/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */
+VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(dmgGetParentModificationUuid);
+
+/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */
+VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(dmgSetParentModificationUuid, PDMGIMAGE);
+
+/** @interface_method_impl{VDIMAGEBACKEND,pfnDump} */
+static DECLCALLBACK(void) dmgDump(void *pBackendData)
+{
+ PDMGIMAGE pThis = (PDMGIMAGE)pBackendData;
+
+ AssertPtrReturnVoid(pThis);
+ vdIfErrorMessage(pThis->pIfError, "Header: Geometry PCHS=%u/%u/%u LCHS=%u/%u/%u cSectors=%llu\n",
+ pThis->PCHSGeometry.cCylinders, pThis->PCHSGeometry.cHeads, pThis->PCHSGeometry.cSectors,
+ pThis->LCHSGeometry.cCylinders, pThis->LCHSGeometry.cHeads, pThis->LCHSGeometry.cSectors,
+ pThis->cbSize / DMG_SECTOR_SIZE);
+}
+
+
+const VDIMAGEBACKEND g_DmgBackend =
+{
+ /* u32Version */
+ VD_IMGBACKEND_VERSION,
+ /* pszBackendName */
+ "DMG",
+ /* uBackendCaps */
+ VD_CAP_FILE | VD_CAP_VFS,
+ /* paFileExtensions */
+ s_aDmgFileExtensions,
+ /* paConfigInfo */
+ NULL,
+ /* pfnProbe */
+ dmgProbe,
+ /* pfnOpen */
+ dmgOpen,
+ /* pfnCreate */
+ dmgCreate,
+ /* pfnRename */
+ dmgRename,
+ /* pfnClose */
+ dmgClose,
+ /* pfnRead */
+ dmgRead,
+ /* pfnWrite */
+ dmgWrite,
+ /* pfnFlush */
+ dmgFlush,
+ /* pfnDiscard */
+ NULL,
+ /* pfnGetVersion */
+ dmgGetVersion,
+ /* pfnGetFileSize */
+ dmgGetFileSize,
+ /* pfnGetPCHSGeometry */
+ dmgGetPCHSGeometry,
+ /* pfnSetPCHSGeometry */
+ dmgSetPCHSGeometry,
+ /* pfnGetLCHSGeometry */
+ dmgGetLCHSGeometry,
+ /* pfnSetLCHSGeometry */
+ dmgSetLCHSGeometry,
+ /* pfnQueryRegions */
+ dmgQueryRegions,
+ /* pfnRegionListRelease */
+ dmgRegionListRelease,
+ /* pfnGetImageFlags */
+ dmgGetImageFlags,
+ /* pfnGetOpenFlags */
+ dmgGetOpenFlags,
+ /* pfnSetOpenFlags */
+ dmgSetOpenFlags,
+ /* pfnGetComment */
+ dmgGetComment,
+ /* pfnSetComment */
+ dmgSetComment,
+ /* pfnGetUuid */
+ dmgGetUuid,
+ /* pfnSetUuid */
+ dmgSetUuid,
+ /* pfnGetModificationUuid */
+ dmgGetModificationUuid,
+ /* pfnSetModificationUuid */
+ dmgSetModificationUuid,
+ /* pfnGetParentUuid */
+ dmgGetParentUuid,
+ /* pfnSetParentUuid */
+ dmgSetParentUuid,
+ /* pfnGetParentModificationUuid */
+ dmgGetParentModificationUuid,
+ /* pfnSetParentModificationUuid */
+ dmgSetParentModificationUuid,
+ /* pfnDump */
+ dmgDump,
+ /* 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
+};
+