Index: /trunk/src/VBox/Devices/Storage/VmdkHDD.cpp
===================================================================
--- /trunk/src/VBox/Devices/Storage/VmdkHDD.cpp	(revision 258)
+++ /trunk/src/VBox/Devices/Storage/VmdkHDD.cpp	(revision 258)
@@ -0,0 +1,975 @@
+/** @file
+ *
+ * VBox storage devices:
+ * VBox VMDK container implementation
+ */
+
+/*
+ * Copyright (C) 2006 InnoTek Systemberatung GmbH
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation,
+ * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
+ * distribution. VirtualBox OSE is distributed in the hope that it will
+ * be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * If you received this file as part of a commercial VirtualBox
+ * distribution, then only the terms of your commercial VirtualBox
+ * license agreement apply instead of the previous paragraph.
+ *
+ * --------------------------------------------------------------------
+ *
+ * This code is based on:
+ *
+ * Block driver for the VMDK format
+ * 
+ * Copyright (c) 2004 Fabrice Bellard
+ * Copyright (c) 2005 Filip Navara
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/*******************************************************************************
+*   Header Files                                                               *
+*******************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_VBOXHDD
+#include <VBox/pdm.h>
+#include <VBox/mm.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+
+#include "vl_vbox.h"
+#include "Builtins.h"
+
+/*******************************************************************************
+*   Constants And Macros, Structures and Typedefs                              *
+*******************************************************************************/
+
+/** The Sector size.
+ * Currently we support only 512 bytes sectors.
+ */
+#define VMDK_GEOMETRY_SECTOR_SIZE    (512)
+/**  512 = 2^^9 */
+#define VMDK_GEOMETRY_SECTOR_SHIFT   (9)
+
+#define VMDK3_MAGIC (('C' << 24) | ('O' << 16) | ('W' << 8) | 'D')
+#define VMDK4_MAGIC (('K' << 24) | ('D' << 16) | ('M' << 8) | 'V')
+
+#pragma pack(1)
+typedef struct {
+    uint32_t version;
+    uint32_t flags;
+    uint32_t disk_sectors;
+    uint32_t granularity;
+    uint32_t l1dir_offset;
+    uint32_t l1dir_size;
+    uint32_t file_sectors;
+    uint32_t cylinders;
+    uint32_t heads;
+    uint32_t sectors_per_track;
+} VMDK3Header;
+#pragma pack()
+
+#pragma pack(1)
+typedef struct {
+    uint32_t version;
+    uint32_t flags;
+    int64_t capacity;
+    int64_t granularity;
+    int64_t desc_offset;
+    int64_t desc_size;
+    int32_t num_gtes_per_gte;
+    int64_t rgd_offset;
+    int64_t gd_offset;
+    int64_t grain_offset;
+    char filler[1];
+    char check_bytes[4];
+} VMDK4Header;
+#pragma pack()
+
+#define L2_CACHE_SIZE 16
+
+typedef struct BDRVVmdkState {
+    /** File handle. */
+    RTFILE                  File;
+
+    bool                    fReadOnly;
+
+    uint64_t                total_sectors;
+
+    int64_t l1_table_offset;
+    int64_t l1_backup_table_offset;
+    uint32_t *l1_table;
+    uint32_t *l1_backup_table;
+    unsigned int l1_size;
+    uint32_t l1_entry_sectors;
+
+    unsigned int l2_size;
+    uint32_t *l2_cache;
+    uint32_t l2_cache_offsets[L2_CACHE_SIZE];
+    uint32_t l2_cache_counts[L2_CACHE_SIZE];
+
+    unsigned int cluster_sectors;
+} BDRVVmdkState;
+
+
+#define VMDKDISK_SIGNATURE          0x8013ABCD
+
+
+/**
+ * Harddisk geometry.
+ */
+#pragma pack(1)
+typedef struct VMDKDISKGEOMETRY
+{
+    /** Cylinders. */
+    uint32_t    cCylinders;
+    /** Heads. */
+    uint32_t    cHeads;
+    /** Sectors per track. */
+    uint32_t    cSectors;
+    /** Sector size. (bytes per sector) */
+    uint32_t    cbSector;
+} VMDKDISKGEOMETRY, *PVMDKDISKGEOMETRY;
+#pragma pack()
+
+/**
+ * VMDK HDD Container main structure, private part.
+ */
+typedef struct VMDKDISK
+{
+    uint32_t        u32Signature;
+
+    BDRVVmdkState   VmdkState;
+
+    /** Hard disk geometry. */
+    VMDKDISKGEOMETRY Geometry;
+
+    /** The media interface. */
+    PDMIMEDIA       IMedia;
+    /** Pointer to the driver instance. */
+    PPDMDRVINS      pDrvIns;
+} VMDKDISK, *PVMDKDISK;
+
+
+/** Converts a pointer to VDIDISK::IMedia to a PVMDKDISK. */
+#define PDMIMEDIA_2_VMDKDISK(pInterface) ( (PVMDKDISK)((uintptr_t)pInterface - RT_OFFSETOF(VMDKDISK, IMedia)) )
+
+/** Converts a pointer to PDMDRVINS::IBase to a PPDMDRVINS. */
+#define PDMIBASE_2_DRVINS(pInterface)   ( (PPDMDRVINS)((uintptr_t)pInterface - RT_OFFSETOF(PDMDRVINS, IBase)) )
+
+/** Converts a pointer to PDMDRVINS::IBase to a PVMDKDISK. */
+#define PDMIBASE_2_VMDKDISK(pInterface)  ( PDMINS2DATA(PDMIBASE_2_DRVINS(pInterface), PVMDKDISK) )
+
+
+/*******************************************************************************
+*   Internal Functions                                                         *
+*******************************************************************************/
+static DECLCALLBACK(int)  vmdkConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle);
+static DECLCALLBACK(void) vmdkDestruct(PPDMDRVINS pDrvIns);
+static DECLCALLBACK(int)  vmdkRead(PPDMIMEDIA pInterface,
+                                  uint64_t off, void *pvBuf, size_t cbRead);
+static DECLCALLBACK(int)  vmdkWrite(PPDMIMEDIA pInterface,
+                                   uint64_t off, const void *pvBuf, size_t cbWrite);
+static DECLCALLBACK(int)  vmdkFlush(PPDMIMEDIA pInterface);
+static DECLCALLBACK(uint64_t) vmdkGetSize(PPDMIMEDIA pInterface);
+static DECLCALLBACK(int)  vmdkBiosGetGeometry(PPDMIMEDIA pInterface, uint32_t *pcCylinders,
+                                             uint32_t *pcHeads, uint32_t *pcSectors);
+static DECLCALLBACK(int)  vmdkBiosSetGeometry(PPDMIMEDIA pInterface, uint32_t cCylinders,
+                                             uint32_t cHeads, uint32_t cSectors);
+static DECLCALLBACK(int)  vmdkGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid);
+static DECLCALLBACK(bool) vmdkIsReadOnly(PPDMIMEDIA pInterface);
+static DECLCALLBACK(int)  vmdkBiosGetTranslation(PPDMIMEDIA pInterface,
+                                                PPDMBIOSTRANSLATION penmTranslation);
+static DECLCALLBACK(int)  vmdkBiosSetTranslation(PPDMIMEDIA pInterface,
+                                                PDMBIOSTRANSLATION enmTranslation);
+static DECLCALLBACK(void *) vmdkQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface);
+
+static int vmdk_probe(const uint8_t *buf, int buf_size, const char *filename)
+{
+    uint32_t magic;
+
+    if (buf_size < 4)
+        return 0;
+    magic = be32_to_cpu(*(uint32_t *)buf);
+    if (magic == VMDK3_MAGIC ||
+        magic == VMDK4_MAGIC)
+        return 100;
+    else
+        return 0;
+}
+
+static int vmdk_open(BDRVVmdkState *s, const char *filename, bool fReadOnly)
+{
+    uint32_t magic, i;
+    int l1_size;
+
+    /*
+     * Open the image.
+     */
+    s->fReadOnly = fReadOnly;
+    int rc = RTFileOpen(&s->File,
+                        filename,
+                        fReadOnly
+                        ? RTFILE_O_READ      | RTFILE_O_OPEN | RTFILE_O_DENY_NONE
+                        : RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+    if (VBOX_FAILURE(rc))
+    {
+        if (!fReadOnly)
+        {
+            /* Try to open image for reading only. */
+            rc = RTFileOpen(&s->File,
+                            filename,
+                            RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+            if (VBOX_SUCCESS(rc))
+                s->fReadOnly = true;
+        }
+        if (VBOX_FAILURE(rc))
+            return rc;
+    }
+    rc = RTFileRead(s->File, &magic, sizeof(magic), NULL);
+    AssertRC(rc);
+    if (VBOX_FAILURE(rc))
+        goto fail;
+
+    magic = be32_to_cpu(magic);
+    if (magic == VMDK3_MAGIC) {
+        VMDK3Header header;
+        rc = RTFileRead(s->File, &header, sizeof(header), NULL);
+        AssertRC(rc);
+        if (VBOX_FAILURE(rc))
+            goto fail;
+        s->cluster_sectors = le32_to_cpu(header.granularity);
+        s->l2_size = 1 << 9;
+        s->l1_size = 1 << 6;
+        s->total_sectors = le32_to_cpu(header.disk_sectors);
+        s->l1_table_offset = le32_to_cpu(header.l1dir_offset) << 9;
+        s->l1_backup_table_offset = 0;
+        s->l1_entry_sectors = s->l2_size * s->cluster_sectors;
+    } else if (magic == VMDK4_MAGIC) {
+        VMDK4Header header;
+        
+        rc = RTFileRead(s->File, &header, sizeof(header), NULL);
+        AssertRC(rc);
+        if (VBOX_FAILURE(rc))
+            goto fail;
+        s->total_sectors = le64_to_cpu(header.capacity);
+        s->cluster_sectors = le64_to_cpu(header.granularity);
+        s->l2_size = le32_to_cpu(header.num_gtes_per_gte);
+        s->l1_entry_sectors = s->l2_size * s->cluster_sectors;
+        if (s->l1_entry_sectors <= 0)
+        {
+            rc = VERR_VDI_INVALID_HEADER;
+            goto fail;
+        }
+        s->l1_size = (s->total_sectors + s->l1_entry_sectors - 1) 
+            / s->l1_entry_sectors;
+        s->l1_table_offset = le64_to_cpu(header.rgd_offset) << 9;
+        s->l1_backup_table_offset = le64_to_cpu(header.gd_offset) << 9;
+    } else {
+        rc = VERR_VDI_INVALID_HEADER;
+        goto fail;
+    }
+    /* read the L1 table */
+    l1_size = s->l1_size * sizeof(uint32_t);
+    s->l1_table = (uint32_t *)RTMemAllocZ(l1_size);
+    if (!s->l1_table)
+    {
+        rc = VERR_NO_MEMORY;
+        goto fail;
+    }
+    rc = RTFileSeek(s->File, s->l1_table_offset, RTFILE_SEEK_BEGIN, NULL);
+    AssertRC(rc);
+    if (VBOX_FAILURE(rc))
+        goto fail;
+    rc = RTFileRead(s->File, s->l1_table, l1_size, NULL);
+    AssertRC(rc);
+    if (VBOX_FAILURE(rc))
+        goto fail;
+    for(i = 0; i < s->l1_size; i++) {
+        le32_to_cpus(&s->l1_table[i]);
+    }
+
+    if (s->l1_backup_table_offset) {
+        s->l1_backup_table = (uint32_t *)RTMemAllocZ(l1_size);
+        if (!s->l1_backup_table)
+        {
+            rc = VERR_NO_MEMORY;
+            goto fail;
+        }
+        rc = RTFileSeek(s->File, s->l1_backup_table_offset, RTFILE_SEEK_BEGIN, NULL);
+        AssertRC(rc);
+        if (VBOX_FAILURE(rc))
+            goto fail;
+        rc = RTFileRead(s->File, s->l1_backup_table, l1_size, NULL);
+        AssertRC(rc);
+        if (VBOX_FAILURE(rc))
+            goto fail;
+        for(i = 0; i < s->l1_size; i++) {
+            le32_to_cpus(&s->l1_backup_table[i]);
+        }
+    }
+
+    s->l2_cache = (uint32_t *)RTMemAllocZ(s->l2_size * L2_CACHE_SIZE * sizeof(uint32_t));
+    if (!s->l2_cache)
+    {
+        rc = VERR_NO_MEMORY;
+        goto fail;
+    }
+
+    return VINF_SUCCESS;
+
+ fail:
+    Log(("vmdk_open failed with %Vrc\n", rc));
+    if (s->l1_backup_table)
+        RTMemFree(s->l1_backup_table);
+    if (s->l1_table)
+        RTMemFree(s->l1_table);
+    if (s->l2_cache)
+        RTMemFree(s->l2_cache);
+    RTFileClose(s->File);
+    return rc;
+}
+
+static uint64_t get_cluster_offset(BDRVVmdkState *s,
+                                   uint64_t offset, int allocate)
+{
+    unsigned int l1_index, l2_offset, l2_index;
+    int min_index, i, j;
+    uint32_t min_count, *l2_table, tmp;
+    uint64_t cluster_offset;
+    int rc;
+    
+    l1_index = (offset >> 9) / s->l1_entry_sectors;
+    if (l1_index >= s->l1_size)
+        return 0;
+    l2_offset = s->l1_table[l1_index];
+    if (!l2_offset)
+        return 0;
+    for(i = 0; i < L2_CACHE_SIZE; i++) {
+        if (l2_offset == s->l2_cache_offsets[i]) {
+            /* increment the hit count */
+            if (++s->l2_cache_counts[i] == 0xffffffff) {
+                for(j = 0; j < L2_CACHE_SIZE; j++) {
+                    s->l2_cache_counts[j] >>= 1;
+                }
+            }
+            l2_table = s->l2_cache + (i * s->l2_size);
+            goto found;
+        }
+    }
+    /* not found: load a new entry in the least used one */
+    min_index = 0;
+    min_count = 0xffffffff;
+    for(i = 0; i < L2_CACHE_SIZE; i++) {
+        if (s->l2_cache_counts[i] < min_count) {
+            min_count = s->l2_cache_counts[i];
+            min_index = i;
+        }
+    }
+    l2_table = s->l2_cache + (min_index * s->l2_size);
+    rc = RTFileSeek(s->File, (int64_t)l2_offset * VMDK_GEOMETRY_SECTOR_SIZE, RTFILE_SEEK_BEGIN, NULL);
+    AssertRC(rc);
+    if (VBOX_FAILURE(rc))
+        return 0;
+    rc = RTFileRead(s->File, l2_table, s->l2_size * sizeof(uint32_t), NULL);
+    AssertRC(rc);
+    if (VBOX_FAILURE(rc))
+        return 0;
+    s->l2_cache_offsets[min_index] = l2_offset;
+    s->l2_cache_counts[min_index] = 1;
+ found:
+    l2_index = ((offset >> 9) / s->cluster_sectors) % s->l2_size;
+    cluster_offset = le32_to_cpu(l2_table[l2_index]);
+    if (!cluster_offset) {
+        if (!allocate)
+            return 0;
+        rc = RTFileSeek(s->File, 0, RTFILE_SEEK_END, &cluster_offset);
+        AssertRC(rc);
+        if (VBOX_FAILURE(rc))
+            return 0;
+        rc = RTFileSetSize(s->File, cluster_offset + (s->cluster_sectors << 9));
+        AssertRC(rc);
+        if (VBOX_FAILURE(rc))
+            return 0;
+        cluster_offset >>= 9;
+        /* update L2 table */
+        tmp = cpu_to_le32(cluster_offset);
+        l2_table[l2_index] = tmp;
+        rc = RTFileSeek(s->File, ((int64_t)l2_offset * VMDK_GEOMETRY_SECTOR_SIZE) + (l2_index * sizeof(tmp)), RTFILE_SEEK_BEGIN, NULL);
+        AssertRC(rc);
+        if (VBOX_FAILURE(rc))
+            return 0;
+        rc = RTFileWrite(s->File, &tmp, sizeof(tmp), NULL);
+        AssertRC(rc);
+        if (VBOX_FAILURE(rc))
+            return 0;
+        /* update backup L2 table */
+        if (s->l1_backup_table_offset != 0) {
+            l2_offset = s->l1_backup_table[l1_index];
+
+            rc = RTFileSeek(s->File, ((int64_t)l2_offset * VMDK_GEOMETRY_SECTOR_SIZE) + (l2_index * sizeof(tmp)), RTFILE_SEEK_BEGIN, NULL);
+            AssertRC(rc);
+            if (VBOX_FAILURE(rc))
+                return 0;
+            rc = RTFileWrite(s->File, &tmp, sizeof(tmp), NULL);
+            AssertRC(rc);
+            if (VBOX_FAILURE(rc))
+                return 0;
+        }
+    }
+    cluster_offset <<= 9;
+    return cluster_offset;
+}
+
+static int vmdk_is_allocated(BDRVVmdkState *s, int64_t sector_num, 
+                             int nb_sectors, int *pnum)
+{
+    int index_in_cluster, n;
+    uint64_t cluster_offset;
+
+    cluster_offset = get_cluster_offset(s, sector_num << 9, 0);
+    index_in_cluster = sector_num % s->cluster_sectors;
+    n = s->cluster_sectors - index_in_cluster;
+    if (n > nb_sectors)
+        n = nb_sectors;
+    *pnum = n;
+    return (cluster_offset != 0);
+}
+
+static int vmdk_read(BDRVVmdkState *s, int64_t sector_num, 
+                    uint8_t *buf, int nb_sectors)
+{
+    int index_in_cluster, n;
+    uint64_t cluster_offset;
+    
+    while (nb_sectors > 0) {
+        cluster_offset = get_cluster_offset(s, sector_num << 9, 0);
+        index_in_cluster = sector_num % s->cluster_sectors;
+        n = s->cluster_sectors - index_in_cluster;
+        if (n > nb_sectors)
+            n = nb_sectors;
+        if (!cluster_offset) {
+            memset(buf, 0, VMDK_GEOMETRY_SECTOR_SIZE * n);
+        } else {
+            int rc = RTFileSeek(s->File, cluster_offset + index_in_cluster * VMDK_GEOMETRY_SECTOR_SIZE, RTFILE_SEEK_BEGIN, NULL);
+            AssertRC(rc);
+            if (VBOX_FAILURE(rc))
+                return rc;
+
+            rc = RTFileRead(s->File, buf, n * VMDK_GEOMETRY_SECTOR_SIZE, NULL);
+            AssertRC(rc);
+            if (VBOX_FAILURE(rc))
+                return rc;
+        }
+        nb_sectors -= n;
+        sector_num += n;
+        buf += n * VMDK_GEOMETRY_SECTOR_SIZE;
+    }
+    return VINF_SUCCESS;
+}
+
+static int vmdk_write(BDRVVmdkState *s, int64_t sector_num, 
+                     const uint8_t *buf, int nb_sectors)
+{
+    int index_in_cluster, n;
+    uint64_t cluster_offset;
+
+    while (nb_sectors > 0) {
+        index_in_cluster = sector_num & (s->cluster_sectors - 1);
+        n = s->cluster_sectors - index_in_cluster;
+        if (n > nb_sectors)
+            n = nb_sectors;
+        cluster_offset = get_cluster_offset(s, sector_num << 9, 1);
+        if (!cluster_offset)
+            return VERR_IO_SECTOR_NOT_FOUND;
+
+        int rc = RTFileSeek(s->File, cluster_offset + index_in_cluster * VMDK_GEOMETRY_SECTOR_SIZE, RTFILE_SEEK_BEGIN, NULL);
+        AssertRC(rc);
+        if (VBOX_FAILURE(rc))
+            return rc;
+
+        rc = RTFileWrite(s->File, buf, n * VMDK_GEOMETRY_SECTOR_SIZE, NULL);
+        AssertRC(rc);
+        if (VBOX_FAILURE(rc))
+            return rc;
+
+        nb_sectors -= n;
+        sector_num += n;
+        buf += n * VMDK_GEOMETRY_SECTOR_SIZE;
+    }
+    return VINF_SUCCESS;
+}
+
+static void vmdk_close(BDRVVmdkState *s)
+{
+    RTMemFree(s->l1_table);
+    RTMemFree(s->l2_cache);
+    RTFileClose(s->File);
+}
+
+static void vmdk_flush(BDRVVmdkState *s)
+{
+    RTFileFlush(s->File);
+}
+
+
+/**
+ * Get read/write mode of VMDK HDD.
+ *
+ * @returns Disk ReadOnly status.
+ * @returns true if no one VMDK image is opened in HDD container.
+ */
+IDER3DECL(bool) VMDKDiskIsReadOnly(PVMDKDISK pDisk)
+{
+    /* sanity check */
+    Assert(pDisk);
+    AssertMsg(pDisk->u32Signature == VMDKDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+    LogFlow(("VmdkDiskIsReadOnly: returns %u\n", pDisk->VmdkState.fReadOnly));
+    return pDisk->VmdkState.fReadOnly;
+}
+
+
+/**
+ * Get disk size of VMDK HDD container.
+ *
+ * @returns Virtual disk size in bytes.
+ * @returns 0 if no one VMDK image is opened in HDD container.
+ */
+IDER3DECL(uint64_t) VMDKDiskGetSize(PVMDKDISK pDisk)
+{
+    /* sanity check */
+    Assert(pDisk);
+    AssertMsg(pDisk->u32Signature == VMDKDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+    return pDisk->VmdkState.total_sectors * VMDK_GEOMETRY_SECTOR_SIZE;
+}
+
+/**
+ * Get block size of VMDK HDD container.
+ *
+ * @returns VDI image block size in bytes.
+ * @returns 0 if no one VMDK image is opened in HDD container.
+ */
+IDER3DECL(unsigned) VMDKDiskGetBlockSize(PVMDKDISK pDisk)
+{
+    /* sanity check */
+    Assert(pDisk);
+    AssertMsg(pDisk->u32Signature == VMDKDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+    return VMDK_GEOMETRY_SECTOR_SIZE;
+}
+
+/**
+ * Get virtual disk geometry stored in image file.
+ *
+ * @returns VBox status code.
+ * @param   pDisk           Pointer to VMDK HDD container.
+ * @param   pcCylinders     Where to store the number of cylinders. NULL is ok.
+ * @param   pcHeads         Where to store the number of heads. NULL is ok.
+ * @param   pcSectors       Where to store the number of sectors. NULL is ok.
+ */
+IDER3DECL(int) VMDKDiskGetGeometry(PVMDKDISK pDisk, unsigned *pcCylinders, unsigned *pcHeads, unsigned *pcSectors)
+{
+    /* sanity check */
+    Assert(pDisk);
+    AssertMsg(pDisk->u32Signature == VMDKDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+    /** @todo not initialized */
+    if (pcCylinders)
+        *pcCylinders = pDisk->Geometry.cCylinders;
+    if (pcHeads)
+        *pcHeads = pDisk->Geometry.cHeads;
+    if (pcSectors)
+        *pcSectors = pDisk->Geometry.cSectors;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Store virtual disk geometry into base image file of HDD container.
+ *
+ * Note that in case of unrecoverable error all images of HDD container will be closed.
+ *
+ * @returns VBox status code.
+ * @param   pDisk           Pointer to VMDK HDD container.
+ * @param   cCylinders      Number of cylinders.
+ * @param   cHeads          Number of heads.
+ * @param   cSectors        Number of sectors.
+ */
+IDER3DECL(int) VMDKDiskSetGeometry(PVMDKDISK pDisk, unsigned cCylinders, unsigned cHeads, unsigned cSectors)
+{
+    LogFlow(("VMDKDiskSetGeometry: C/H/S = %u/%u/%u\n", cCylinders, cHeads, cSectors));
+    /* sanity check */
+    Assert(pDisk);
+    AssertMsg(pDisk->u32Signature == VMDKDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+    pDisk->Geometry.cCylinders = cCylinders;
+    pDisk->Geometry.cHeads     = cHeads;
+    pDisk->Geometry.cSectors   = cSectors;
+    pDisk->Geometry.cbSector   = VMDK_GEOMETRY_SECTOR_SIZE;
+
+    /** @todo Update header information in base image file. */
+    return VINF_SUCCESS;
+}
+
+/**
+ * Get number of opened images in HDD container.
+ *
+ * @returns Number of opened images for HDD container. 0 if no images is opened.
+ * @param   pDisk           Pointer to VMDK HDD container.
+ */
+IDER3DECL(int) VMDKDiskGetImagesCount(PVMDKDISK pDisk)
+{
+    /* sanity check */
+    Assert(pDisk);
+    AssertMsg(pDisk->u32Signature == VMDKDISK_SIGNATURE, ("u32Signature=%08x\n", pDisk->u32Signature));
+
+    return 1;
+}
+
+/*******************************************************************************
+*   PDM interface                                                              *
+*******************************************************************************/
+
+/**
+ * Construct a VBox HDD media driver instance.
+ *
+ * @returns VBox status.
+ * @param   pDrvIns     The driver instance data.
+ *                      If the registration structure is needed, pDrvIns->pDrvReg points to it.
+ * @param   pCfgHandle  Configuration node handle for the driver. Use this to obtain the configuration
+ *                      of the driver instance. It's also found in pDrvIns->pCfgHandle, but like
+ *                      iInstance it's expected to be used a bit in this function.
+ */
+static DECLCALLBACK(int) vmdkConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
+{
+    LogFlow(("vmdkConstruct:\n"));
+    PVMDKDISK pData = PDMINS2DATA(pDrvIns, PVMDKDISK);
+
+    /*
+     * Init the static parts.
+     */
+    pDrvIns->IBase.pfnQueryInterface    = vmdkQueryInterface;
+    pData->pDrvIns = pDrvIns;
+
+    /* IMedia */
+    pData->IMedia.pfnRead               = vmdkRead;
+    pData->IMedia.pfnWrite              = vmdkWrite;
+    pData->IMedia.pfnFlush              = vmdkFlush;
+    pData->IMedia.pfnGetSize            = vmdkGetSize;
+    pData->IMedia.pfnGetUuid            = vmdkGetUuid;
+    pData->IMedia.pfnIsReadOnly         = vmdkIsReadOnly;
+    pData->IMedia.pfnBiosGetGeometry    = vmdkBiosGetGeometry;
+    pData->IMedia.pfnBiosSetGeometry    = vmdkBiosSetGeometry;
+    pData->IMedia.pfnBiosGetTranslation = vmdkBiosGetTranslation;
+    pData->IMedia.pfnBiosSetTranslation = vmdkBiosSetTranslation;
+
+    /*
+     * Validate and read top level configuration.
+     */
+    char *pszName;
+    int rc = CFGMR3QueryStringAlloc(pCfgHandle, "Path", &pszName);
+    if (VBOX_FAILURE(rc))
+        return PDMDRV_SET_ERROR(pDrvIns, rc,
+                                N_("VHDD: Configuration error: Querying \"Path\" as string failed"));
+
+    /** True if the media is readonly. */
+    bool fReadOnly;
+    rc = CFGMR3QueryBool(pCfgHandle, "ReadOnly", &fReadOnly);
+    if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+        fReadOnly = false;
+    else if (VBOX_FAILURE(rc))
+    {
+        MMR3HeapFree(pszName);
+        return PDMDRV_SET_ERROR(pDrvIns, rc,
+                                N_("VHDD: Configuration error: Querying \"ReadOnly\" as boolean failed"));
+    }
+
+    /*
+     * Open the image.
+     */
+    rc = vmdk_open(&pData->VmdkState, pszName, fReadOnly);
+    if (VBOX_SUCCESS(rc))
+        Log(("vmdkConstruct: Opened '%s' in %s mode\n", pszName, VMDKDiskIsReadOnly(pData) ? "read-only" : "read-write"));
+    else
+        AssertMsgFailed(("Failed to open image '%s' rc=%Vrc\n", pszName, rc));
+
+    MMR3HeapFree(pszName);
+    pszName = NULL;
+
+    return rc;
+}
+
+/**
+ * Destruct a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param   pDrvIns     The driver instance data.
+ */
+static DECLCALLBACK(void) vmdkDestruct(PPDMDRVINS pDrvIns)
+{
+    LogFlow(("vmdkDestruct:\n"));
+    PVMDKDISK pData = PDMINS2DATA(pDrvIns, PVMDKDISK);
+    vmdk_close(&pData->VmdkState);
+}
+
+/**
+ * When the VM has been suspended we'll change the image mode to read-only
+ * so that main and others can read the VDIs. This is important when
+ * saving state and so forth.
+ *
+ * @param   pDrvIns     The driver instance data.
+ */
+static DECLCALLBACK(void) vmdkSuspend(PPDMDRVINS pDrvIns)
+{
+    LogFlow(("vmdkSuspend:\n"));
+    PVMDKDISK pData = PDMINS2DATA(pDrvIns, PVMDKDISK);
+    if (!VMDKDiskIsReadOnly(pData))
+    {
+        /** @todo does this even make sense? the vdi method locks the whole file, but don't we close it afterwards?? */
+        //int rc = vmdkChangeImageMode(pData, true);
+        //AssertRC(rc);
+    }
+}
+
+/**
+ * Before the VM resumes we'll have to undo the read-only mode change
+ * done in vmdkSuspend.
+ *
+ * @param   pDrvIns     The driver instance data.
+ */
+static DECLCALLBACK(void) vmdkResume(PPDMDRVINS pDrvIns)
+{
+    LogFlow(("vmdkSuspend:\n"));
+    PVMDKDISK pData = PDMINS2DATA(pDrvIns, PVMDKDISK);
+    if (!VMDKDiskIsReadOnly(pData))
+    {
+        /** @todo does this even make sense? the vdi method locks the whole file, but don't we close it afterwards?? */
+        //int rc = vmdkChangeImageMode(pData, false);
+        //AssertRC(rc);
+    }
+}
+
+
+/** @copydoc PDMIMEDIA::pfnGetSize */
+static DECLCALLBACK(uint64_t) vmdkGetSize(PPDMIMEDIA pInterface)
+{
+    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
+    uint64_t cb = VMDKDiskGetSize(pData);
+    LogFlow(("vmdkGetSize: returns %#llx (%llu)\n", cb, cb));
+    return cb;
+}
+
+/**
+ * Get stored media geometry - BIOS property.
+ *
+ * @see PDMIMEDIA::pfnBiosGetGeometry for details.
+ */
+static DECLCALLBACK(int) vmdkBiosGetGeometry(PPDMIMEDIA pInterface, uint32_t *pcCylinders, uint32_t *pcHeads, uint32_t *pcSectors)
+{
+    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
+    int rc = VMDKDiskGetGeometry(pData, pcCylinders, pcHeads, pcSectors);
+    if (VBOX_SUCCESS(rc))
+    {
+        LogFlow(("vmdkBiosGetGeometry: returns VINF_SUCCESS\n"));
+        return VINF_SUCCESS;
+    }
+    Log(("vmdkBiosGetGeometry: The Bios geometry data was not available.\n"));
+    return VERR_PDM_GEOMETRY_NOT_SET;
+}
+
+/**
+ * Set stored media geometry - BIOS property.
+ *
+ * @see PDMIMEDIA::pfnBiosSetGeometry for details.
+ */
+static DECLCALLBACK(int) vmdkBiosSetGeometry(PPDMIMEDIA pInterface, uint32_t cCylinders, uint32_t cHeads, uint32_t cSectors)
+{
+    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
+    int rc = VMDKDiskSetGeometry(pData, cCylinders, cHeads, cSectors);
+    LogFlow(("vmdkBiosSetGeometry: returns %Vrc (%d,%d,%d)\n", rc, cCylinders, cHeads, cSectors));
+    return rc;
+}
+
+/**
+ * Read bits.
+ *
+ * @see PDMIMEDIA::pfnRead for details.
+ */
+static DECLCALLBACK(int) vmdkRead(PPDMIMEDIA pInterface, uint64_t off, void *pvBuf, size_t cbRead)
+{
+    LogFlow(("vmdkRead: off=%#llx pvBuf=%p cbRead=%d\n", off, pvBuf, cbRead));
+    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
+    int rc = vmdk_read(&pData->VmdkState, off >> VMDK_GEOMETRY_SECTOR_SHIFT, (uint8_t *)pvBuf, cbRead >> VMDK_GEOMETRY_SECTOR_SHIFT);
+    if (VBOX_SUCCESS(rc))
+        Log2(("vmdkRead: off=%#llx pvBuf=%p cbRead=%d\n"
+              "%.*Vhxd\n",
+              off, pvBuf, cbRead, cbRead, pvBuf));
+    LogFlow(("vmdkRead: returns %Vrc\n", rc));
+    return rc;
+}
+
+/**
+ * Write bits.
+ *
+ * @see PDMIMEDIA::pfnWrite for details.
+ */
+static DECLCALLBACK(int) vmdkWrite(PPDMIMEDIA pInterface, uint64_t off, const void *pvBuf, size_t cbWrite)
+{
+    LogFlow(("vmdkWrite: off=%#llx pvBuf=%p cbWrite=%d\n", off, pvBuf, cbWrite));
+    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
+    Log2(("vmdkWrite: off=%#llx pvBuf=%p cbWrite=%d\n"
+          "%.*Vhxd\n",
+          off, pvBuf, cbWrite, cbWrite, pvBuf));
+    int rc = vmdk_write(&pData->VmdkState, off >> VMDK_GEOMETRY_SECTOR_SHIFT, (const uint8_t *)pvBuf, cbWrite >> VMDK_GEOMETRY_SECTOR_SHIFT);
+    LogFlow(("vmdkWrite: returns %Vrc\n", rc));
+    return rc;
+}
+
+/**
+ * Flush bits to media.
+ *
+ * @see PDMIMEDIA::pfnFlush for details.
+ */
+static DECLCALLBACK(int) vmdkFlush(PPDMIMEDIA pInterface)
+{
+    LogFlow(("vmdkFlush:\n"));
+    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
+    vmdk_flush(&pData->VmdkState);
+    int rc = VINF_SUCCESS;
+    LogFlow(("vmdkFlush: returns %Vrc\n", rc));
+    return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnGetUuid */
+static DECLCALLBACK(int) vmdkGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid)
+{
+    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
+    /** @todo */
+    int rc = VINF_SUCCESS;
+    NOREF(pData);
+    LogFlow(("vmdkGetUuid: returns %Vrc ({%Vuuid})\n", rc, pUuid));
+    return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnIsReadOnly */
+static DECLCALLBACK(bool) vmdkIsReadOnly(PPDMIMEDIA pInterface)
+{
+    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
+    LogFlow(("vmdkIsReadOnly: returns %d\n", VMDKDiskIsReadOnly(pData)));
+    return VMDKDiskIsReadOnly(pData);
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosGetTranslation */
+static DECLCALLBACK(int) vmdkBiosGetTranslation(PPDMIMEDIA pInterface,
+                                               PPDMBIOSTRANSLATION penmTranslation)
+{
+    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
+    int rc = VINF_SUCCESS;
+    NOREF(pData);
+    *penmTranslation = PDMBIOSTRANSLATION_AUTO; /** @todo */
+    LogFlow(("vmdkBiosGetTranslation: returns %Vrc (%d)\n", rc, *penmTranslation));
+    return rc;
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosSetTranslation */
+static DECLCALLBACK(int) vmdkBiosSetTranslation(PPDMIMEDIA pInterface,
+                                               PDMBIOSTRANSLATION enmTranslation)
+{
+    PVMDKDISK pData = PDMIMEDIA_2_VMDKDISK(pInterface);
+    /** @todo */
+    int rc = VINF_SUCCESS;
+    NOREF(pData);
+    LogFlow(("vmdkBiosSetTranslation: returns %Vrc (%d)\n", rc, enmTranslation));
+    return rc;
+}
+
+
+/**
+ * Queries an interface to the driver.
+ *
+ * @returns Pointer to interface.
+ * @returns NULL if the interface was not supported by the driver.
+ * @param   pInterface          Pointer to this interface structure.
+ * @param   enmInterface        The requested interface identification.
+ * @thread  Any thread.
+ */
+static DECLCALLBACK(void *) vmdkQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
+{
+    PPDMDRVINS pDrvIns = PDMIBASE_2_DRVINS(pInterface);
+    PVMDKDISK pData = PDMINS2DATA(pDrvIns, PVMDKDISK);
+    switch (enmInterface)
+    {
+        case PDMINTERFACE_BASE:
+            return &pDrvIns->IBase;
+        case PDMINTERFACE_MEDIA:
+            return &pData->IMedia;
+        default:
+            return NULL;
+    }
+}
+
+
+/**
+ * VMDK driver registration record.
+ */
+const PDMDRVREG g_DrvVmdkHDD =
+{
+    /* u32Version */
+    PDM_DRVREG_VERSION,
+    /* szDriverName */
+    "VdmkHDD",
+    /* pszDescription */
+    "VMDK media driver.",
+    /* fFlags */
+    PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+    /* fClass. */
+    PDM_DRVREG_CLASS_MEDIA,
+    /* cMaxInstances */
+    ~0,
+    /* cbInstance */
+    sizeof(VMDKDISK),
+    /* pfnConstruct */
+    vmdkConstruct,
+    /* pfnDestruct */
+    vmdkDestruct,
+    /* pfnIOCtl */
+    NULL,
+    /* pfnPowerOn */
+    NULL,
+    /* pfnReset */
+    NULL,
+    /* pfnSuspend */
+    vmdkSuspend,
+    /* pfnResume */
+    vmdkResume,
+    /* pfnDetach */
+    NULL
+};
