www.pudn.com > vdksrc.zip > VDiskExtVmdk.cpp


/*
	VDiskExtVmdk.cpp

	VMDK sparse extent class
	Copyright (c) 2003 Ken Kato
*/

#include "vdkbase.h"
#include "vdkutil.h"

#include "vmdisk.h"

#include "VDisk.h"
#include "VDiskExtVmdk.h"
#include "VDiskUtil.h"

//
//	Constructor
//
VDiskExtVmdk::VDiskExtVmdk()
{
	VdkZeroMem(&m_Header, sizeof(m_Header));
}

//
//	Destructor
//
VDiskExtVmdk::~VDiskExtVmdk()
{
}

//
//	Load and obtain parameters from extent file
//
VDKSTAT VDiskExtVmdk::Load(HANDLE hFile)
{
	VDKSTAT		ret;

	//
	//	Get file attributes
	//
	m_nFileAttr = VdkGetAttribute(m_pFullPath);

	if (m_nFileAttr == (ULONG)INVALID_FILE_ATTRIBUTES) {
		m_nFileAttr = 0;
	}

	//
	//	Get actual file size
	//
	ret = VdkGetFileSize(hFile, &m_nFileSize);

	if (ret != VDK_OK) {
		return ret;
	}

	//
	//	read VMDK header
	//
	ret = VdkReadFileAt(hFile, 0, &m_Header, sizeof(VMDK_HEADER), NULL);

	return ret;
}

//
//	Check parameter consistency
//
VDKSTAT VDiskExtVmdk::Check()
{
	PVOID cbparams[3];
	ULONG filesize;

	cbparams[0] = m_pFullPath;

	//
	//	Signature
	//
	if (m_Header.Signature != VMDK_SIGNATURE) {

		cbparams[1] = (PVOID)m_Header.Signature;

		if (!VDiskCallBack(VDISK_CB_SIGNATURE, cbparams)) {
			return VDK_CANCEL;
		}

		m_Header.Signature = VMDK_SIGNATURE;
		SetModify();
	}

	//
	//	File version
	//
	if (m_Header.FileVersion != VMDK_FILEVER_VMWARE4) {

		cbparams[1] = (PVOID)m_Header.FileVersion;

		m_Header.FileVersion = VDiskCallBack(
			VDISK_CB_VMDK_FILEVER, cbparams);

		if (m_Header.FileVersion != VMDK_FILEVER_VMWARE4) {
			return VDK_CANCEL;
		}

		SetModify();
	}

	//
	//	File Capacity
	//
	if (m_Header.CapacityLow == 0 ||
		m_Header.CapacityHigh != 0) {

		cbparams[1] = &(m_Header.CapacityLow);

		if (!VDiskCallBack(VDISK_CB_VMDK_FILECAP, cbparams)) {
			return VDK_DATA;
		}

		SetModify();
	}

	//
	//	Granularity
	//	Although VMware does not limit the maximum value,
	//	too large values are simply impractical.
	//
	if (m_Header.GranularityHigh != 0	||
		(m_Header.GranularityLow != 16	&&
		m_Header.GranularityLow != 32	&&
		m_Header.GranularityLow != 64	&&
		m_Header.GranularityLow != 128	&&
		m_Header.GranularityLow != 256	&&
		m_Header.GranularityLow != 512	&&
		m_Header.GranularityLow != 1024)) {

		cbparams[1] = &(m_Header.GranularityLow);

		if (!VDiskCallBack(VDISK_CB_VMDK_GRANULARITY, cbparams)) {
			return VDK_DATA;
		}

		SetModify();
	}

	//
	//	Descriptor Offset
	//
	filesize = (ULONG)(m_nFileSize >> VDK_BYTE_SHIFT_TO_SECTOR);

	if (m_Header.DescOffsetHigh != 0 ||
		(m_Header.DescSizeLow && !m_Header.DescOffsetLow) ||
		m_Header.DescOffsetLow >= filesize) {

		cbparams[1] = &(m_Header.DescOffsetLow);

		if (!VDiskCallBack(VDISK_CB_VMDK_DESCOFFSET, cbparams)) {
			return VDK_DATA;
		}

		SetModify();
	}

	//
	//	Descriptor Size
	//
	if (m_Header.DescSizeHigh != 0 ||
		(m_Header.DescOffsetLow && !m_Header.DescSizeLow) ||
		m_Header.DescSizeLow + m_Header.DescOffsetLow > filesize) {

		cbparams[1] = &(m_Header.DescSizeLow);

		if (!VDiskCallBack(VDISK_CB_VMDK_DESCSIZE, cbparams)) {
			return VDK_DATA;
		}

		SetModify();
	}

	//
	//	Number of Grain Table Entries per Grain Table
	//	Although VMware does not limit the minimum / maximum value,
	//	too large values are simply impractical.
	//
	if (m_Header.numGTEsPerGT != 128 &&
		m_Header.numGTEsPerGT != 256 &&
		m_Header.numGTEsPerGT != 512 &&
		m_Header.numGTEsPerGT != 1024) {

		cbparams[1] = (PVOID)m_Header.numGTEsPerGT;

		if (!VDiskCallBack(VDISK_CB_VMDK_GTESPERGT, cbparams)) {
			return VDK_DATA;
		}

		SetModify();
	}

	//
	//	Grain Directory 1 Offset
	//
	if (!m_Header.rgdOffsetLow || m_Header.rgdOffsetHigh ||
		m_Header.rgdOffsetLow >= filesize) {

		cbparams[1] = &(m_Header.rgdOffsetLow);

		if (!VDiskCallBack(VDISK_CB_VMDK_GDOFFSET,  cbparams)) {
			return VDK_DATA;
		}

		SetModify();
	}

	//
	//	Grain Directory 2 Offset
	//
	if (!m_Header.gdOffsetLow || m_Header.gdOffsetHigh ||
		m_Header.gdOffsetLow >= filesize) {

		cbparams[1] = &(m_Header.gdOffsetLow);

		if (!VDiskCallBack(VDISK_CB_VMDK_GDOFFSET, cbparams)) {
			return VDK_DATA;
		}

		SetModify();
	}

	//
	//	Grain Entry Offset
	//
	if (m_Header.GrainOffsetLow == 0 || m_Header.GrainOffsetHigh ||
		m_Header.GrainOffsetLow > filesize) {

		cbparams[1] = &(m_Header.GrainOffsetLow);

		if (!VDiskCallBack(VDISK_CB_VMDK_GRAINOFFSET, cbparams)) {
			return VDK_DATA;
		}

		SetModify();
	}

	//
	//	CheckBytes
	//
	if (memcmp(m_Header.CheckBytes,
		VMDK_HEADER_CHECKBYTES,
		sizeof(VMDK_HEADER_CHECKBYTES) - 1)) {

		if (!VDiskCallBack(VDISK_CB_VMDK_CHECKBYTES, cbparams)) {
			return VDK_CANCEL;
		}

		SetModify();
	}

	//
	//	compare capacity in the header and descriptor
	//
	if (m_nCapacity != m_Header.CapacityLow) {

		cbparams[1] = (PVOID)m_Header.CapacityLow;
		cbparams[2] = (PVOID)m_nCapacity;

		if (!VDiskCallBack(VDISK_CB_VMDK_SIZEMISMATCH, cbparams)) {

			return VDK_CANCEL;
		}

		m_nCapacity = m_Header.CapacityLow;
		SetModify();
	}

	return VDK_OK;
}

//
//	Update VMDK header
//
VDKSTAT VDiskExtVmdk::Update()
{
	HANDLE			hFile;
	VDKSTAT			ret;

	if (!IsModified()) {
		return VDK_OK;
	}

	ret = VdkOpenFile(&hFile, m_pFullPath, strlen(m_pFullPath), FALSE);

	if (ret != VDK_OK) {
		return ret;
	}

	ret = UpdateFile(hFile);

	VdkCloseFile(hFile);

	ClrModify();

	return ret;
}

//
//	Create an actural VMDK file
//
VDKSTAT VDiskExtVmdk::Create(ULONG flags)
{
	HANDLE	hFile;
	ULONG	grains;
	ULONG	gd_size;
	ULONG	gt_size;
	ULONG	gt_count;
	VDKSTAT	ret;

	VdkZeroMem(&m_Header, sizeof(m_Header));

	// number of grains per file
	grains = (m_nCapacity + VMDK_DEFAULT_GRANULARITY - 1) /
		VMDK_DEFAULT_GRANULARITY;

	// size of a single grain table (sectors)
	gt_size = ((VMDK_DEFAULT_NUMGTESPERGT * sizeof(ULONG)) + 511)
		>> VDK_BYTE_SHIFT_TO_SECTOR;

	// number of necessary grain tables
	gt_count = (grains + VMDK_DEFAULT_NUMGTESPERGT - 1) /
		VMDK_DEFAULT_NUMGTESPERGT;

	// size of the grain directory (sectors)
	gd_size = (gt_count * sizeof(ULONG) + 511)
		>> VDK_BYTE_SHIFT_TO_SECTOR;

	// fixed parameters
	m_Header.Signature				= VMDK_SIGNATURE;
	m_Header.FileVersion			= VMDK_FILEVER_VMWARE4;
	m_Header.Flags					= (VMDK_FLAG_UNKNOWN1 | VMDK_FLAG_UNKNOWN2);

	// capacity and mapping parameters
	*(INT64 *)&m_Header.CapacityLow		= m_nCapacity;
	*(INT64 *)&m_Header.GranularityLow	= VMDK_DEFAULT_GRANULARITY;
	m_Header.numGTEsPerGT				= VMDK_DEFAULT_NUMGTESPERGT;

	// offset parameters
	if (flags & VDISK_CREATE_SINGLE) {
		*(INT64 *)&m_Header.DescOffsetLow	= VMDK_DEFAULT_DESCOFFSET;
		*(INT64 *)&m_Header.DescSizeLow		= VMDK_DEFAULT_DESCSIZE;

		*(INT64 *)&m_Header.rgdOffsetLow	=
			VMDK_DEFAULT_DESCOFFSET + VMDK_DEFAULT_DESCSIZE;
	}
	else {
		*(INT64 *)&m_Header.DescOffsetLow	= 0;
		*(INT64 *)&m_Header.DescSizeLow		= 0;
		*(INT64 *)&m_Header.rgdOffsetLow	= 1;
	}

	*(INT64 *)&m_Header.gdOffsetLow			=
		*(INT64 *)&m_Header.rgdOffsetLow +
		gd_size + (gt_size * gt_count);

	// grain entry offset == initial file size
	*(INT64 *)&m_Header.GrainOffsetLow		=
		((*(INT64 *)&m_Header.gdOffsetLow + gd_size + (gt_size * gt_count) +
		(VMDK_DEFAULT_GRANULARITY - 1)) / VMDK_DEFAULT_GRANULARITY) *
		VMDK_DEFAULT_GRANULARITY;

	// check bytes
	VdkCopyMem(
		m_Header.CheckBytes,
		VMDK_HEADER_CHECKBYTES,
		sizeof(m_Header.CheckBytes));

	// open/create file
	ret = VdkCreateFile(&hFile, m_pFullPath, (flags & VDISK_CREATE_FORCE));

	if (ret != VDK_OK) {
		return ret;
	}

	// update header and actual file size
	m_nFileSize = *(INT64 *)&m_Header.GrainOffsetLow << VDK_BYTE_SHIFT_TO_SECTOR;

	ret = UpdateFile(hFile);

	if (ret != VDK_OK) {
		VdkCloseFile(hFile);
		return ret;
	}

	// create grain directories
	ret = WriteGrainDir(hFile, m_Header.rgdOffsetLow, gd_size, gt_count, gt_size);

	if (ret != VDK_OK) {
		VdkCloseFile(hFile);
		return ret;
	}

	ret = WriteGrainDir(hFile, m_Header.gdOffsetLow, gd_size, gt_count, gt_size);

	if (ret != VDK_OK) {
		VdkCloseFile(hFile);
		return ret;
	}

	VdkCloseFile(hFile);

	return ret;
}

//
//	Write VMDK header
//
VDKSTAT VDiskExtVmdk::UpdateFile(HANDLE hFile)
{
	VDKSTAT	ret;

	ret = VdkWriteFileAt(hFile, 0, &m_Header, sizeof(VMDK_HEADER), NULL);

	if (ret != VDK_OK) {
		VdkCloseFile(hFile);

		return ret;
	}

	ret = VdkSetFileSize(hFile, m_nFileSize);

	return ret;
}

//
//	Create initial grain directory
//
VDKSTAT VDiskExtVmdk::WriteGrainDir(
	HANDLE	hFile,
	ULONG	offset,
	ULONG	gd_size,
	ULONG	gt_count,
	ULONG	gt_size)
{
	VDKSTAT ret;
	
	ret = VdkSeekFile(hFile, (INT64)offset << VDK_BYTE_SHIFT_TO_SECTOR);

	if (ret != VDK_OK) {
		return ret;
	}

	offset += gd_size;

	do {
		ret = VdkWriteFileAt(hFile, -1, &offset, sizeof(offset), NULL);

		if (ret != VDK_OK) {
			return ret;
		}

		offset += gt_size;
	}
	while (--gt_count);

	return VDK_OK;
}