DrvVmdk.cpp revision da957c069c2a3c582fe265ff88170ce4c42b499d
/** @file
*
* VBox storage devices:
* VBox VMDK container implementation
*/
/*
* Copyright (C) 2006-2007 innotek GmbH
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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
* 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 "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)
#pragma pack(1)
typedef struct {
} VMDK3Header;
#pragma pack()
#pragma pack(1)
typedef struct {
char filler[1];
char check_bytes[4];
} VMDK4Header;
#pragma pack()
#define L2_CACHE_SIZE 16
typedef struct BDRVVmdkState {
/** File handle. */
bool fReadOnly;
unsigned int l1_size;
unsigned int l2_size;
unsigned int cluster_sectors;
#define VMDKDISK_SIGNATURE 0x8013ABCD
/**
* Harddisk geometry.
*/
#pragma pack(1)
typedef struct VMDKDISKGEOMETRY
{
/** Cylinders. */
/** Heads. */
/** Sectors per track. */
/** Sector size. (bytes per sector) */
#pragma pack()
/**
* VMDK HDD Container main structure, private part.
*/
typedef struct VMDKDISK
{
/** Hard disk geometry. */
/** The media interface. */
/** Pointer to the driver instance. */
/** 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. */
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
#if 0
{
if (buf_size < 4)
return 0;
if (magic == VMDK3_MAGIC ||
magic == VMDK4_MAGIC)
return 100;
else
return 0;
}
#endif
{
int l1_size;
/*
* Open the image.
*/
if (VBOX_FAILURE(rc))
{
if (!fReadOnly)
{
/* Try to open image for reading only. */
if (VBOX_SUCCESS(rc))
s->fReadOnly = true;
}
if (VBOX_FAILURE(rc))
return rc;
}
if (VBOX_FAILURE(rc))
goto fail;
if (magic == VMDK3_MAGIC)
{
if (VBOX_FAILURE(rc))
goto fail;
s->l1_backup_table_offset = 0;
/* fill in the geometry structure */
}
else if (magic == VMDK4_MAGIC)
{
if (VBOX_FAILURE(rc))
goto fail;
if (s->l1_entry_sectors <= 0)
{
goto fail;
}
/ s->l1_entry_sectors;
/* fill in the geometry structure */
/// @todo we should read these properties from the DDB section
// of the Disk DescriptorFile. So, the below values are just a
// quick hack.
(16 * 63)), 16383);
}
else
{
goto fail;
}
/* read the L1 table */
if (!s->l1_table)
{
rc = VERR_NO_MEMORY;
goto fail;
}
if (VBOX_FAILURE(rc))
goto fail;
if (VBOX_FAILURE(rc))
goto fail;
for(i = 0; i < s->l1_size; i++) {
}
if (s->l1_backup_table_offset) {
if (!s->l1_backup_table)
{
rc = VERR_NO_MEMORY;
goto fail;
}
if (VBOX_FAILURE(rc))
goto fail;
if (VBOX_FAILURE(rc))
goto fail;
for(i = 0; i < s->l1_size; i++) {
}
}
if (!s->l2_cache)
{
rc = VERR_NO_MEMORY;
goto fail;
}
return VINF_SUCCESS;
fail:
if (s->l1_backup_table)
RTMemFree(s->l1_backup_table);
if (s->l1_table)
if (s->l2_cache)
RTFileClose(s->File);
return rc;
}
{
int min_index, i, j;
int rc;
return 0;
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;
}
}
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;
}
}
if (VBOX_FAILURE(rc))
return 0;
if (VBOX_FAILURE(rc))
return 0;
if (!cluster_offset) {
if (!allocate)
return 0;
if (VBOX_FAILURE(rc))
return 0;
if (VBOX_FAILURE(rc))
return 0;
cluster_offset >>= 9;
/* update L2 table */
rc = RTFileSeek(s->File, ((int64_t)l2_offset * VMDK_GEOMETRY_SECTOR_SIZE) + (l2_index * sizeof(tmp)), RTFILE_SEEK_BEGIN, NULL);
if (VBOX_FAILURE(rc))
return 0;
if (VBOX_FAILURE(rc))
return 0;
/* update backup L2 table */
if (s->l1_backup_table_offset != 0) {
rc = RTFileSeek(s->File, ((int64_t)l2_offset * VMDK_GEOMETRY_SECTOR_SIZE) + (l2_index * sizeof(tmp)), RTFILE_SEEK_BEGIN, NULL);
if (VBOX_FAILURE(rc))
return 0;
if (VBOX_FAILURE(rc))
return 0;
}
}
cluster_offset <<= 9;
return cluster_offset;
}
#if 0
int nb_sectors, int *pnum)
{
int index_in_cluster, n;
n = s->cluster_sectors - index_in_cluster;
if (n > nb_sectors)
n = nb_sectors;
*pnum = n;
return (cluster_offset != 0);
}
#endif
{
int index_in_cluster, n;
while (nb_sectors > 0) {
n = s->cluster_sectors - index_in_cluster;
if (n > nb_sectors)
n = nb_sectors;
if (!cluster_offset) {
} else {
int rc = RTFileSeek(s->File, cluster_offset + index_in_cluster * VMDK_GEOMETRY_SECTOR_SIZE, RTFILE_SEEK_BEGIN, NULL);
if (VBOX_FAILURE(rc))
return rc;
if (VBOX_FAILURE(rc))
return rc;
}
nb_sectors -= n;
sector_num += n;
buf += n * VMDK_GEOMETRY_SECTOR_SIZE;
}
return VINF_SUCCESS;
}
{
int index_in_cluster, n;
while (nb_sectors > 0) {
n = s->cluster_sectors - index_in_cluster;
if (n > nb_sectors)
n = nb_sectors;
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);
if (VBOX_FAILURE(rc))
return 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)
{
RTFileClose(s->File);
}
static void vmdk_flush(BDRVVmdkState *s)
{
RTFileFlush(s->File);
}
/**
*
* @returns Disk ReadOnly status.
* @returns true if no one VMDK image is opened in HDD container.
*/
{
/* sanity check */
}
/**
* 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.
*/
{
/* sanity check */
}
/**
* 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.
*/
{
/* sanity check */
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 */
LogFlow(("VDIDiskGetGeometry: C/H/S = %u/%u/%u\n",
int rc = VINF_SUCCESS;
if ( pGeometry->cCylinders > 0
{
if (pcCylinders)
if (pcHeads)
if (pcSectors)
}
else
return rc;
}
/**
* 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)
{
/* sanity check */
/** @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.
*/
{
/* sanity check */
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.
*/
{
LogFlow(("drvVmdkConstruct:\n"));
/*
* Init the static parts.
*/
/* IMedia */
/*
* Validate and read top level configuration.
*/
char *pszName;
if (VBOX_FAILURE(rc))
N_("VHDD: Configuration error: Querying \"Path\" as string failed"));
/** True if the media is readonly. */
bool fReadOnly;
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
fReadOnly = false;
else if (VBOX_FAILURE(rc))
{
N_("VHDD: Configuration error: Querying \"ReadOnly\" as boolean failed"));
}
/*
* Open the image.
*/
if (VBOX_SUCCESS(rc))
Log(("drvVmdkConstruct: Opened '%s' in %s mode\n", pszName, VMDKDiskIsReadOnly(pData) ? "read-only" : "read-write"));
else
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.
*/
{
LogFlow(("drvVmdkDestruct:\n"));
}
/**
* 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.
*/
{
LogFlow(("drvVmdkSuspend:\n"));
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 drvVmdkSuspend.
*
* @param pDrvIns The driver instance data.
*/
{
LogFlow(("drvVmdkSuspend:\n"));
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 */
{
return cb;
}
/**
* Get stored media geometry - BIOS property.
*
* @see PDMIMEDIA::pfnBiosGetGeometry for details.
*/
static DECLCALLBACK(int) drvVmdkBiosGetGeometry(PPDMIMEDIA pInterface, uint32_t *pcCylinders, uint32_t *pcHeads, uint32_t *pcSectors)
{
if (VBOX_SUCCESS(rc))
{
LogFlow(("drvVmdkBiosGetGeometry: returns VINF_SUCCESS\n"));
return VINF_SUCCESS;
}
Log(("drvVmdkBiosGetGeometry: 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) drvVmdkBiosSetGeometry(PPDMIMEDIA pInterface, uint32_t cCylinders, uint32_t cHeads, uint32_t cSectors)
{
return rc;
}
/**
* Read bits.
*
* @see PDMIMEDIA::pfnRead for details.
*/
static DECLCALLBACK(int) drvVmdkRead(PPDMIMEDIA pInterface, uint64_t off, void *pvBuf, size_t cbRead)
{
int rc = vmdk_read(&pData->VmdkState, off >> VMDK_GEOMETRY_SECTOR_SHIFT, (uint8_t *)pvBuf, cbRead >> VMDK_GEOMETRY_SECTOR_SHIFT);
if (VBOX_SUCCESS(rc))
Log2(("drvVmdkRead: off=%#llx pvBuf=%p cbRead=%d\n"
"%.*Vhxd\n",
return rc;
}
/**
* Write bits.
*
* @see PDMIMEDIA::pfnWrite for details.
*/
static DECLCALLBACK(int) drvVmdkWrite(PPDMIMEDIA pInterface, uint64_t off, const void *pvBuf, size_t cbWrite)
{
Log2(("drvVmdkWrite: off=%#llx pvBuf=%p cbWrite=%d\n"
"%.*Vhxd\n",
int rc = vmdk_write(&pData->VmdkState, off >> VMDK_GEOMETRY_SECTOR_SHIFT, (const uint8_t *)pvBuf, cbWrite >> VMDK_GEOMETRY_SECTOR_SHIFT);
return rc;
}
/**
* Flush bits to media.
*
* @see PDMIMEDIA::pfnFlush for details.
*/
{
LogFlow(("drvVmdkFlush:\n"));
int rc = VINF_SUCCESS;
return rc;
}
/** @copydoc PDMIMEDIA::pfnGetUuid */
{
/** @todo */
int rc = VINF_SUCCESS;
return rc;
}
/** @copydoc PDMIMEDIA::pfnIsReadOnly */
{
return VMDKDiskIsReadOnly(pData);
}
/** @copydoc PDMIMEDIA::pfnBiosGetTranslation */
{
int rc = VINF_SUCCESS;
return rc;
}
/** @copydoc PDMIMEDIA::pfnBiosSetTranslation */
{
/** @todo */
int rc = VINF_SUCCESS;
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.
*/
{
switch (enmInterface)
{
case PDMINTERFACE_BASE:
case PDMINTERFACE_MEDIA:
default:
return NULL;
}
}
/**
* VMDK driver registration record.
*/
const PDMDRVREG g_DrvVmdkHDD =
{
/* u32Version */
/* szDriverName */
"VmdkHDD",
/* pszDescription */
"VMDK media driver.",
/* fFlags */
/* fClass. */
/* cMaxInstances */
~0,
/* cbInstance */
sizeof(VMDKDISK),
/* pfnConstruct */
/* pfnDestruct */
/* pfnIOCtl */
NULL,
/* pfnPowerOn */
NULL,
/* pfnReset */
NULL,
/* pfnSuspend */
/* pfnResume */
/* pfnDetach */
};