VmdkHDDCore.cpp revision 3e04f45aeb634f1256d0510780bf77d4fdefd926
/** $Id$ */
/** @file
* VMDK Disk image, Core Code.
*/
/*
* 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.
*/
/*******************************************************************************
* 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. */
const char *pszBasename;
/** Full name of the image extent. */
const 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 100U
/**
* 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 yet 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. */
/** Image flags defined during creation or determined during open. */
unsigned uImageFlags;
/** 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);
{
return rc;
}
/**
* Internal: truncate a string (at a UTF8 code point boundary) and encode the
* critical non-ASCII characters.
*/
static char *vmdkEncodeString(const char *psz)
{
/** @todo implement me. */
}
/**
* Internal: decode a string and store it into the specified string.
*/
{
/** @todo implement me. */
if (!cb)
return VINF_SUCCESS;
return VINF_SUCCESS;
}
{
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 in '%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 in '%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 '%s'"), pExtent->pszFullname);
goto out;
}
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error reading grain table in '%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 '%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 in '%s'"), pExtent->pszFullname);
goto out;
}
}
/** @todo figure out what to do for unclean VMDKs. */
}
out:
if (VBOX_FAILURE(rc))
return rc;
}
{
int rc = VINF_SUCCESS;
unsigned i;
if (fPreAlloc)
else
cbGTRounded = 0;
if (!pGD)
{
rc = VERR_NO_MEMORY;
goto out;
}
if (!pRGD)
{
rc = VERR_NO_MEMORY;
goto out;
}
cbOverhead = RT_ALIGN_64(VMDK_SECTOR2BYTE(uStartSector) + 2 * (cbGDRounded + cbGTRounded), VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain));
if (VBOX_FAILURE(rc))
goto out;
if (fPreAlloc)
{
for (i = 0; i < pExtent->cGDEntries; i++)
{
pRGD[i] = uOffsetSectors;
/* Write the redundant grain directory entry to disk. */
rc = RTFileWriteAt(pExtent->File, VMDK_SECTOR2BYTE(pExtent->uSectorRGD) + i * sizeof(uGTSectorLE), &uGTSectorLE, sizeof(uGTSectorLE), NULL);
if (VBOX_FAILURE(rc))
return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write new redundant grain directory entry in '%s'"), pExtent->pszFullname);
}
for (i = 0; i < pExtent->cGDEntries; i++)
{
pGD[i] = uOffsetSectors;
/* Write the grain directory entry to disk. */
rc = RTFileWriteAt(pExtent->File, VMDK_SECTOR2BYTE(pExtent->uSectorGD) + i * sizeof(uGTSectorLE), &uGTSectorLE, sizeof(uGTSectorLE), NULL);
if (VBOX_FAILURE(rc))
return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: cannot write new grain directory entry in '%s'"), pExtent->pszFullname);
}
}
out:
if (VBOX_FAILURE(rc))
return rc;
}
{
{
}
{
}
}
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 in '%s'"), pImage->pszFilename);
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrectly quoted value in descriptor in '%s'"), pImage->pszFilename);
if (!pszUnquoted)
return VERR_NO_MEMORY;
if (ppszNext)
return VINF_SUCCESS;
}
const char *pszLine)
{
return vmdkError(pImage, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too big in '%s'"), pImage->pszFilename);
pDescriptor->cLines++;
pDescriptor->fDirty = true;
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 = 0;
while (uStart != 0)
{
{
/* Key matches, check if there is a '=' (preceded by whitespace). */
pszTmp++;
if (*pszTmp == '=')
{
pszTmp++;
break;
}
}
}
if (uStart)
{
if (pszValue)
{
/* Key already exists, replace existing value. */
/* Check for buffer overflow. */
return vmdkError(pImage, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too big in '%s'"), pImage->pszFilename);
}
else
{
{
if (pDescriptor->aNextLines[i])
else
}
pDescriptor->cLines--;
/* Adjust starting line numbers of following descriptor sections. */
pDescriptor->uFirstDDB--;
}
}
else
{
/* Key doesn't exist, append it after the last entry in this category. */
if (!pszValue)
{
/* Key doesn't exist, and it should be removed. Simply a no-op. */
return VINF_SUCCESS;
}
/* Check for buffer overflow. */
return vmdkError(pImage, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too big in '%s'"), pImage->pszFilename);
{
else
pDescriptor->aNextLines[i] = 0;
}
pDescriptor->cLines++;
/* Adjust starting line numbers of following descriptor sections. */
pDescriptor->uFirstDDB++;
}
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;
}
{
char *pszValueQuoted;
if (VBOX_FAILURE(rc))
return rc;
return rc;
}
{
if (!uEntry)
return;
/* Move everything including the \0 in the entry marking the end of buffer. */
{
if (pDescriptor->aNextLines[i])
else
}
pDescriptor->cLines--;
if (pDescriptor->uFirstDDB)
pDescriptor->uFirstDDB--;
return;
}
{
char *pszTmp;
char szExt[1024];
/* Find last entry in extent description. */
while (uStart)
{
}
if (enmType == VMDKETYPE_ZERO)
{
}
else
{
if (!uSectorOffset)
else
}
/* Check for buffer overflow. */
return vmdkError(pImage, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too big in '%s'"), pImage->pszFilename);
{
else
pDescriptor->aNextLines[i] = 0;
}
pDescriptor->cLines++;
/* Adjust starting line numbers of following descriptor sections. */
pDescriptor->uFirstDDB++;
pDescriptor->fDirty = true;
return VINF_SUCCESS;
}
{
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;
}
{
const char *pszValue;
char *pszValueUnquoted;
return VERR_VDI_VALUE_NOT_FOUND;
if (VBOX_FAILURE(rc))
return rc;
return rc;
}
static int vmdkDescDDBSetStr(PVMDKIMAGE pImage, PVMDKDESCRIPTOR pDescriptor, const char *pszKey, const char *pszVal)
{
int rc;
char *pszValQuoted;
if (pszVal)
{
if (VBOX_FAILURE(rc))
return rc;
}
else
pszValQuoted = NULL;
if (pszValQuoted)
return rc;
}
static 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)
{
rc = vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: descriptor too big in '%s'"), pImage->pszFilename);
goto out;
}
{
if (*pTmp == '\r')
{
{
rc = vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: unsupported end of line in descriptor in '%s'"), pImage->pszFilename);
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 in '%s'"), pImage->pszFilename);
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 in '%s'"), pImage->pszFilename);
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 in '%s'"), pImage->pszFilename);
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 in '%s'"), pImage->pszFilename);
goto out;
}
if (!pDescriptor->uFirstDesc)
{
pDescriptor->uFirstDesc = i;
uLastNonEmptyLine = 0;
}
}
if (uLastNonEmptyLine)
uLastNonEmptyLine = i;
}
}
out:
return rc;
}
static int vmdkCreateDescriptor(PVMDKIMAGE pImage, char *pDescData, size_t cbDescData, PVMDKDESCRIPTOR pDescriptor)
{
int rc;
pDescriptor->uFirstDesc = 0;
pDescriptor->uFirstExtent = 0;
pDescriptor->uFirstDDB = 0;
pDescriptor->cLines = 0;
pDescriptor->fDirty = false;
if (VBOX_FAILURE(rc))
goto out;
if (VBOX_FAILURE(rc))
goto out;
if (VBOX_FAILURE(rc))
goto out;
if (VBOX_FAILURE(rc))
goto out;
if (VBOX_FAILURE(rc))
goto out;
if (VBOX_FAILURE(rc))
goto out;
/* The trailing space is created by VMware, too. */
if (VBOX_FAILURE(rc))
goto out;
if (VBOX_FAILURE(rc))
goto out;
if (VBOX_FAILURE(rc))
goto out;
if (VBOX_FAILURE(rc))
goto out;
/* Now that the framework is in place, use the normal functions to insert
* the remaining keys. */
char szBuf[9];
if (VBOX_FAILURE(rc))
goto out;
if (VBOX_FAILURE(rc))
goto out;
if (VBOX_FAILURE(rc))
goto out;
out:
return rc;
}
{
int rc;
unsigned cExtents;
unsigned uLine;
if (VBOX_FAILURE(rc))
return rc;
/* Check version, must be 1. */
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error finding key 'version' in descriptor in '%s'"), pImage->pszFilename);
if (uVersion != 1)
return vmdkError(pImage, VERR_VDI_UNSUPPORTED_VERSION, RT_SRC_POS, N_("VMDK: unsupported format version in descriptor in '%s'"), pImage->pszFilename);
/* Get image creation type and determine image flags. */
const char *pszCreateType;
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: cannot get image type from descriptor in '%s'"), pImage->pszFilename);
else
pImage->uImageFlags = 0;
RTStrFree((char *)(void *)pszCreateType);
/* 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 in '%s'"), pImage->pszFilename);
}
{
/* 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 in '%s'"), pImage->pszFilename);
if (*pszLine++ != ' ')
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
/* 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 in '%s'"), pImage->pszFilename);
if (*pszLine++ != ' ')
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
/* 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 in '%s'"), pImage->pszFilename);
{
/* 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 in '%s'"), pImage->pszFilename);
}
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 in '%s'"), pImage->pszFilename);
/* Basename of the image. Surrounded by quotes. */
char *pszBasename;
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 in '%s'"), pImage->pszFilename);
}
}
if (*pszLine != '\0')
return vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: parse error in extent description in '%s'"), pImage->pszFilename);
}
}
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error getting CHS geometry from extent description in '%s'"), pImage->pszFilename);
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error getting CHS geometry from extent description in '%s'"), pImage->pszFilename);
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error getting CHS geometry from extent description in '%s'"), pImage->pszFilename);
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))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing image UUID in descriptor in '%s'"), pImage->pszFilename);
}
}
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 in '%s'"), pImage->pszFilename);
}
}
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))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing parent UUID in descriptor in '%s'"), pImage->pszFilename);
}
}
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. */
}
{
return vmdkError(pImage, VERR_BUFFER_OVERFLOW, RT_SRC_POS, N_("VMDK: descriptor too long in '%s'"), pImage->pszFilename);
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error writing descriptor in '%s'"), pImage->pszFilename);
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error writing descriptor in '%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 in '%s'"), pImage->pszFilename);
uOffset++;
}
}
else
{
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error truncating descriptor in '%s'"), pImage->pszFilename);
}
return rc;
}
{
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error reading extent header in '%s'"), pExtent->pszFullname);
goto out;
}
{
rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: incorrect magic/version in extent header in '%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 in '%s'"), pExtent->pszFullname);
goto out;
}
{
rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: corrupted by CR/LF translation in '%s'"), 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 in '%s'"), pExtent->cSectorsPerGrain, pExtent->pszFullname);
goto out;
}
{
rc = vmdkError(pExtent->pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: inconsistent embedded descriptor config in '%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 in '%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 in '%s'"), pExtent->pszFullname);
goto out;
}
out:
if (VBOX_FAILURE(rc))
vmdkFreeExtentData(pExtent, false);
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 in '%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->cOverheadSectors = 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))
vmdkFreeExtentData(pExtent, false);
return rc;
}
#endif /* VBOX_WITH_VMDK_ESX */
{
{
}
{
if ( fDelete
&& pExtent->pszFullname)
}
if (pExtent->pszBasename)
{
}
if (pExtent->pszFullname)
{
}
}
{
/* Allocate grain table cache if any sparse extent is present. */
{
#ifdef VBOX_WITH_VMDK_ESX
#endif /* VBOX_WITH_VMDK_ESX */
)
{
/* Allocate grain table cache. */
return VERR_NO_MEMORY;
for (unsigned i = 0; i < VMDK_GT_CACHE_SIZE; i++)
{
}
break;
}
}
return VINF_SUCCESS;
}
{
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 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(pImage, rc, RT_SRC_POS, N_("VMDK: error reading the magic number in '%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(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: monolithic image without descriptor in '%s'"), pszFilename);
goto out;
}
/* Read the descriptor from the extent. */
{
rc = VERR_NO_MEMORY;
goto out;
}
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: read error for descriptor in '%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))
{
goto out;
}
{
/* Likely the read is truncated. Better fail a bit too early
* (normally the descriptor is much smaller than our buffer). */
rc = vmdkError(pImage, VERR_VDI_INVALID_HEADER, RT_SRC_POS, N_("VMDK: cannot read descriptor in '%s'"), pszFilename);
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. */
char *pszFullname;
/** @todo implement proper path absolute check. */
{
if (!pszFullname)
{
rc = VERR_NO_MEMORY;
goto out;
}
}
else
{
if (!pszDirname)
{
rc = VERR_NO_MEMORY;
goto out;
}
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;
}
}
if (VBOX_FAILURE(rc))
goto out;
out:
if (VBOX_FAILURE(rc))
vmdkFreeImage(pImage, false);
return rc;
}
static int vmdkCreateImage(PVMDKIMAGE pImage, const char *pszFilename, VDIMAGETYPE enmType, uint64_t cbSize, unsigned uImageFlags, const char *pszComment, uint32_t cCylinders, uint32_t cHeads, uint32_t cSectors)
{
int rc;
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new descriptor in '%s'"), pszFilename);
goto out;
}
if ( enmType == VD_IMAGE_TYPE_FIXED
{
/* Fixed images and split images in general have a separate descriptor
* file. This is the more complicated case, as it requires setting up
* potentially more than one extent, including filename generation. */
if ( enmType == VD_IMAGE_TYPE_FIXED
{
/* As the comment is misused, zap it so that no garbage comment
* is set below. */
pszComment = NULL;
{
/* Full raw disk access. This requires setting up a descriptor
* file and open the (flat) raw disk. */
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new extent list in '%s'"), pszFilename);
goto out;
}
if (VBOX_FAILURE(rc))
{
goto out;
}
/* Set up basename for extent description. Cannot use StrDup. */
if (!pszBasename)
{
rc = VERR_NO_MEMORY;
goto out;
}
/* For raw disks the full name is identical to the base name. */
if (!pExtent->pszFullname)
{
rc = VERR_NO_MEMORY;
goto out;
}
pExtent->uSectorOffset = 0;
pExtent->fMetaDirty = false;
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not set the image type in '%s'"), pszFilename);
goto out;
}
/* Open flat image, the raw disk. */
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not open raw disk file '%s'"), pExtent->pszFullname);
goto out;
}
}
else
{
/* Raw partition access. This requires setting up a descriptor
* file, write the partition information to a flat extent and
* open all the (flat) raw disk partitions. */
/* First pass over the partitions to determine how many
* extents we need. One partition can require up to 4 extents.
* One to skip over unpartitioned space, one for the
* partitioning data, one to skip over unpartitioned space
* and one for the partition data. */
unsigned cExtents = 0;
for (unsigned i = 0; i < pRaw->cPartitions; i++)
{
if (pPart->cbPartitionData)
{
{
rc = vmdkError(pImage, VERR_INVALID_PARAMETER, RT_SRC_POS, N_("VMDK: cannot go backwards for partitioning information in '%s'"), pszFilename);
goto out;
cExtents++;
cExtents++;
}
if (pPart->cbPartition)
{
{
rc = vmdkError(pImage, VERR_INVALID_PARAMETER, RT_SRC_POS, N_("VMDK: cannot go backwards for partition data in '%s'"), pszFilename);
goto out;
cExtents++;
cExtents++;
}
}
/* Another extent for filling up the rest of the image. */
cExtents++;
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new extent list in '%s'"), pszFilename);
goto out;
}
if (VBOX_FAILURE(rc))
{
goto out;
}
/* Create base filename for the partition table extent. */
/** @todo remove fixed buffer. */
char pszPartition[1024];
{
goto out;
}
/* Second pass over the partitions, now define all extents. */
uint64_t uPartOffset = 0;
cExtents = 0;
uStart = 0;
for (unsigned i = 0; i < pRaw->cPartitions; i++)
{
if (pPart->cbPartitionData)
{
{
pExtent->uSectorOffset = 0;
pExtent->fMetaDirty = false;
}
/* Set up basename for extent description. Cannot use StrDup. */
if (!pszBasename)
{
rc = VERR_NO_MEMORY;
goto out;
}
/* Set up full name for partition extent. */
if (!pszDirname)
{
rc = VERR_NO_MEMORY;
goto out;
}
char *pszFullname;
if (VBOX_FAILURE(rc))
goto out;
pExtent->fMetaDirty = false;
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new partition data file '%s'"), pExtent->pszFullname);
goto out;
}
rc = RTFileWriteAt(pExtent->File, VMDK_SECTOR2BYTE(uPartOffset), pPart->pvPartitionData, pPart->cbPartitionData, NULL);
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not write partition data to '%s'"), pExtent->pszFullname);
goto out;
}
}
if (pPart->cbPartition)
{
{
pExtent->uSectorOffset = 0;
pExtent->fMetaDirty = false;
}
if (pPart->pszRawDevice)
{
/* Set up basename for extent description. Cannot use StrDup. */
if (!pszBasename)
{
rc = VERR_NO_MEMORY;
goto out;
}
/* For raw disks the full name is identical to the base name. */
if (!pExtent->pszFullname)
{
rc = VERR_NO_MEMORY;
goto out;
}
pExtent->fMetaDirty = false;
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not open raw partition file '%s'"), pExtent->pszFullname);
goto out;
}
}
else
{
pExtent->uSectorOffset = 0;
pExtent->fMetaDirty = false;
}
}
}
/* Another extent for filling up the rest of the image. */
{
pExtent->uSectorOffset = 0;
pExtent->fMetaDirty = false;
}
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not set the image type in '%s'"), pszFilename);
goto out;
}
}
}
else
{
goto out;
}
}
else
{
/* Normal (growing) image which is not split into pieces. */
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new extent list in '%s'"), pszFilename);
goto out;
}
if (VBOX_FAILURE(rc))
{
goto out;
}
/* Set up basename for extent description. Cannot use StrDup, as it is
* not guaranteed that the memory can be freed with RTMemTmpFree, which
* must be used as in other code paths StrDup is not usable. */
if (!pszBasename)
{
rc = VERR_NO_MEMORY;
goto out;
}
if (!pExtent->pszFullname)
{
rc = VERR_NO_MEMORY;
goto out;
}
pExtent->fUncleanShutdown = true;
pExtent->uSectorOffset = 0;
pExtent->fMetaDirty = true;
rc = vmdkCreateGrainDirectory(pExtent, pExtent->uDescriptorSector + pExtent->cDescriptorSectors, true);
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not create new grain directory in '%s'"), pszFilename);
goto out;
}
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not set the image type in '%s'"), pszFilename);
goto out;
}
/* The descriptor is part of the extent, move info to extent. */
}
else
{
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: could not insert the extent list into descriptor in '%s'"), pszFilename);
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;
if (VBOX_FAILURE(rc))
goto out;
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing image UUID in new descriptor in '%s'"), pszFilename);
goto out;
}
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing parent image UUID in new descriptor in '%s'"), pszFilename);
goto out;
}
if (VBOX_FAILURE(rc))
{
rc = vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing modification UUID in new descriptor in '%s'"), pszFilename);
goto out;
}
if (VBOX_FAILURE(rc))
goto out;
if (VBOX_FAILURE(rc))
{
goto out;
}
out:
if (VBOX_FAILURE(rc))
return rc;
}
{
char *pszCommentEncoded;
if (pszComment)
{
if (!pszCommentEncoded)
return VERR_NO_MEMORY;
}
else
"ddb.comment", pszCommentEncoded);
if (pszComment)
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing image comment in descriptor in '%s'"), pImage->pszFilename);
return VINF_SUCCESS;
}
{
if (pImage->enmImageType)
{
{
/* 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:
/** @todo implement proper path absolute check. */
if (pExtent->File != NIL_RTFILE && !(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) && !(pExtent->pszBasename[0] == RTPATH_SLASH))
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 '%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 in '%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 '%s'"), pExtent->pszFullname);
}
{
if (VBOX_FAILURE(rc))
return vmdkError(pExtent->pImage, rc, RT_SRC_POS, N_("VMDK: error getting size in '%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 '%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 '%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 '%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 in '%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 '%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 '%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 '%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 '%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;
/** @todo check the image file name for invalid characters, especially double quotes. */
/* Check open flags. All valid flags are supported. */
if (uOpenFlags & ~VD_OPEN_FLAGS_MASK)
{
goto out;
}
if (!pImage)
{
rc = VERR_NO_MEMORY;
goto out;
}
if (VBOX_SUCCESS(rc))
*ppvBackendData = pImage;
out:
return rc;
}
void **ppvBackendData)
{
int rc;
/** @todo check the image file name for invalid characters, especially double quotes. */
/* Check open flags. All valid flags are supported. */
if (uOpenFlags & ~VD_OPEN_FLAGS_MASK)
{
goto out;
}
if (!pImage)
{
rc = VERR_NO_MEMORY;
goto out;
}
{
rc = VERR_NO_MEMORY;
goto out;
}
if (VBOX_SUCCESS(rc))
{
* image is opened in read-only mode if the caller requested that. */
if (uOpenFlags & VD_OPEN_FLAGS_READONLY)
{
vmdkFreeImage(pImage, false);
if (VBOX_FAILURE(rc))
goto out;
}
*ppvBackendData = pImage;
}
out:
/** @todo implement meaningful progress stuff (especially for fixed images). */
if ( VBOX_SUCCESS(rc)
&& pfnProgress)
return rc;
}
{
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, &uSectorExtentRel);
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. */
cbRead = RT_MIN(cbRead, VMDK_SECTOR2BYTE(pExtent->uSectorOffset + pExtent->cNominalSectors - uSectorExtentRel));
/* 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 */
if (VBOX_FAILURE(rc))
goto out;
/* Clip read range to at most the rest of the grain. */
cbRead = RT_MIN(cbRead, VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain - uSectorExtentRel % pExtent->cSectorsPerGrain));
if (uSectorExtentAbs == 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, &uSectorExtentRel);
if (VBOX_FAILURE(rc))
goto out;
/* Check access permissions as defined in the extent descriptor. */
{
goto out;
}
/** @todo implement suppressing of zero data writes (a bit tricky in this
* case, as VMDK has no marker for zero blocks). We somehow need to get the
* information whether the information in this area is all zeroes as of the
* parent image. Then (based on the assumption that parent images are
* immutable) the write can be ignored. */
/* 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 */
if (VBOX_FAILURE(rc))
goto out;
/* Clip write range to at most the rest of the grain. */
cbWrite = RT_MIN(cbWrite, VMDK_SECTOR2BYTE(pExtent->cSectorsPerGrain - uSectorExtentRel % pExtent->cSectorsPerGrain));
if (uSectorExtentAbs == 0)
{
{
/* Full block write to a previously unallocated block.
* Allocate GT and find out where to store the grain. */
*pcbPreRead = 0;
*pcbPostRead = 0;
}
else
{
/* Clip write range to remain in this extent. */
cbWrite = RT_MIN(cbWrite, VMDK_SECTOR2BYTE(pExtent->uSectorOffset + pExtent->cNominalSectors - uSectorExtentRel));
}
}
else
break;
case VMDKETYPE_FLAT:
/* Clip write range to remain in this extent. */
cbWrite = RT_MIN(cbWrite, VMDK_SECTOR2BYTE(pExtent->uSectorOffset + pExtent->cNominalSectors - uSectorExtentRel));
break;
case VMDKETYPE_ZERO:
/* Clip write range to remain in this extent. */
cbWrite = RT_MIN(cbWrite, VMDK_SECTOR2BYTE(pExtent->uSectorOffset + pExtent->cNominalSectors - uSectorExtentRel));
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. */
vmdkFreeImage(pImage, false);
out:
return rc;
}
{
int rc;
if (pImage)
{
const char *pszCommentEncoded = NULL;
"ddb.comment", &pszCommentEncoded);
if (rc == VERR_VDI_VALUE_NOT_FOUND)
else if (VBOX_FAILURE(rc))
goto out;
if (pszComment)
else
{
*pszComment = '\0';
rc = VINF_SUCCESS;
}
if (pszCommentEncoded)
RTStrFree((char *)(void *)pszCommentEncoded);
}
else
out:
return rc;
}
{
int rc;
if (pImage)
{
}
else
return rc;
}
{
int rc;
if (pImage)
{
rc = VINF_SUCCESS;
}
else
return rc;
}
{
int rc;
if (pImage)
{
"ddb.uuid.image", pUuid);
if (VBOX_FAILURE(rc))
return vmdkError(pImage, rc, RT_SRC_POS, N_("VMDK: error storing image UUID in descriptor in '%s'"), pImage->pszFilename);
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 in '%s'"), pImage->pszFilename);
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 in '%s'"), pImage->pszFilename);
rc = VINF_SUCCESS;
}
else
return rc;
}
{
/* pfnOpen */
/* pfnCreate */
/* pfnClose */
/* pfnRead */
/* pfnWrite */
/* pfnFlush */
/* pfnGetImageType */
/* pfnGetSize */
/* pfnGetGeometry */
/* pfnSetGeometry */
/* pfnGetTranslation */
/* pfnSetTranslation */
/* pfnGetOpenFlags */
/* pfnSetOpenFlags */
/* pfnGetComment */
/* pfnSetComment */
/* pfnGetUuid */
/* pfnSetUuid */
/* pfnGetModificationUuid */
/* pfnSetModificationUuid */
/* pfnGetParentUuid */
/* pfnSetParentUuid */
};