VmdkHDDCore.cpp revision a0240ff4f7663045c848fdbc192ea3d4d9f70a11
/** $Id$ */
/** @file
* VMDK Disk image, Core Code.
*/
/*
* 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;
* 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.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_VD_VMDK
#include "VBoxHDD-newInternal.h"
/*******************************************************************************
* Constants And Macros, Structures and Typedefs *
*******************************************************************************/
/**
* Magic number for hosted images created by VMware Workstation 4, VMware
* Workstation 5, VMware Server or VMware Player.
*/
/** VMDK hosted sparse extent header. */
#pragma pack(1)
typedef struct SparseExtentHeader
{
bool uncleanShutdown;
char singleEndLineChar;
char nonEndLineChar;
char doubleEndLineChar1;
char doubleEndLineChar2;
#pragma pack()
#ifdef VBOX_WITH_VMDK_ESX
/** @todo the ESX code is not tested, not used, and lacks error messages. */
/**
* Magic number for images created by VMware GSX Server 3 or ESX Server 3.
*/
#pragma pack(1)
typedef struct COWDisk_Header
{
/* The spec incompletely documents quite a few further fields, but states
* that they are not used by the current format. Replace them by padding. */
char reserved1[1604];
char reserved2[8];
char padding[396];
#pragma pack()
#endif /* VBOX_WITH_VMDK_ESX */
#define VMDK_SECTOR2BYTE(u) ((u) << 9)
#define VMDK_BYTE2SECTOR(u) ((u) >> 9)
/**
* VMDK extent type.
*/
typedef enum VMDKETYPE
{
/** Hosted sparse extent. */
/** Flat extent. */
/** Zero extent. */
#ifdef VBOX_WITH_VMDK_ESX
,
/** ESX sparse extent. */
#endif /* VBOX_WITH_VMDK_ESX */
} VMDKETYPE, *PVMDKETYPE;
/**
* VMDK access type for a extent.
*/
typedef enum VMDKACCESS
{
/** No access allowed. */
VMDKACCESS_NOACCESS = 0,
/** Read-only access. */
/** Read-write access. */
} VMDKACCESS, *PVMDKACCESS;
/**
* VMDK extent data structure.
*/
typedef struct VMDKEXTENT
{
/** File handle. */
/** Base name of the image extent. */
char *pszBasename;
/** Full name of the image extent. */
char *pszFullname;
/** Number of sectors in this extent. */
/** Number of sectors per block (grain in VMDK speak). */
/** Starting sector number of descriptor. */
/** Size of descriptor in sectors. */
/** Starting sector number of grain directory. */
/** Starting sector number of redundant grain directory. */
/** Total number of metadata sectors. */
/** Nominal size (i.e. as described by the descriptor) of this extent. */
/** Sector offset (i.e. as described by the descriptor) of this extent. */
/** Number of entries in a grain table. */
/** Number of sectors reachable via a grain directory entry. */
/** Number of entries in the grain directory. */
/** Pointer to the next free sector. Legacy information. Do not use. */
/** Number of this extent in the list of images. */
/** Pointer to the descriptor (NULL if no descriptor in this extent). */
char *pDescData;
/** Pointer to the grain directory. */
/** Pointer to the redundant grain directory. */
/** Type of this extent. */
/** Access to this extent. */
/** Flag whether this extent is marked as unclean. */
bool fUncleanShutdown;
/** Flag whether the metadata in the extent header needs to be updated. */
bool fMetaDirty;
/** Reference to the image in which this extent is used. Do not use this
* on a regular basis to avoid passing pImage references to functions
* explicitly. */
} VMDKEXTENT, *PVMDKEXTENT;
/**
* Grain table cache size. Allocated per image.
*/
#define VMDK_GT_CACHE_SIZE 256
/**
* Grain table block size. Smaller than an actual grain table block to allow
* more grain table blocks to be cached without having to allocate excessive
* amounts of memory for the cache.
*/
#define VMDK_GT_CACHELINE_SIZE 128
/**
* Maximum number of lines in a descriptor file. Not worth the effort of
* making it variable. Descriptor files are generally very short (~20 lines).
*/
#define VMDK_DESCRIPTOR_LINES_MAX 100
/**
* Parsed descriptor information. Allows easy access and update of the
* descriptor (whether separate file or not). Free form text files suck.
*/
typedef struct VMDKDESCRIPTOR
{
/** Line number of first entry of the disk descriptor. */
unsigned uFirstDesc;
/** Line number of first entry in the extent description. */
unsigned uFirstExtent;
/** Line number of first disk database entry. */
unsigned uFirstDDB;
/** Total number of lines. */
unsigned cLines;
/** Total amount of memory available for the descriptor. */
/** Set if descriptor has been changed and not yer written to disk. */
bool fDirty;
/** Array of pointers to the data in the descriptor. */
char *aLines[VMDK_DESCRIPTOR_LINES_MAX];
/** Array of line indices pointing to the next non-comment line. */
unsigned aNextLines[VMDK_DESCRIPTOR_LINES_MAX];
/**
* extent.
*/
typedef struct VMDKGTCACHEENTRY
{
/** Extent number for which this entry is valid. */
/** GT data block number. */
/** Data part of the cache entry. */
/**
* Cache data structure for blocks of grain table entries. For now this is a
* fixed size direct mapping cache, but this should be adapted to the size of
* the sparse image and maybe converted to a set-associative cache. The
* implementation below implements a write-through cache with write allocate.
*/
typedef struct VMDKGTCACHE
{
/** Cache entries. */
/** Number of cache entries (currently unused). */
unsigned cEntries;
} VMDKGTCACHE, *PVMDKGTCACHE;
/**
* Complete VMDK image data structure. Mainly a collection of extents and a few
* extra global data fields.
*/
typedef struct VMDKIMAGE
{
unsigned cExtents;
/** Base image name. */
const char *pszFilename;
/** Descriptor file if applicable. */
/** Error callback. */
/** Opaque data for error callback. */
void *pvErrorUser;
/** Open flags passed by VBoxHD layer. */
unsigned uOpenFlags;
/** Image type. */
/** Total size of the image. */
/** BIOS translation mode. */
/** Physical geometry of this image, cylinders. */
/** Physical geometry of this image, heads. */
/** Physical geometry of this image, sectors. */
/** Image UUID. */
/** Image modification UUID. */
/** Parent image UUID. */
/** Pointer to the grain table cache, if this image contains sparse extents. */
/** Pointer to the descriptor (NULL if no separate descriptor file). */
char *pDescData;
/** Allocation size of the descriptor file. */
/** Parsed descriptor file content. */
} VMDKIMAGE, *PVMDKIMAGE;
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
static int vmdkPreprocessDescriptor(PVMDKIMAGE pImage, char *pDescData, size_t cbDescData, PVMDKDESCRIPTOR pDescriptor);
#ifdef VBOX_WITH_VMDK_ESX
#endif /* VBOX_WITH_VMDK_ESX */
static int vmdkOpen(const char *pszFilename, unsigned uOpenFlags, PFNVDERROR pfnError, void *pvErrorUser, void **ppvBackendData);
static int vmdkClose(void *pBackendData);
{
return rc;
}
{
int rc = VINF_SUCCESS;
unsigned i;
if (!pGD)
{
rc = VERR_NO_MEMORY;
goto out;
}
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: could not read grain directory for file '%s'"), pExtent->pszFullname);
goto out;
}
if (pExtent->uSectorRGD)
{
if (!pRGD)
{
rc = VERR_NO_MEMORY;
goto out;
}
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: could not read redundant grain directory for file '%s'"), pExtent->pszFullname);
goto out;
}
/* Check grain table and redundant grain table for consistency. */
if (!pTmpGT1)
{
rc = VERR_NO_MEMORY;
goto out;
}
if (!pTmpGT2)
{
rc = VERR_NO_MEMORY;
goto out;
}
{
/* If no grain table is allocated skip the entry. */
continue;
{
/* Just one grain directory entry refers to a not yet allocated
* grain table or both grain directory copies refer to the same
* grain table. Not allowed. */
rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: inconsistent references to grain directory in file '%s'"), pExtent->pszFullname);
goto out;
}
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error reading grain table in file '%s'"), pExtent->pszFullname);
goto out;
}
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error reading backup grain table in file '%s'"), pExtent->pszFullname);
goto out;
}
{
rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: inconsistency between grain table and backup grain table"));
goto out;
}
}
/** @todo figure out what to do for unclean VMDKs. */
}
out:
if (VBOX_FAILURE(rc))
return rc;
}
{
if (pExtent->pszBasename)
{
}
{
}
{
}
}
static int vmdkStringUnquote(PVMDKIMAGE pImage, const char *pszStr, char **ppszUnquoted, char **ppszNext)
{
char *pszQ;
char *pszUnquoted;
/* Skip over whitespace. */
pszStr++;
if (*pszStr++ != '"')
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrectly quoted value in descriptor"));
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrectly quoted value in descriptor"));
if (!pszUnquoted)
return VERR_NO_MEMORY;
if (ppszNext)
return VINF_SUCCESS;
}
{
const char *pszValue;
while (uStart != 0)
{
{
/* Key matches, check if there is a '=' (preceded by whitespace). */
pszValue++;
if (*pszValue == '=')
{
break;
}
}
}
return !!uStart;
}
unsigned uStart,
{
char *pszTmp;
unsigned uLast;
while (uStart != 0)
{
{
/* Key matches, check if there is a '=' (preceded by whitespace). */
pszTmp++;
if (*pszTmp == '=')
{
pszTmp++;
break;
}
}
}
if (uStart)
{
/* Key already exists, replace existing value. */
/* Check for buffer overflow. */
}
else
{
/* Key doesn't exist, append it after the last entry in this category. */
/* Check for buffer overflow. */
{
if (pDescriptor->aNextLines[i])
pDescriptor->aNextLines[i]++;
}
pDescriptor->cLines++;
}
pDescriptor->fDirty = true;
return VINF_SUCCESS;
}
{
const char *pszValue;
return VERR_VDI_VALUE_NOT_FOUND;
}
{
const char *pszValue;
char *pszValueUnquoted;
return VERR_VDI_VALUE_NOT_FOUND;
if (VBOX_FAILURE(rc))
return rc;
return rc;
}
{
const char *pszValue;
char *pszValueUnquoted;
return VERR_VDI_VALUE_NOT_FOUND;
if (VBOX_FAILURE(rc))
return rc;
return rc;
}
int vmdkDescDDBSetUuid(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor, const char *pszKey, PCRTUUID pUuid)
{
char *pszUuid;
if (VBOX_FAILURE(rc))
return rc;
return rc;
}
int vmdkDescDDBSetU32(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor, const char *pszKey, uint32_t uValue)
{
char *pszValue;
if (VBOX_FAILURE(rc))
return rc;
return rc;
}
static int vmdkPreprocessDescriptor(PVMDKIMAGE pImage, char *pDescData, size_t cbDescData, PVMDKDESCRIPTOR pDescriptor)
{
int rc = VINF_SUCCESS;
unsigned cLine = 0, uLastNonEmptyLine = 0;
while (*pTmp != '\0')
{
if (cLine >= VMDK_DESCRIPTOR_LINES_MAX)
{
goto out;
}
{
if (*pTmp == '\r')
{
{
rc = vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: unsupported end of line in descriptor"));
goto out;
}
else
{
/* Get rid of CR character. */
*pTmp = '\0';
}
}
pTmp++;
}
/* Get rid of LF character. */
if (*pTmp == '\n')
{
*pTmp = '\0';
pTmp++;
}
}
/* Pointer right after the end of the used part of the buffer. */
{
rc = vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: descriptor does not start as expected"));
goto out;
}
/* Initialize those, because we need to be able to reopen an image. */
pDescriptor->uFirstDesc = 0;
pDescriptor->uFirstExtent = 0;
pDescriptor->uFirstDDB = 0;
for (unsigned i = 0; i < cLine; i++)
{
{
{
/* An extent descriptor. */
{
/* Incorrect ordering of entries. */
rc = vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect ordering of entries in descriptor"));
goto out;
}
if (!pDescriptor->uFirstExtent)
{
pDescriptor->uFirstExtent = i;
uLastNonEmptyLine = 0;
}
}
{
/* A disk database entry. */
{
/* Incorrect ordering of entries. */
rc = vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect ordering of entries in descriptor"));
goto out;
}
if (!pDescriptor->uFirstDDB)
{
pDescriptor->uFirstDDB = i;
uLastNonEmptyLine = 0;
}
}
else
{
/* A normal entry. */
{
/* Incorrect ordering of entries. */
rc = vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect ordering of entries in descriptor"));
goto out;
}
if (!pDescriptor->uFirstDesc)
{
pDescriptor->uFirstDesc = i;
uLastNonEmptyLine = 0;
}
}
if (uLastNonEmptyLine)
uLastNonEmptyLine = i;
}
}
out:
return rc;
}
{
int rc;
unsigned cExtents;
unsigned uLine;
if (VBOX_FAILURE(rc))
return rc;
/* Check version, must be 1. */
if (VBOX_FAILURE(rc))
if (uVersion != 1)
return vmdkError(pImage, VERR_VDI_UNSUPPORTED_VERSION, RT_SRC_POS, N_("VMDK: unsupported format version in descriptor"));
/* Count the number of extent config entries. */
uLine != 0;
/* nothing */;
{
/* Monolithic image, must have only one extent (already opened). */
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: monolithic image may only have one extent"));
}
{
/* Non-monolithic image, extents need to be allocated. */
if (VBOX_FAILURE(rc))
return rc;
}
{
/* Access type of the extent. */
{
pszLine += 2;
}
{
pszLine += 6;
}
{
pszLine += 8;
}
else
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
if (*pszLine++ != ' ')
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
/* Nominal size of the extent. */
if (VBOX_FAILURE(rc))
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
if (*pszLine++ != ' ')
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
/* Type of the extent. */
#ifdef VBOX_WITH_VMDK_ESX
/** @todo Add the ESX extent types. Not necessary for now because
* the ESX extent types are only used inside an ESX server. They are
* automatically converted if the VMDK is exported. */
#endif /* VBOX_WITH_VMDK_ESX */
{
pszLine += 6;
}
{
pszLine += 4;
}
{
pszLine += 4;
}
else
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
{
/* This one has no basename or offset. */
if (*pszLine == ' ')
pszLine++;
if (*pszLine != '\0')
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
}
else
{
/* All other extent types have basename and optional offset. */
if (*pszLine++ != ' ')
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
/* Basename of the image. Surrounded by quotes. */
if (VBOX_FAILURE(rc))
return rc;
if (*pszLine == ' ')
{
pszLine++;
if (*pszLine != '\0')
{
/* Optional offset in extent specified. */
if (VBOX_FAILURE(rc))
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
}
}
if (*pszLine != '\0')
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description"));
}
}
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error getting CHS geometry from extent description"));
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error getting CHS geometry from extent description"));
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error getting CHS geometry from extent description"));
else
/* Get image UUID. */
if (rc == VERR_VDI_VALUE_NOT_FOUND)
{
/* Image without UUID. Probably created by VMware and not yet used
* mode, so don't bother producing a sensible UUID otherwise. */
else
{
if (VBOX_FAILURE(rc))
return rc;
if (VBOX_FAILURE(rc))
}
}
else if (VBOX_FAILURE(rc))
return rc;
/* Get image modification UUID. */
if (rc == VERR_VDI_VALUE_NOT_FOUND)
{
/* Image without UUID. Probably created by VMware and not yet used
* mode, so don't bother producing a sensible UUID otherwise. */
else
{
if (VBOX_FAILURE(rc))
return rc;
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing image modification UUID in descriptor"));
}
}
else if (VBOX_FAILURE(rc))
return rc;
/* Get UUID of parent image. */
&pImage->ParentUuid);
if (rc == VERR_VDI_VALUE_NOT_FOUND)
{
/* Image without UUID. Probably created by VMware and not yet used
* mode, so don't bother producing a sensible UUID otherwise. */
else
{
if (VBOX_FAILURE(rc))
return rc;
if (VBOX_FAILURE(rc))
}
}
else if (VBOX_FAILURE(rc))
return rc;
return VINF_SUCCESS;
}
{
int rc = VINF_SUCCESS;
{
/* Separate descriptor file. */
uOffset = 0;
cbLimit = 0;
}
else
{
/* Embedded descriptor file. */
}
{
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error writing descriptor for '%s'"), pImage->pszFilename);
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error writing descriptor for '%s'"), pImage->pszFilename);
uOffset++;
}
if (cbLimit)
{
/* Inefficient, but simple. */
{
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error writing descriptor for '%s'"), pImage->pszFilename);
uOffset++;
}
}
else
{
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error truncating descriptor file '%s'"), pImage->pszFilename);
}
return rc;
}
{
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error reading extent header of file '%s'"), pExtent->pszFullname);
goto out;
}
{
rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect magic/version in extent header of file '%s'"), pExtent->pszFullname);
goto out;
}
/* The image must be a multiple of a sector in size. If not, it means the
* image is at least truncated, or even seriously garbled. */
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error getting size of file '%s'"), pExtent->pszFullname);
goto out;
}
{
rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: file '%s' is corrupted by CR/LF translation"), pExtent->pszFullname);
goto out;
}
/* The spec says that this must be a power of two and greater than 8,
* but probably they meant not less than 8. */
{
rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: invalid extent grain size %u for file '%s'"), pExtent->cSectorsPerGrain, pExtent->pszFullname);
goto out;
}
{
rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: inconsistent embedded descriptor config for file '%s'"), pExtent->pszFullname);
goto out;
}
/* This code requires that a grain table must hold a power of two multiple
* of the number of entries per GT cache entry. */
{
rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: grain table cache size problem for file '%s'"), pExtent->pszFullname);
goto out;
}
{
}
else
{
/** @todo this is just guesswork, the spec doesn't document this
* properly and I don't have a vmdk without RGD. */
pExtent->uSectorRGD = 0;
}
{
rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect grain directory size for file '%s'"), pExtent->pszFullname);
goto out;
}
out:
if (VBOX_FAILURE(rc))
{
}
return rc;
}
{
{
}
else
{
/** @todo this is just guesswork, the spec doesn't document this
* properly and I don't have a vmdk without RGD. */
}
if (VBOX_FAILURE(rc))
rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error writing extent header to file '%s'"), pExtent->pszFullname);
return rc;
}
#ifdef VBOX_WITH_VMDK_ESX
{
if (VBOX_FAILURE(rc))
goto out;
{
goto out;
}
/* The spec says that this must be between 1 sector and 1MB. This code
* assumes it's a power of two, so check that requirement, too. */
|| pExtent->cSectorsPerGrain == 0
{
goto out;
}
pExtent->uDescriptorSector = 0;
pExtent->cDescriptorSectors = 0;
pExtent->uSectorRGD = 0;
pExtent->uOverheadSectors = 0;
{
goto out;
}
{
/* Inconsistency detected. Computed number of GD entries doesn't match
* stored value. Better be safe than sorry. */
goto out;
}
out:
if (VBOX_FAILURE(rc))
{
}
return rc;
}
#endif /* VBOX_WITH_VMDK_ESX */
{
{
}
if (pExtent->pszFullname)
{
}
{
}
}
{
int rc = VINF_SUCCESS;
if (pImage)
{
for (unsigned i = 0; i < cExtents; i++)
{
}
}
else
rc = VERR_NO_MEMORY;
return rc;
}
{
int rc = VINF_SUCCESS;
/** @todo check the image file name for invalid characters, especially double quotes. */
/** @todo check whether the same file is used somewhere else. don't open any file twice, leads to locking problems and can cause trouble with file caching. */
/*
* Open the image.
*/
if (VBOX_FAILURE(rc))
{
/* Do NOT signal an appropriate error here, as the VD layer has the
* choice of retrying the open if it failed. */
goto out;
}
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error reading the magic number from file '%s'"), pszFilename);
goto out;
}
/* Handle the file according to its magic number. */
{
/* It's a hosted sparse single-extent image. */
if (VBOX_FAILURE(rc))
goto out;
/* The opened file is passed to the extent. No separate descriptor
* file, so no need to keep anything open for the image. */
if (VBOX_FAILURE(rc))
goto out;
/* As we're dealing with a monolithic sparse image here, there must
* be a descriptor embedded in the image file. */
{
rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: monolithic image without descriptor in file '%s'"), pszFilename);
goto out;
}
/* Read the descriptor from the extent. */
{
rc = VERR_NO_MEMORY;
goto out;
}
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: read error for descriptor in file '%s'"), pExtent->pszFullname);
goto out;
}
if (VBOX_FAILURE(rc))
goto out;
/* Mark the extent as unclean if opened in read-write mode. */
if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
{
pExtent->fUncleanShutdown = true;
pExtent->fMetaDirty = true;
}
}
else
{
{
rc = VERR_NO_MEMORY;
goto out;
}
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: read error for descriptor in file '%s'"), pExtent->pszFullname);
goto out;
}
{
/* Likely the read is truncated. Better fail a bit too early
* (normally the descriptor is much smaller than our buffer). */
rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: cannot read descriptor in file '%s'"), pExtent->pszFullname);
goto out;
}
if (VBOX_FAILURE(rc))
goto out;
{
if (pExtent->pszBasename)
{
/* Hack to figure out whether the specified name in the
* extent descriptor is absolute. Doesn't always work, but
* should be good enough for now. */
/** @todo implement proper path absolute check. */
{
if (!pszFilename)
{
rc = VERR_NO_MEMORY;
goto out;
}
}
else
{
if (!pszDirname)
{
rc = VERR_NO_MEMORY;
goto out;
}
char *pszFullname;
if (VBOX_FAILURE(rc))
goto out;
}
}
else
{
case VMDKETYPE_HOSTED_SPARSE:
if (VBOX_FAILURE(rc))
{
/* Do NOT signal an appropriate error here, as the VD
* layer has the choice of retrying the open if it
* failed. */
goto out;
}
if (VBOX_FAILURE(rc))
goto out;
/* Mark the extent as unclean if opened in read-write mode. */
if (!(uOpenFlags & VD_OPEN_FLAGS_READONLY))
{
pExtent->fUncleanShutdown = true;
pExtent->fMetaDirty = true;
}
break;
case VMDKETYPE_FLAT:
if (VBOX_FAILURE(rc))
{
/* Do NOT signal an appropriate error here, as the VD
* layer has the choice of retrying the open if it
* failed. */
goto out;
}
break;
case VMDKETYPE_ZERO:
/* Nothing to do. */
break;
default:
}
}
}
/* Make sure this is not reached accidentally with an error status. */
/* Update the image metadata now in case has changed. */
if (VBOX_FAILURE(rc))
goto out;
/* Figure out a few per-image constants from the extents. */
{
#ifdef VBOX_WITH_VMDK_ESX
#endif /* VBOX_WITH_VMDK_ESX */
)
{
/* Here used to be a check whether the nominal size of an extent
* is a multiple of the grain size. The spec says that this is
* always the case, but unfortunately some files out there in the
* wild violate the spec (e.g. ReactOS 0.3.1). */
}
}
{
{
break;
}
}
/* Allocate grain table cache if any sparse extent is present. */
{
#ifdef VBOX_WITH_VMDK_ESX
#endif /* VBOX_WITH_VMDK_ESX */
)
{
/* Allocate grain table cache. */
{
rc = VERR_NO_MEMORY;
goto out;
}
for (unsigned i = 0; i < VMDK_GT_CACHE_SIZE; i++)
{
}
break;
}
}
out:
if (VBOX_FAILURE(rc))
return rc;
}
{
{
/* Mark all extents as clean. */
{
#ifdef VBOX_WITH_VMDK_ESX
#endif /* VBOX_WITH_VMDK_ESX */
)
{
}
}
}
(void)vmdkFlushImage(pImage);
{
}
{
}
}
{
int rc = VINF_SUCCESS;
/* Update descriptor if changed. */
{
if (VBOX_FAILURE(rc))
goto out;
}
{
{
{
case VMDKETYPE_HOSTED_SPARSE:
if (VBOX_FAILURE(rc))
goto out;
break;
#ifdef VBOX_WITH_VMDK_ESX
case VMDKETYPE_ESX_SPARSE:
/** @todo update the header. */
break;
#endif /* VBOX_WITH_VMDK_ESX */
case VMDKETYPE_FLAT:
/* Nothing to do. */
break;
case VMDKETYPE_ZERO:
default:
AssertMsgFailed(("extent with type %d marked as dirty\n",
break;
}
}
{
case VMDKETYPE_HOSTED_SPARSE:
#ifdef VBOX_WITH_VMDK_ESX
case VMDKETYPE_ESX_SPARSE:
#endif /* VBOX_WITH_VMDK_ESX */
case VMDKETYPE_FLAT:
break;
case VMDKETYPE_ZERO:
/* No need to do anything for this extent. */
break;
default:
break;
}
}
out:
return rc;
}
static int vmdkFindExtent(PVMDKIMAGE pImage, uint64_t offSector, PVMDKEXTENT *ppExtent, uint64_t *puSectorInExtent)
{
int rc = VINF_SUCCESS;
{
{
break;
}
}
if (pExtent)
else
return rc;
}
{
/** @todo this hash function is quite simple, maybe use a better one which
* scrambles the bits better. */
}
{
int rc;
return VERR_OUT_OF_RANGE;
if (!uGTSector)
{
/* There is no grain table referenced by this grain directory
* entry. So there is absolutely no data in this area. */
*puExtentSector = 0;
return VINF_SUCCESS;
}
{
/* Cache miss, fetch data from disk. */
VMDK_SECTOR2BYTE(uGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
if (VBOX_FAILURE(rc))
return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot read grain table entry in file '%s'"), pExtent->pszFullname);
for (unsigned i = 0; i < VMDK_GT_CACHELINE_SIZE; i++)
}
if (uGrainSector)
else
*puExtentSector = 0;
return VINF_SUCCESS;
}
/**
* Internal. Allocates a new grain table (if necessary), writes the grain
* and updates the grain table. The cache is also updated by this operation.
* This is separate from vmdkGetSector, because that should be as fast as
* possible. Most code from vmdkGetSector also appears here.
*/
{
int rc;
return VERR_OUT_OF_RANGE;
if (!uGTSector)
{
/* There is no grain table referenced by this grain directory
* entry. So there is absolutely no data in this area. Allocate
* a new grain table and put the reference to it in the GDs. */
if (VBOX_FAILURE(rc))
return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error getting size of file '%s'"), pExtent->pszFullname);
/* Normally the grain table is preallocated for hosted sparse extents
* that support more than 32 bit sector numbers. So this shouldn't
* ever happen on a valid extent. */
if (uGTSector > UINT32_MAX)
return VERR_VDI_INVALID_HEADER;
/* Write grain table by writing the required number of grain table
* cache chunks. Avoids dynamic memory allocation, but is a bit
* slower. But as this is a pretty infrequently occurring case it
* should be acceptable. */
{
rc = RTFileWriteAt(pExtent->File, VMDK_SECTOR2BYTE(uGTSector) + i * sizeof(aGTDataTmp), aGTDataTmp, sizeof(aGTDataTmp), NULL);
if (VBOX_FAILURE(rc))
return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write grain table allocation in file '%s'"), pExtent->pszFullname);
}
{
if (VBOX_FAILURE(rc))
return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error getting size of file '%s'"), pExtent->pszFullname);
/* Write backup grain table by writing the required number of grain
* table cache chunks. Avoids dynamic memory allocation, but is a
* bit slower. But as this is a pretty infrequently occurring case
* it should be acceptable. */
{
rc = RTFileWriteAt(pExtent->File, VMDK_SECTOR2BYTE(uRGTSector) + i * sizeof(aGTDataTmp), aGTDataTmp, sizeof(aGTDataTmp), NULL);
if (VBOX_FAILURE(rc))
return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write backup grain table allocation in file '%s'"), pExtent->pszFullname);
}
}
/* Update the grain directory on disk (doing it before writing the
* grain table will result in a garbled extent if the operation is
* aborted for some reason. Otherwise the worst that can happen is
* some unused sectors in the extent. */
rc = RTFileWriteAt(pExtent->File, VMDK_SECTOR2BYTE(pExtent->uSectorGD) + uGDIndex * sizeof(uGTSectorLE), &uGTSectorLE, sizeof(uGTSectorLE), NULL);
if (VBOX_FAILURE(rc))
return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write grain directory entry in file '%s'"), pExtent->pszFullname);
{
rc = RTFileWriteAt(pExtent->File, VMDK_SECTOR2BYTE(pExtent->uSectorRGD) + uGDIndex * sizeof(uRGTSectorLE), &uRGTSectorLE, sizeof(uRGTSectorLE), NULL);
if (VBOX_FAILURE(rc))
return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write backup grain directory entry in file '%s'"), pExtent->pszFullname);
}
/* As the final step update the in-memory copy of the GDs. */
}
if (VBOX_FAILURE(rc))
return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error getting size of file '%s'"), pExtent->pszFullname);
/* Write the data. */
if (VBOX_FAILURE(rc))
return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write allocated data block in file '%s'"), pExtent->pszFullname);
/* Update the grain table (and the cache). */
{
/* Cache miss, fetch data from disk. */
VMDK_SECTOR2BYTE(uGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
if (VBOX_FAILURE(rc))
return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot read allocated grain table entry in file '%s'"), pExtent->pszFullname);
for (unsigned i = 0; i < VMDK_GT_CACHELINE_SIZE; i++)
}
else
{
/* Cache hit. Convert grain table block back to disk format, otherwise
* the code below will write garbage for all but the updated entry. */
for (unsigned i = 0; i < VMDK_GT_CACHELINE_SIZE; i++)
}
/* Update grain table on disk. */
VMDK_SECTOR2BYTE(uGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
if (VBOX_FAILURE(rc))
return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write updated grain table in file '%s'"), pExtent->pszFullname);
{
/* Update backup grain table on disk. */
VMDK_SECTOR2BYTE(uRGTSector) + (uGTBlock % (pExtent->cGTEntries / VMDK_GT_CACHELINE_SIZE)) * sizeof(aGTDataTmp),
if (VBOX_FAILURE(rc))
return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write updated backup grain table in file '%s'"), pExtent->pszFullname);
}
#ifdef VBOX_WITH_VMDK_ESX
{
pExtent->fMetaDirty = true;
}
#endif /* VBOX_WITH_VMDK_ESX */
return rc;
}
static int vmdkOpen(const char *pszFilename, unsigned uOpenFlags, PFNVDERROR pfnError, void *pvErrorUser, void **ppvBackendData)
{
int rc;
/* Check open flags. Just readonly flag is supported. */
if (uOpenFlags & ~VD_OPEN_FLAGS_READONLY)
{
goto out;
}
if (!pImage)
{
rc = VERR_NO_MEMORY;
goto out;
}
if (VBOX_SUCCESS(rc))
{
if (VBOX_SUCCESS(rc))
*ppvBackendData = pImage;
}
out:
return rc;
}
static int vmdkClose(void *pBackendData)
{
int rc = VINF_SUCCESS;
/* Freeing a never allocated image (e.g. because the open failed) is
* not signalled as an error. After all nothing bad happens. */
if (pImage)
return rc;
}
static int vmdkRead(void *pBackendData, uint64_t uOffset, void *pvBuf, size_t cbRead, size_t *pcbActuallyRead)
{
int rc;
{
goto out;
}
&pExtent, &uSectorInExtent);
if (VBOX_FAILURE(rc))
goto out;
/* Check access permissions as defined in the extent descriptor. */
{
goto out;
}
/* Clip read range to remain in this extent. */
/* Handle the read according to the current extent type. */
{
case VMDKETYPE_HOSTED_SPARSE:
#ifdef VBOX_WITH_VMDK_ESX
case VMDKETYPE_ESX_SPARSE:
#endif /* VBOX_WITH_VMDK_ESX */
/* Clip read range to at most the rest of the grain. */
cbRead = RT_MIN(cbRead, VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain - (uSectorOffset % pExtent->cSectorsPerGrain)));
if (VBOX_FAILURE(rc))
goto out;
if (uSectorOffset == 0)
else
break;
case VMDKETYPE_FLAT:
break;
case VMDKETYPE_ZERO:
break;
}
out:
return rc;
}
static int vmdkWrite(void *pBackendData, uint64_t uOffset, const void *pvBuf, size_t cbWrite, size_t *pcbWriteProcess, size_t *pcbPreRead, size_t *pcbPostRead)
{
int rc;
{
goto out;
}
/* No size check here, will do that later when the extent is located.
* There are sparse images out there which according to the spec are
* invalid, because the total size is not a multiple of the grain size.
* Also for sparse images which are stitched together in odd ways (not at
* grain boundaries, and with the nominal size not being a multiple of the
* grain size), this would prevent writing to the last grain. */
&pExtent, &uSectorInExtent);
if (VBOX_FAILURE(rc))
goto out;
/* Check access permissions as defined in the extent descriptor. */
{
goto out;
}
/* Handle the write according to the current extent type. */
{
case VMDKETYPE_HOSTED_SPARSE:
#ifdef VBOX_WITH_VMDK_ESX
case VMDKETYPE_ESX_SPARSE:
#endif /* VBOX_WITH_VMDK_ESX */
/* Clip write range to at most the rest of the grain. */
cbWrite = RT_MIN(cbWrite, VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain - (uSectorOffset % pExtent->cSectorsPerGrain)));
if (VBOX_FAILURE(rc))
goto out;
if (uSectorOffset == 0)
{
{
/* Full block write to a previously unallocated block.
* Allocate GT and find out where to store the grain. */
}
else
{
/* Clip write range to remain in this extent. */
}
}
else
break;
case VMDKETYPE_FLAT:
/* Clip write range to remain in this extent. */
break;
case VMDKETYPE_ZERO:
/* Clip write range to remain in this extent. */
break;
}
if (pcbWriteProcess)
out:
return rc;
}
static int vmdkFlush(void *pBackendData)
{
return rc;
}
{
int rc = VINF_SUCCESS;
else
return rc;
}
{
if (pImage)
else
return 0;
}
static int vmdkGetGeometry(void *pBackendData, unsigned *pcCylinders, unsigned *pcHeads, unsigned *pcSectors)
{
int rc;
if (pImage)
{
if (pImage->cCylinders)
{
rc = VINF_SUCCESS;
}
else
}
else
return rc;
}
static int vmdkSetGeometry(void *pBackendData, unsigned cCylinders, unsigned cHeads, unsigned cSectors)
{
int rc;
if (pImage)
{
{
goto out;
}
"ddb.geometry.cylinders", cCylinders);
if (VBOX_FAILURE(rc))
goto out;
"ddb.geometry.heads", cHeads);
if (VBOX_FAILURE(rc))
goto out;
"ddb.geometry.sectors", cSectors);
if (VBOX_FAILURE(rc))
goto out;
rc = VINF_SUCCESS;
}
else
out:
return rc;
}
{
int rc;
if (pImage)
{
if (pImage->enmTranslation)
{
rc = VINF_SUCCESS;
}
else
}
else
pImage->enmTranslation));
return rc;
}
{
int rc;
if (pImage)
{
/** @todo maybe store this in the image descriptor */
rc = VINF_SUCCESS;
}
else
return rc;
}
static unsigned vmdkGetOpenFlags(void *pBackendData)
{
unsigned uOpenFlags;
if (pImage)
else
uOpenFlags = 0;
return uOpenFlags;
}
{
int rc;
const char *pszFilename;
/* Image must be opened and the new flags must be valid. Just readonly flag
* is supported. */
{
goto out;
}
/* Implement this operation via reopening the image. */
out:
return rc;
}
{
int rc;
if (pImage)
{
rc = VINF_SUCCESS;
}
else
return rc;
}
{
int rc;
if (pImage)
{
"ddb.uuid.image", pUuid);
if (VBOX_FAILURE(rc))
rc = VINF_SUCCESS;
}
else
return rc;
}
{
int rc;
if (pImage)
{
rc = VINF_SUCCESS;
}
else
return rc;
}
{
int rc;
if (pImage)
{
"ddb.uuid.modification", pUuid);
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing modification UUID in descriptor"));
rc = VINF_SUCCESS;
}
else
return rc;
}
{
int rc;
if (pImage)
{
rc = VINF_SUCCESS;
}
else
return rc;
}
{
int rc;
if (pImage)
{
"ddb.uuid.parent", pUuid);
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing parent image UUID in descriptor"));
rc = VINF_SUCCESS;
}
else
return rc;
}
{
/* pfnOpen */
/* pfnClose */
/* pfnRead */
/* pfnWrite */
/* pfnFlush */
/* pfnGetImageType */
/* pfnGetSize */
/* pfnGetGeometry */
/* pfnSetGeometry */
/* pfnGetTranslation */
/* pfnSetTranslation */
/* pfnGetOpenFlags */
/* pfnSetOpenFlags */
/* pfnGetUuid */
/* pfnGetUuid */
/* pfnGetModificationUuid */
/* pfnSetModificationUuid */
/* pfnGetParentUuid */
/* pfnSetParentUuid */
};