/*===================================================================
Microsoft Denali

Microsoft Confidential.
Copyright 1997 Microsoft Corporation. All Rights Reserved.

Component: MetaUtil object

File: ChkMeta.cpp

Owner: t-BrianM

This file contains implementations of the CheckSchema and CheckKey
methods of the main MetaUtil class.
===================================================================*/

#include "stdafx.h"
#include "MetaUtil.h"
#include "MUtilObj.h"
#include "ChkMeta.h"

/*------------------------------------------------------------------
 * C M e t a U t i l  (check portion)
 */

/*===================================================================
CMetaUtil::CheckSchema

Check the schema of a given machine for errors.

Directly Generates:
	MUTIL_CHK_NO_SCHEMA
	MUTIL_CHK_NO_PROPERTIES
	MUTIL_CHK_NO_PROP_NAMES
	MUTIL_CHK_NO_PROP_TYPES
	MUTIL_CHK_NO_CLASSES

Parameters:
    bstrMachine	[in] Base key of the machine to check
	ppIReturn	[out, retval] interface for the output error collection

Returns:
	E_OUTOFMEMORY if allocation fails.
	E_INVALIDARG ppIReturn == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::CheckSchema(BSTR bstrMachine, 
									ICheckErrorCollection **ppIReturn)
{
	TRACE0("MetaUtil: CMetaUtil::CheckSchema\n");

	ASSERT_NULL_OR_POINTER(ppIReturn, ICheckErrorCollection *);

	if ((ppIReturn == NULL)) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	HRESULT hr;
	TCHAR tszMachine[ADMINDATA_MAX_NAME_LEN];

    if (bstrMachine) {
      	_tcscpy(tszMachine, OLE2T(bstrMachine));
	    CannonizeKey(tszMachine);
    }
    else {
        tszMachine[0] = _T('\0');
    }

	// Create the CheckErrorCollection
	CComObject<CCheckErrorCollection> *pCErrorCol = NULL;

	ATLTRY(pCErrorCol = new CComObject<CCheckErrorCollection>);
	if (pCErrorCol == NULL) {
		return ::ReportError(E_OUTOFMEMORY);
	}

	// Open the Machine Key
	METADATA_HANDLE hMDMachine = NULL;

	hr = m_pIMeta->OpenKey(METADATA_MASTER_ROOT_HANDLE,
						   L"",
						   METADATA_PERMISSION_READ,
					       MUTIL_OPEN_KEY_TIMEOUT,
						   &hMDMachine);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}

	// Make sure "Schema" exists
	if (!KeyExists(hMDMachine, _T("Schema"))) {
		AddError(pCErrorCol,
				 MUTIL_CHK_NO_SCHEMA,
				 MUTIL_CHK_NO_SCHEMA_S,
				 tszMachine,
				 NULL,
				 0);
		
		goto LDone; // Can't do anything else
	}

	// Make sure "Schema/Properties" exists
	if (!KeyExists(hMDMachine, _T("Schema/Properties"))) {
		AddError(pCErrorCol,
				 MUTIL_CHK_NO_PROPERTIES,
				 MUTIL_CHK_NO_PROPERTIES_S,
				 tszMachine,
				 _T("Schema"),
				 0);
		
		goto LClasses; // Can't do anything else with properties
	}

	// Make sure "Schema/Properties/Names" exists
	if (!KeyExists(hMDMachine, _T("Schema/Properties/Names"))) {
		AddError(pCErrorCol,
				 MUTIL_CHK_NO_PROP_NAMES,
				 MUTIL_CHK_NO_PROP_NAMES_S,
				 tszMachine,
				 _T("Schema/Properties"),
				 0);
		
		goto LPropTypes; // Can't do anything else with names
	}

	// Check property names
	hr = CheckPropertyNames(pCErrorCol, hMDMachine, tszMachine);
	if (FAILED(hr)) {
		goto LError;
	}

LPropTypes:

	// Make sure "Schema/Properties/Types" exists
	if (!KeyExists(hMDMachine, _T("Schema/Properties/Types"))) {
		AddError(pCErrorCol,
				 MUTIL_CHK_NO_PROP_TYPES,
				 MUTIL_CHK_NO_PROP_TYPES_S,
				 tszMachine,
				 _T("Schema/Properties"),
				 0);
		
		goto LClasses; // Can't do anything else with types
	}

	// Check property types
	hr = CheckPropertyTypes(pCErrorCol, hMDMachine, tszMachine);
	if (FAILED(hr)) {
		goto LError;
	}

LClasses:

	// Make sure "Schema/Classes" exists
	if (!KeyExists(hMDMachine, _T("Schema/Classes"))) {
		AddError(pCErrorCol,
				 MUTIL_CHK_NO_CLASSES,
				 MUTIL_CHK_NO_CLASSES_S,
				 tszMachine,
				 _T("Schema"),
				 0);
		
		goto LDone; // Can't do anything else
	}

	// Check classes
	hr = CheckClasses(pCErrorCol, hMDMachine, tszMachine);
	if (FAILED(hr)) {
		goto LError;
	}

LDone:

	// Close the Machine Key
	m_pIMeta->CloseKey(hMDMachine);

	// Set the interface to ICheckErrorCollection
	hr = pCErrorCol->QueryInterface(IID_ICheckErrorCollection, (void **) ppIReturn);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}
	ASSERT(*ppIReturn != NULL);

	return S_OK;

LError:

	if (pCErrorCol != NULL) {
		delete pCErrorCol;
	}
	if (hMDMachine != NULL) {
		m_pIMeta->CloseKey(hMDMachine);
	}

	return hr;
}

/*===================================================================
CMetaUtil::CheckPropertyNames

Private function to check the "Schema/Properties/Names" key of a 
given machine.
	o Make sure that each name entry is of type STRING_METADATA
	o Make sure that each name is unique

Directly Generates:
	MUTIL_CHK_PROP_NAME_BAD_TYPE
	MUTIL_CHK_PROP_NAME_NOT_UNIQUE
	MUTIL_CHK_PROP_NAME_NOT_CASE_UNIQUE

Parameters:
	pCErrorCol	Pointer to the error collection to put errors in
	hMDMachine	Open metabase handle for the machine key
	tszMachine	Name of the machine key

Returns:
	E_OUTOFMEMORY if allocation fails.
	S_OK on success
===================================================================*/
HRESULT CMetaUtil::CheckPropertyNames(CComObject<CCheckErrorCollection> *pCErrorCol, 
									  METADATA_HANDLE hMDMachine, 
									  LPTSTR tszMachine)
{
	ASSERT_POINTER(pCErrorCol, CComObject<CCheckErrorCollection>);
	ASSERT_STRING(tszMachine);

	USES_CONVERSION;
	HRESULT hr;
	int iDataIndex;
	METADATA_RECORD mdr;
	DWORD dwReqDataLen;
	DWORD dwDataBufLen;
	BYTE *lpDataBuf = NULL;
	LPTSTR tszName;
	CNameTable CPropNameTable;


	//Setup the return buffer
	dwDataBufLen = 256;
	lpDataBuf = new BYTE[dwDataBufLen];
	if (lpDataBuf == NULL) {
		return E_OUTOFMEMORY;
	}

	// For Each Data Item
	iDataIndex = 0;
	mdr.dwMDIdentifier = 0;
	mdr.dwMDAttributes = METADATA_NO_ATTRIBUTES;
    mdr.dwMDUserType = ALL_METADATA;
	mdr.dwMDDataType = ALL_METADATA;
	mdr.dwMDDataLen = dwDataBufLen;
	mdr.pbMDData = (PBYTE) lpDataBuf;
	mdr.dwMDDataTag = 0;
	hr = m_pIMeta->EnumData(hMDMachine, 
			                L"Schema/Properties/Names", 
						    &mdr, 
						    iDataIndex, 
						    &dwReqDataLen);
	while (SUCCEEDED(hr)) {

		// Datatype must be STRING_METADATA
		if (mdr.dwMDDataType != STRING_METADATA) {
			AddError(pCErrorCol,
					 MUTIL_CHK_PROP_NAME_BAD_TYPE,
					 MUTIL_CHK_PROP_NAME_BAD_TYPE_S,
					 tszMachine,
					 _T("Schema/Properties/Names"),
					 mdr.dwMDIdentifier);
		}
		else { // mdr.dwMDDataType == STRING_METADATA

			// Check uniqueness of the name
			tszName = W2T(reinterpret_cast<LPWSTR> (lpDataBuf));

			if (CPropNameTable.IsCaseSenDup(tszName)) { 
				// Not unique
				AddError(pCErrorCol,
					 MUTIL_CHK_PROP_NAME_NOT_UNIQUE,
					 MUTIL_CHK_PROP_NAME_NOT_UNIQUE_S,
					 tszMachine,
					 _T("Schema/Properties/Names"),
					 mdr.dwMDIdentifier);
			}
			else if (CPropNameTable.IsCaseInsenDup(tszName)) { 
				// Case sensitive unique
				AddError(pCErrorCol,
					 MUTIL_CHK_PROP_NAME_NOT_CASE_UNIQUE,
					 MUTIL_CHK_PROP_NAME_NOT_CASE_UNIQUE_S,
					 tszMachine,
					 _T("Schema/Properties/Names"),
					 mdr.dwMDIdentifier);
				// Add it to pick up case sensitive collisions
				hr = CPropNameTable.Add(tszName);
				if (FAILED(hr)) {
					goto LError;
				}
			}
			else { 
				// Unique
				hr = CPropNameTable.Add(tszName);
				if (FAILED(hr)) {
					goto LError;
				}
			}
		}

		// Next data item
		iDataIndex++;
		mdr.dwMDIdentifier = 0;
		mdr.dwMDAttributes = METADATA_NO_ATTRIBUTES;
		mdr.dwMDUserType = ALL_METADATA;
		mdr.dwMDDataType = ALL_METADATA;
		mdr.dwMDDataLen = dwDataBufLen;
		mdr.pbMDData = (PBYTE) lpDataBuf;
		mdr.dwMDDataTag = 0;
		hr = m_pIMeta->EnumData(hMDMachine, 
								L"Schema/Properties/Names", 
							    &mdr, 
							    iDataIndex, 
							    &dwReqDataLen);
	}

	// Make sure we ran out of items
	if (HRESULT_CODE(hr) != ERROR_NO_MORE_ITEMS) {
		goto LError;
	}

	delete lpDataBuf;

	return S_OK;

LError:

	if (lpDataBuf != NULL) {
		delete lpDataBuf;
	}

	return hr;
}

/*===================================================================
CMetaUtil::CheckPropertyTypes

Private function to check the "Schema/Properties/Types" key of a 
given machine.
	o Make sure that each type entry is of type BINARY_METADATA
	o Make sure that the type data is valid
		o mdrDataRec.dwMDDataLen == sizeof(PropValue)
		o PropValue.dwMetaID != 0
		o PropValue.dwMetaType != ALL_METADATA
		o PropValue.dwUserGroup != ALL_METADATA
		o (PropValue.dwMetaFlags & METADATA_PARTIAL_PATH) != METADATA_PARTIAL_PATH
		o (PropValue.dwMetaFlags & METADATA_ISINHERITED) != METADATA_ISINHERITED

Directly Generates:
	MUTIL_CHK_PROP_TYPE_BAD_TYPE
	MUTIL_CHK_PROP_TYPE_BAD_DATA

Parameters:
	pCErrorCol	Pointer to the error collection to put errors in
	hMDMachine	Open metabase handle for the machine key
	tszMachine	Name of the machine key

Returns:
	E_OUTOFMEMORY if allocation fails.
	S_OK on success
===================================================================*/
HRESULT CMetaUtil::CheckPropertyTypes(CComObject<CCheckErrorCollection> *pCErrorCol, 
									  METADATA_HANDLE hMDMachine, 
									  LPTSTR tszMachine)
{
	ASSERT_POINTER(pCErrorCol, CComObject<CCheckErrorCollection>);
	ASSERT_STRING(tszMachine);

	USES_CONVERSION;
	HRESULT hr;
	int iDataIndex;
	METADATA_RECORD mdr;
	DWORD dwReqDataLen;
	DWORD dwDataBufLen;
	UCHAR *lpDataBuf = NULL;
	PropValue *pPropType;

	//Setup the return buffer
	dwDataBufLen = 256;
	lpDataBuf = new UCHAR[dwDataBufLen];
	if (lpDataBuf == NULL) {
		return E_OUTOFMEMORY;
	}

	// For Each Data Item
	iDataIndex = 0;
	mdr.dwMDIdentifier = 0;
	mdr.dwMDAttributes = METADATA_NO_ATTRIBUTES;
    mdr.dwMDUserType = ALL_METADATA;
	mdr.dwMDDataType = ALL_METADATA;
	mdr.dwMDDataLen = dwDataBufLen;
	mdr.pbMDData = (PBYTE) lpDataBuf;
	mdr.dwMDDataTag = 0;
	hr = m_pIMeta->EnumData(hMDMachine, 
			                L"Schema/Properties/Types", 
						    &mdr, 
						    iDataIndex, 
						    &dwReqDataLen);
	while (SUCCEEDED(hr)) {

		// Datatype must be BINARY_METADATA
		if (mdr.dwMDDataType != BINARY_METADATA) {
			AddError(pCErrorCol,
					 MUTIL_CHK_PROP_TYPE_BAD_TYPE,
					 MUTIL_CHK_PROP_TYPE_BAD_TYPE_S,
					 tszMachine,
					 _T("Schema/Properties/Types"),
					 mdr.dwMDIdentifier);
		}
		else { // mdr.dwMDDataType == BINARY_METADATA

			// Validate the data
			pPropType = reinterpret_cast<PropValue *> (lpDataBuf);

			if ((mdr.dwMDDataLen != sizeof(PropValue)) || 
				(pPropType->dwMetaID == 0) ||
				(pPropType->dwMetaType == ALL_METADATA) ||
				(pPropType->dwUserGroup == ALL_METADATA) ||
				((pPropType->dwMetaFlags & METADATA_PARTIAL_PATH) == METADATA_PARTIAL_PATH) ||
				((pPropType->dwMetaFlags & METADATA_ISINHERITED) == METADATA_ISINHERITED)) {
				AddError(pCErrorCol,
					 MUTIL_CHK_PROP_TYPE_BAD_DATA,
					 MUTIL_CHK_PROP_TYPE_BAD_DATA_S,
					 tszMachine,
					 _T("Schema/Properties/Types"),
					 mdr.dwMDIdentifier);
			}

		}

		// Next data item
		iDataIndex++;
		mdr.dwMDIdentifier = 0;
		mdr.dwMDAttributes = METADATA_NO_ATTRIBUTES;
		mdr.dwMDUserType = ALL_METADATA;
		mdr.dwMDDataType = ALL_METADATA;
		mdr.dwMDDataLen = dwDataBufLen;
		mdr.pbMDData = (PBYTE) lpDataBuf;
		mdr.dwMDDataTag = 0;
		hr = m_pIMeta->EnumData(hMDMachine, 
								L"Schema/Properties/Types", 
							    &mdr, 
							    iDataIndex, 
							    &dwReqDataLen);
	}

	// Make sure we ran out of items
	if (HRESULT_CODE(hr) != ERROR_NO_MORE_ITEMS) {
		goto LError;
	}

	delete lpDataBuf;

	return S_OK;

LError:
	if (lpDataBuf != NULL) {
		delete lpDataBuf;
	}

	return hr;
}

/*===================================================================
CMetaUtil::CheckClasses

Private method to check the "Schema/Classes" key of a given machine.
	o Make sure that each class name is unique
	o Make sure that each class has a MANDATORY subkey
	o Make sure that each class has a OPTIONAL subkey
	o Make sure that each default property value is valid

Directly Generates:
	MUTIL_CHK_CLASS_NOT_CASE_UNIQUE
	MUTIL_CHK_CLASS_NO_MANDATORY
	MUTIL_CHK_CLASS_NO_OPTIONAL

Parameters:
	pCErrorCol	Pointer to the error collection to put errors in
	hMDMachine	Open metabase handle for the machine key
	tszMachine	Name of the machine key

Returns:
	E_OUTOFMEMORY if allocation fails.
	S_OK on success
===================================================================*/
HRESULT CMetaUtil::CheckClasses(CComObject<CCheckErrorCollection> *pCErrorCol, 
								METADATA_HANDLE hMDMachine, 
								LPTSTR tszMachine)
{
	ASSERT_POINTER(pCErrorCol, CComObject<CCheckErrorCollection>);
	ASSERT_STRING(tszMachine);

	USES_CONVERSION;
	HRESULT hr;
	int iKeyIndex;
	wchar_t wszSubKey[ADMINDATA_MAX_NAME_LEN];
	LPTSTR tszSubKey;
	CNameTable CClassNameTable;

	// For each Class key
	iKeyIndex = 0;
	hr = m_pIMeta->EnumKeys(hMDMachine, 
			                L"Schema/Classes", 
						    wszSubKey, 
						    iKeyIndex);
	while (SUCCEEDED(hr)) {
		tszSubKey = W2T(wszSubKey);

		// Build the full key
		TCHAR tszFullKey[ADMINDATA_MAX_NAME_LEN];
		_tcscpy(tszFullKey, _T("/Schema/Classes/"));
		_tcscat(tszFullKey, tszSubKey);

		// Class name is unique
		if (CClassNameTable.IsCaseInsenDup(tszSubKey)) { 
			// Case sensitive unique
			AddError(pCErrorCol,
					 MUTIL_CHK_CLASS_NOT_CASE_UNIQUE,
					 MUTIL_CHK_CLASS_NOT_CASE_UNIQUE_S,
					 tszFullKey,
					 NULL,
					 0);
		}
		else { 
			// Unique
			hr = CClassNameTable.Add(tszSubKey);
			if (FAILED(hr)) {
				goto LError;
			}
		}

		// Open the class key
		METADATA_HANDLE hMDClass = NULL;

		hr = m_pIMeta->OpenKey(METADATA_MASTER_ROOT_HANDLE,
						   T2W(tszFullKey),
						   METADATA_PERMISSION_READ,
					       MUTIL_OPEN_KEY_TIMEOUT,
						   &hMDClass);
		if (FAILED(hr)) {
			return ::ReportError(hr);
		}

		// Mandatory key exists
		if (!KeyExists(hMDClass, _T("Mandatory"))) {
			AddError(pCErrorCol,
					 MUTIL_CHK_CLASS_NO_MANDATORY,
					 MUTIL_CHK_CLASS_NO_MANDATORY_S,
					 tszFullKey,
					 NULL,
					 0);
		}
		else {
			// Make sure default mandatory settings make sense
			CheckClassProperties(pCErrorCol,
								 hMDClass,
								 tszFullKey,
								 _T("Mandatory"));
		}

		// Optional key exits
		if (!KeyExists(hMDClass, _T("Optional"))) {
			AddError(pCErrorCol,
					 MUTIL_CHK_CLASS_NO_OPTIONAL,
					 MUTIL_CHK_CLASS_NO_OPTIONAL_S,
					 tszFullKey,
					 NULL,
					 0);
		}
		else {
			// Make sure default optional settings make sense
			CheckClassProperties(pCErrorCol,
								 hMDClass,
								 tszFullKey,
								 _T("Optional"));
		}

		// Close the class key
		m_pIMeta->CloseKey(hMDClass);

		// Next key
		iKeyIndex++;
		hr = m_pIMeta->EnumKeys(hMDMachine, 
								L"Schema/Classes", 
								wszSubKey, 
								iKeyIndex);
	}

	// Make sure we ran out of items
	if (HRESULT_CODE(hr) != ERROR_NO_MORE_ITEMS) {
		goto LError;
	}

	return S_OK;

LError:

	return ::ReportError(hr);
}

/*===================================================================
CMetaUtil::CheckClassProperties

Private method to check the properties under 
"Schema/Classes/_Class_/Madatory" and "Schema/Classes/_Class_/Optional".

	o Make sure that the class property type is compatible with the
	  type under "Schema/Properties/Types"
		o DataType must match
		o UserType must match
		o Attributes must be a superset of the type attributes

Directly Generates:
	MUTIL_CHK_CLASS_PROP_BAD_TYPE

Parameters:
	pCErrorCol		Pointer to the error collection to put errors in
	hMDClassKey		Open metabase handle for the "Schema/Classes/_Class_" key
	tszClassKey		Full path of the "Schema/Classes/_Class_" key
	tszClassSubKey	Name of the specific class sub-key ("Mandatory"
					or "Optional")

Returns:
	E_OUTOFMEMORY if allocation fails.
	S_OK on success
===================================================================*/
HRESULT CMetaUtil::CheckClassProperties(CComObject<CCheckErrorCollection> *pCErrorCol, 
										METADATA_HANDLE hMDClassKey, 
										LPTSTR tszClassKey, 
										LPTSTR tszClassSubKey)
{
	ASSERT_POINTER(pCErrorCol, CComObject<CCheckErrorCollection>);
	ASSERT_STRING(tszClassKey);
	ASSERT_STRING(tszClassSubKey);

	USES_CONVERSION;
	HRESULT hr;
	int iDataIndex;
	METADATA_RECORD mdr;
	DWORD dwReqDataLen;
	DWORD dwDataBufLen;
	BYTE *lpDataBuf = NULL;

	// Setup the return buffer
	dwDataBufLen = 1024;
	lpDataBuf = new BYTE[dwDataBufLen];
	if (lpDataBuf == NULL) {
		return ::ReportError(E_OUTOFMEMORY);
	}

	// For each property
	iDataIndex = 0;
	mdr.dwMDIdentifier = 0;
	mdr.dwMDAttributes = METADATA_NO_ATTRIBUTES;
    mdr.dwMDUserType = ALL_METADATA;
	mdr.dwMDDataType = ALL_METADATA;
	mdr.dwMDDataLen = dwDataBufLen;
	mdr.pbMDData = (PBYTE) lpDataBuf;
	mdr.dwMDDataTag = 0;
	hr = m_pIMeta->EnumData(hMDClassKey,
			                T2W(tszClassSubKey), 
						    &mdr,
						    iDataIndex, 
						    &dwReqDataLen);
	while (SUCCEEDED(hr) || (HRESULT_CODE(hr) == ERROR_INSUFFICIENT_BUFFER)) {

		if (HRESULT_CODE(hr) == ERROR_INSUFFICIENT_BUFFER) {
			delete lpDataBuf;
			dwDataBufLen = dwReqDataLen;
			lpDataBuf = new BYTE[dwDataBufLen];
			if (lpDataBuf == NULL) {
				return ::ReportError(E_OUTOFMEMORY);
			}
		
		}
		else {
			// Get the property information
			CPropInfo *pCPropInfo;
			PropValue *pTypeInfo;

			// Get the property info from the Schema Table
			pCPropInfo = m_pCSchemaTable->GetPropInfo(tszClassKey, mdr.dwMDIdentifier);

			if ((pCPropInfo == NULL) ||
				(pCPropInfo->GetTypeInfo() == NULL)) {

				// Error: no property type information for class property
				AddError(pCErrorCol,
						 MUTIL_CHK_CLASS_PROP_NO_TYPE,
						 MUTIL_CHK_CLASS_PROP_NO_TYPE_S,
						 tszClassKey,
						 tszClassSubKey,
						 mdr.dwMDIdentifier);
			}
			else {
				pTypeInfo = pCPropInfo->GetTypeInfo();

				// Validate the property defaults :
				//		DataType must match
				//		UserType must match
				//		Attributes must be a superset of the type attributes
				if (mdr.dwMDDataType != pTypeInfo->dwMetaType) {
					AddError(pCErrorCol,
							 MUTIL_CHK_CLASS_PROP_BAD_DATA_TYPE,
							 MUTIL_CHK_CLASS_PROP_BAD_DATA_TYPE_S,
							 tszClassKey,
							 tszClassSubKey,
							 mdr.dwMDIdentifier);
				}
				if (mdr.dwMDUserType != pTypeInfo->dwUserGroup) {
					AddError(pCErrorCol,
							 MUTIL_CHK_CLASS_PROP_BAD_USER_TYPE,
							 MUTIL_CHK_CLASS_PROP_BAD_USER_TYPE_S,
							 tszClassKey,
							 tszClassSubKey,
							 mdr.dwMDIdentifier);
				}
				if ((mdr.dwMDAttributes & pTypeInfo->dwMetaFlags) != pTypeInfo->dwMetaFlags) {
					AddError(pCErrorCol,
							 MUTIL_CHK_CLASS_PROP_BAD_ATTR,
							 MUTIL_CHK_CLASS_PROP_BAD_ATTR_S,
							 tszClassKey,
							 tszClassSubKey,
							 mdr.dwMDIdentifier);
				}		
			}

			// Next property
			iDataIndex++;
		}
		mdr.dwMDIdentifier = 0;
		mdr.dwMDAttributes = METADATA_NO_ATTRIBUTES;
		mdr.dwMDUserType = ALL_METADATA;
		mdr.dwMDDataType = ALL_METADATA;
		mdr.dwMDDataLen = dwDataBufLen;
		mdr.pbMDData = (PBYTE) lpDataBuf;
		mdr.dwMDDataTag = 0;
		hr = m_pIMeta->EnumData(hMDClassKey, 
								T2W(tszClassSubKey), 
								&mdr, 
								iDataIndex, 
								&dwReqDataLen);
	}

	// Make sure we ran out of items
	if (HRESULT_CODE(hr) != ERROR_NO_MORE_ITEMS) {
		delete lpDataBuf;
		return ::ReportError(hr);
	}

	delete lpDataBuf;
	return S_OK;
}

/*===================================================================
CMetaUtil::CheckKey

Check a given key for errors.

Directly Generates:
	MUTIL_CHK_DATA_TOO_BIG
	MUTIL_CHK_NO_NAME_ENTRY
	MUTIL_CHK_NO_TYPE_ENTRY
	MUTIL_CHK_BAD_TYPE
	MUTIL_CHK_CLSID_NOT_FOUND
	MUTIL_CHK_MTX_PACK_ID_NOT_FOUND
	MUTIL_CHK_KEY_TOO_BIG
	
Parameters:
    bstrKey		[in] Key to check
	ppIReturn	[out, retval] interface for the output error collection

Returns:
	E_OUTOFMEMORY if allocation fails.
	E_INVALIDARG if bstrKey == NULL or ppIReturn == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::CheckKey(BSTR bstrKey, 
								 ICheckErrorCollection **ppIReturn)
{
	TRACE0("MetaUtil: CMetaUtil::CheckKey\n");

	ASSERT_NULL_OR_POINTER(bstrKey, OLECHAR);
	ASSERT_NULL_OR_POINTER(ppIReturn, ICheckErrorCollection *);

	if ((bstrKey == NULL) || (ppIReturn == NULL)) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	HRESULT hr;
	TCHAR tszKey[ADMINDATA_MAX_NAME_LEN];
	METADATA_HANDLE hMDKey = NULL;
	BYTE *lpDataBuf = NULL;
	DWORD dwKeySize = 0;

	_tcscpy(tszKey, OLE2T(bstrKey));
	CannonizeKey(tszKey);

	// Create the CheckErrorCollection
	CComObject<CCheckErrorCollection> *pCErrorCol = NULL;

	ATLTRY(pCErrorCol = new CComObject<CCheckErrorCollection>);
	if (pCErrorCol == NULL) {
		return ::ReportError(E_OUTOFMEMORY);
	}

	// If it's in the schema or IISAdmin, don't check
	if (::KeyIsInSchema(tszKey) || ::KeyIsInIISAdmin(tszKey)) {
		goto LDone;
	}

	// Open the key
	hr = m_pIMeta->OpenKey(METADATA_MASTER_ROOT_HANDLE,
						   T2W(tszKey),
						   METADATA_PERMISSION_READ,
					       MUTIL_OPEN_KEY_TIMEOUT,
						   &hMDKey);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}


	// TODO: Hard coded checks for expected subkeys

	int iDataIndex;
	METADATA_RECORD mdrDataRec;
	DWORD dwReqDataLen;
	DWORD dwDataBufLen;

	//Setup the return buffer
	dwDataBufLen = 1024;
	lpDataBuf = new BYTE[dwDataBufLen];
	if (lpDataBuf == NULL) {
		return ::ReportError(E_OUTOFMEMORY);
	}

	// For each property
	iDataIndex = 0;
	mdrDataRec.dwMDIdentifier = 0;
	mdrDataRec.dwMDAttributes = METADATA_NO_ATTRIBUTES;
    mdrDataRec.dwMDUserType = ALL_METADATA;
	mdrDataRec.dwMDDataType = ALL_METADATA;
	mdrDataRec.dwMDDataLen = dwDataBufLen;
	mdrDataRec.pbMDData = (PBYTE) lpDataBuf;
	mdrDataRec.dwMDDataTag = 0;
	hr = m_pIMeta->EnumData(hMDKey,
			                NULL, 
						    &mdrDataRec, 
						    iDataIndex, 
						    &dwReqDataLen);
	while (SUCCEEDED(hr) || (HRESULT_CODE(hr) == ERROR_INSUFFICIENT_BUFFER)) {
		if (HRESULT_CODE(hr) == ERROR_INSUFFICIENT_BUFFER) {
			delete lpDataBuf;
			dwDataBufLen = dwReqDataLen;
			lpDataBuf = new BYTE[dwDataBufLen];
			if (lpDataBuf == NULL) {
				hr = E_OUTOFMEMORY;
				goto LError;
			}
			hr = S_OK; // Loop again
		}
		else {
			// Check property data size
			if (mdrDataRec.dwMDDataLen > m_dwMaxPropSize) {
				AddError(pCErrorCol,
						 MUTIL_CHK_DATA_TOO_BIG,
						 MUTIL_CHK_DATA_TOO_BIG_S,
						 tszKey,
						 NULL,
						 mdrDataRec.dwMDIdentifier);
			}

			// Add to the key size
			dwKeySize += mdrDataRec.dwMDDataLen;
	
			CPropInfo *pCPropInfo;
			PropValue *pTypeInfo;

			pCPropInfo = m_pCSchemaTable->GetPropInfo(tszKey, mdrDataRec.dwMDIdentifier);

			// Property should have a name entry
			if ((pCPropInfo == NULL) || (pCPropInfo->GetName() == NULL)) {
				AddError(pCErrorCol,
						 MUTIL_CHK_NO_NAME_ENTRY,
						 MUTIL_CHK_NO_NAME_ENTRY_S,
						 tszKey,
						 NULL,
						 mdrDataRec.dwMDIdentifier);
			}

			// Property should have a type entry
			if ((pCPropInfo == NULL) || (pCPropInfo->GetTypeInfo() == NULL)) {
				AddError(pCErrorCol,
						 MUTIL_CHK_NO_TYPE_ENTRY,
						 MUTIL_CHK_NO_TYPE_ENTRY_S,
						 tszKey,
						 NULL,
						 mdrDataRec.dwMDIdentifier);
			}
			else { 
				// Check property type
				//		DataType must match
				//		UserType must match
				//		Attributes must be a superset of the type attributes
				pTypeInfo = pCPropInfo->GetTypeInfo();

				if (mdrDataRec.dwMDDataType != pTypeInfo->dwMetaType) {
					AddError(pCErrorCol,
						 MUTIL_CHK_BAD_DATA_TYPE,
						 MUTIL_CHK_BAD_DATA_TYPE_S,
						 tszKey,
						 NULL,
						 mdrDataRec.dwMDIdentifier);
				}
				if (mdrDataRec.dwMDUserType != pTypeInfo->dwUserGroup) {
					AddError(pCErrorCol,
						 MUTIL_CHK_BAD_USER_TYPE,
						 MUTIL_CHK_BAD_USER_TYPE_S,
						 tszKey,
						 NULL,
						 mdrDataRec.dwMDIdentifier);
				}
				if ((mdrDataRec.dwMDAttributes & pTypeInfo->dwMetaFlags) != pTypeInfo->dwMetaFlags) {
					AddError(pCErrorCol,
						 MUTIL_CHK_BAD_ATTR,
						 MUTIL_CHK_BAD_ATTR_S,
						 tszKey,
						 NULL,
						 mdrDataRec.dwMDIdentifier);
				}
			}
			
			// Hard coded property checks (hard coded logic)
			if ((mdrDataRec.dwMDIdentifier == MD_APP_WAM_CLSID) ||
				(mdrDataRec.dwMDIdentifier == MD_LOG_PLUGIN_ORDER)) {

				// If property is a CLSID 
				if (!CheckCLSID(W2T(reinterpret_cast<LPWSTR> (lpDataBuf)))) {
					AddError(pCErrorCol,
						 MUTIL_CHK_CLSID_NOT_FOUND,
						 MUTIL_CHK_CLSID_NOT_FOUND_S,
						 tszKey,
						 NULL,
						 mdrDataRec.dwMDIdentifier);
				}
			}
			else if (mdrDataRec.dwMDIdentifier == MD_APP_PACKAGE_ID) {

				// Property is a Transaction Server package
				if (!CheckMTXPackage(W2T(reinterpret_cast<LPWSTR> (lpDataBuf)))) {
					AddError(pCErrorCol,
						 MUTIL_CHK_MTX_PACK_ID_NOT_FOUND,
						 MUTIL_CHK_MTX_PACK_ID_NOT_FOUND_S,
						 tszKey,
						 NULL,
						 mdrDataRec.dwMDIdentifier);
				}
			}
			else if ((mdrDataRec.dwMDIdentifier == MD_VR_PATH) ||
					 (mdrDataRec.dwMDIdentifier == MD_FILTER_IMAGE_PATH))  {

				// Property is a path
				BOOL fResult;
				hr = CheckIfFileExists(W2T(reinterpret_cast<LPWSTR> (lpDataBuf)), &fResult);
				if (SUCCEEDED(hr) && !fResult) {
					AddError(pCErrorCol,
						 MUTIL_CHK_PATH_NOT_FOUND,
						 MUTIL_CHK_PATH_NOT_FOUND_S,
						 tszKey,
						 NULL,
						 mdrDataRec.dwMDIdentifier);
				}
			}

			// Next property
			iDataIndex++;
		}
		mdrDataRec.dwMDIdentifier = 0;
		mdrDataRec.dwMDAttributes = METADATA_NO_ATTRIBUTES;
		mdrDataRec.dwMDUserType = ALL_METADATA;
		mdrDataRec.dwMDDataType = ALL_METADATA;
		mdrDataRec.dwMDDataLen = dwDataBufLen;
		mdrDataRec.pbMDData = (PBYTE) lpDataBuf;
		mdrDataRec.dwMDDataTag = 0;
		hr = m_pIMeta->EnumData(hMDKey, 
								NULL, 
								&mdrDataRec, 
								iDataIndex, 
								&dwReqDataLen);
	}
	// Make sure we ran out of items
	if (HRESULT_CODE(hr) != ERROR_NO_MORE_ITEMS) {
		goto LError;
	}

	// Check total size of key
	if (dwKeySize > m_dwMaxKeySize) {
		AddError(pCErrorCol,
				 MUTIL_CHK_KEY_TOO_BIG,
				 MUTIL_CHK_KEY_TOO_BIG_S,
				 tszKey,
				 NULL,
				 0);
	}

	// Check the KeyType property against schema class information
	hr = CheckKeyType(pCErrorCol, hMDKey, tszKey);
	if (FAILED(hr)) {
		goto LError;
	}

	delete lpDataBuf;

	// Close the key
	m_pIMeta->CloseKey(hMDKey);

LDone:

	// Set the interface to ICheckErrorCollection
	hr = pCErrorCol->QueryInterface(IID_ICheckErrorCollection, (void **) ppIReturn);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}
	ASSERT(*ppIReturn != NULL);

	return S_OK;

LError:

	if (pCErrorCol != NULL) {
		delete pCErrorCol;
	}
	if (hMDKey != NULL) {
		m_pIMeta->CloseKey(hMDKey);
	}
	if (lpDataBuf != NULL) {
		delete lpDataBuf;
	}

	return hr;
}

/*===================================================================
CMetaUtil::AddError

Add an error to a given error collection.  Uses the string table to
get the error description.

Parameters:
	pCErrorCol	Pointer to the error collection to put errors in
	lId			Identifing constant of the type of error
	lSeverity	Severity of the error
	tszKey		Base part of the key where the error occurred
	tszSubKey	NULL or second part of the key where the error occured
	dwProperty	Id of the property where the error occured or 0

Returns:
	E_OUTOFMEMORY if allocation fails.
	E_INVALIDARG if bstrMachine == NULL or ppIReturn == NULL
	S_OK on success

Notes:
	I split the key parameter into 2 parts, because of the nature of
	the metabase API's taking 2 part keys, often you are working
	with keys in 2 parts.  This takes the responsibility for combining
	them from the caller, simplifying the caller and eliminating
	redundancy.
===================================================================*/
void CMetaUtil::AddError(CComObject<CCheckErrorCollection> *pCErrorCol,
						 long lId, 
						 long lSeverity, 
						 LPCTSTR tszKey,
						 LPCTSTR tszSubKey,
						 DWORD dwProperty) 
{
	ASSERT_POINTER(pCErrorCol, CComObject<CCheckErrorCollection>);
	ASSERT_STRING(tszKey);
	ASSERT_NULL_OR_STRING(tszSubKey);

	long lNumErrors;

	pCErrorCol->get_Count(&lNumErrors);
	if (((DWORD) lNumErrors) == m_dwMaxNumErrors) {
		lId = MUTIL_CHK_TOO_MANY_ERRORS;
		lSeverity = MUTIL_CHK_TOO_MANY_ERRORS_S;
		tszKey = _T("");
		tszSubKey = NULL;
		dwProperty = 0;
	}
	else if (((DWORD) lNumErrors) > m_dwMaxNumErrors) {
		// Too many errors, bail
		return;
	}

	// Get the description
	TCHAR tszDescription[1024];
	LoadString(_Module.GetResourceInstance(), IDS_CHK_BASE + lId, tszDescription, 1024);

	// Build the full key
	TCHAR tszFullKey[ADMINDATA_MAX_NAME_LEN];

	if (tszSubKey == NULL) {
		_tcscpy(tszFullKey, tszKey);
	}
	else {
		_tcscpy(tszFullKey, tszKey);
		_tcscat(tszFullKey, _T("/"));
		_tcscat(tszFullKey, tszSubKey);
	}

	// Report the error
	pCErrorCol->AddError(lId, lSeverity, tszDescription, tszFullKey, dwProperty);
}

/*===================================================================
CMetaUtil::KeyExists

Private function to see if a given key exists.

Parameters:
    hMDKey		Open metabase read handle
	tszSubKey	Subkey to check relatetive to hMDKey

Returns:
	TRUE if the key exists.  A key is considered to exist if on
		an open call, it is opened or ERROR_PATH_BUSY or 
		ERROR_ACCESS_DENIED is returned.
	FALSE otherwise
===================================================================*/
BOOL CMetaUtil::KeyExists(METADATA_HANDLE hMDKey, LPTSTR tszSubKey) 
{
	ASSERT_NULL_OR_STRING(tszSubKey);

	//Attempt to open the key
	USES_CONVERSION;
	HRESULT hr;
	METADATA_HANDLE hMDSubKey;

	hr = m_pIMeta->OpenKey(hMDKey,
						   T2W(tszSubKey),
						   METADATA_PERMISSION_READ,
					       MUTIL_OPEN_KEY_TIMEOUT,
						   &hMDSubKey);
	if (FAILED(hr)) {
		// Why?
		if ((HRESULT_CODE(hr) == ERROR_PATH_BUSY) ||
			(HRESULT_CODE(hr) == ERROR_ACCESS_DENIED)) {
			// It exists, just can't get to it
			return TRUE;
		}
		else {
			// Assume that it doesn't exist
			return FALSE;
		}
	}
	else { // SUCCEEDED(hr)
		m_pIMeta->CloseKey(hMDSubKey);
		return TRUE;
	}
}

/*===================================================================
CMetaUtil::PropertyExists

Private function to see if a given property exists.

Parameters:
    hMDKey		Open metabase read handle
	tszSubKey	Subkey to check relatetive to hMDKey
	dwId		Id of the property to check

Returns:
	TRUE if the property exists.  A property is considered to exist if 
		on an GetData call, it is retrived or ERROR_INSUFFICENT_BUFFER 
		or ERROR_ACCESS_DENIED is returned.
	FALSE otherwise
===================================================================*/
BOOL CMetaUtil::PropertyExists(METADATA_HANDLE hMDKey, 
							   LPTSTR tszSubKey, 
							   DWORD dwId) 
{
	ASSERT_NULL_OR_STRING(tszSubKey);

	USES_CONVERSION;
	HRESULT hr;
	LPWSTR wszSubKey;
	METADATA_RECORD mdr;
	BYTE *lpDataBuf = NULL;
	DWORD dwDataBufLen;
	DWORD dwReqDataLen;

	if (tszSubKey == NULL) {
		wszSubKey = NULL;
	}
	else {
		wszSubKey = T2W(tszSubKey);
	}

	//Setup the return buffer
	dwDataBufLen = 256;
	lpDataBuf = new BYTE[dwDataBufLen];
	if (lpDataBuf == NULL) {
		return FALSE;
	}

	// See if there is a KeyType property  MD_KEY_TYPE
	mdr.dwMDIdentifier = dwId;
	mdr.dwMDAttributes = METADATA_NO_ATTRIBUTES;
    mdr.dwMDUserType = ALL_METADATA;
	mdr.dwMDDataType = ALL_METADATA;
	mdr.dwMDDataLen = dwDataBufLen;
	mdr.pbMDData = (PBYTE) lpDataBuf;
	mdr.dwMDDataTag = 0;

	hr = m_pIMeta->GetData(hMDKey, wszSubKey, &mdr, &dwReqDataLen);

	delete lpDataBuf;

	if (SUCCEEDED(hr) || 
		(HRESULT_CODE(hr) == ERROR_INSUFFICIENT_BUFFER) || 
		(HRESULT_CODE(hr) == ERROR_ACCESS_DENIED)) {
		return TRUE;
	}
	else {
		return FALSE;
	}
}

/*===================================================================
CMetaUtil::CheckCLSID

Private function to look up a CLSID in the local registry.

Parameters:
	tszCLSID	CLSID to check in string format.

Returns:
	TRUE if the CLSID is in the local registry
	FALSE otherwise
===================================================================*/
BOOL CMetaUtil::CheckCLSID(LPCTSTR tszCLSID) {
	ASSERT_STRING(tszCLSID);

	HKEY hCLSIDsKey;
	HKEY hCLSIDKey;
	LONG lRet;

	// Open HKEY_CLASSES_ROOT\CLSID
	lRet = RegOpenKeyEx(HKEY_CLASSES_ROOT,
					   _T("CLSID"),
					   0,
					   KEY_READ,
					   &hCLSIDsKey);
	if (lRet != ERROR_SUCCESS) {
		return FALSE;
	}

	// Open the specific CLSID
	lRet = RegOpenKeyEx(hCLSIDsKey,
					   tszCLSID,
					   0,
					   KEY_READ,
					   &hCLSIDKey);
	if (lRet != ERROR_SUCCESS) {
		RegCloseKey(hCLSIDsKey);
		return FALSE;
	}

	// Close the keys
	RegCloseKey(hCLSIDsKey);
	RegCloseKey(hCLSIDKey);

	return TRUE;
}

/*===================================================================
CMetaUtil::CheckMTXPackage

Private function to look up a Microsoft Transaction Server package 
identifier (GUID) in the local registry.

Parameters:
	tszPackId	MTX package identifier (GUID) in string format

Returns:
	TRUE if the package id is in the local registry
	FALSE otherwise
===================================================================*/
BOOL CMetaUtil::CheckMTXPackage(LPCTSTR tszPackId) {
	ASSERT_STRING(tszPackId);

	HKEY hMTSPackKey;
	HKEY hPackIdKey;
	LONG lRet;

	// Open HKEY_LOCAL_MACHINE\Software\Microsoft\Transaction Server\Packages
	lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
					   _T("Software\\Microsoft\\Transaction Server\\Packages"),
					   0,
					   KEY_READ,
					   &hMTSPackKey);
	if (lRet != ERROR_SUCCESS) {
		return FALSE;
	}

	// Open the specific package id
	lRet = RegOpenKeyEx(hMTSPackKey,
					   tszPackId,
					   0,
					   KEY_READ,
					   &hPackIdKey);
	if (lRet != ERROR_SUCCESS) {
		RegCloseKey(hMTSPackKey);
		return FALSE;
	}

	// Close the keys
	RegCloseKey(hMTSPackKey);
	RegCloseKey(hPackIdKey);

	return TRUE;
}

/*===================================================================
CMetaUtil::CheckKeyType

Private method to check class information on a non-schema key via
the KeyType property.

Directly Generates:
	MUTIL_CHK_NO_KEYTYPE
	MUTIL_CHK_NO_KEYTYPE_NOT_FOUND

Parameters:
	pCErrorCol	Pointer to the error collection to put errors in
	hMDKey		Open metabase handle for the key to check
	tszKey		Full path of the key to check

Returns:
	E_OUTOFMEMORY if allocation fails.
	S_OK on success
===================================================================*/
HRESULT CMetaUtil::CheckKeyType(CComObject<CCheckErrorCollection> *pCErrorCol, 
								METADATA_HANDLE hMDKey, 
								LPTSTR tszKey) 
{
	ASSERT_POINTER(pCErrorCol, CComObject<CCheckErrorCollection>);
	ASSERT_STRING(tszKey);

	USES_CONVERSION;
	HRESULT hr;
	METADATA_RECORD mdr;
	BYTE *lpDataBuf = NULL;
	DWORD dwDataBufLen;
	DWORD dwReqDataLen;

	//Setup the return buffer
	dwDataBufLen = 256;
	lpDataBuf = new BYTE[dwDataBufLen];
	if (lpDataBuf == NULL) {
		return ::ReportError(E_OUTOFMEMORY);
	}

	// See if there is a KeyType property  MD_KEY_TYPE
	mdr.dwMDIdentifier = MD_KEY_TYPE;
	mdr.dwMDAttributes = METADATA_NO_ATTRIBUTES;
    mdr.dwMDUserType = ALL_METADATA;
	mdr.dwMDDataType = ALL_METADATA;
	mdr.dwMDDataLen = dwDataBufLen;
	mdr.pbMDData = (PBYTE) lpDataBuf;
	mdr.dwMDDataTag = 0;

	hr = m_pIMeta->GetData(hMDKey, NULL, &mdr, &dwReqDataLen);

	if (HRESULT_CODE(hr) == ERROR_INSUFFICIENT_BUFFER) {
		// Try a bigger buffer
		delete lpDataBuf;
		dwDataBufLen = dwReqDataLen;
		lpDataBuf = new BYTE[dwDataBufLen];
		if (lpDataBuf == NULL) {
			hr = E_OUTOFMEMORY;
			goto LError;
		}

		mdr.dwMDIdentifier = MD_KEY_TYPE;
		mdr.dwMDAttributes = METADATA_NO_ATTRIBUTES;
		mdr.dwMDUserType = ALL_METADATA;
		mdr.dwMDDataType = ALL_METADATA;
		mdr.dwMDDataLen = dwDataBufLen;
		mdr.pbMDData = (PBYTE) lpDataBuf;
		mdr.dwMDDataTag = 0;

		hr = m_pIMeta->GetData(hMDKey, NULL, &mdr, &dwReqDataLen);
	}

	if (hr == MD_ERROR_DATA_NOT_FOUND) {
		// Error: KeyType property not found
		AddError(pCErrorCol,
				 MUTIL_CHK_NO_KEYTYPE,
				 MUTIL_CHK_NO_KEYTYPE_S,
				 tszKey,
				 NULL,
				 0);
		goto LDone;
	}
	else if (FAILED(hr)) {
		// Unexpected error
		goto LError;
	}
	else {  
		// KeyType property exists, get class information
		LPTSTR tszClassName;
		CClassInfo *pCClassInfo;
		
		tszClassName = W2T(reinterpret_cast<LPWSTR> (lpDataBuf));
		pCClassInfo = m_pCSchemaTable->GetClassInfo(tszKey, tszClassName);

		if (pCClassInfo == NULL) {
			// Error: KeyType does not map to a class
			AddError(pCErrorCol,
					 MUTIL_CHK_NO_KEYTYPE_NOT_FOUND,
					 MUTIL_CHK_NO_KEYTYPE_NOT_FOUND_S,
					 tszKey,
					 NULL,
					 MD_KEY_TYPE);
			goto LDone;
		}
		else { // KeyType maps to a class name
			// Check mandatory properties
			CClassPropInfo *pCMandatoryList;

			pCMandatoryList = m_pCSchemaTable->GetMandatoryClassPropList(tszKey, tszClassName);
			while (pCMandatoryList != NULL) {
				// Make sure the property exists
				if (!PropertyExists(hMDKey, NULL, pCMandatoryList->GetId())) {
					AddError(pCErrorCol,
							 MUTIL_CHK_MANDATORY_PROP_MISSING,
							 MUTIL_CHK_MANDATORY_PROP_MISSING_S,
							 tszKey,
							 NULL,
							 pCMandatoryList->GetId());
				}

				// Next mandatory list element
				pCMandatoryList = pCMandatoryList->GetListNext();
			}
		}
	}
	
LDone:

	delete lpDataBuf;

	return S_OK;

LError:
	if (lpDataBuf != NULL) {
		delete lpDataBuf;
	}

	return hr;
}

/*===================================================================
CMetaUtil::CheckIfFileExists

Private function to check if there is indeed a file or dir at the 
path indicated. 

Parameters:
tszFSPath   The filesystem path to check.
pfExists    Returns true if the file or dir at the path exists,
            false if it does not. Indeterminate in error cases.

Returns:
    S_OK on success.
    other HRESULTs from subroutines otherwise.
===================================================================*/
HRESULT CMetaUtil::CheckIfFileExists(LPCTSTR tszFSPath,
                                     BOOL *pfExists)
{
    ASSERT_STRING(tszFSPath);

    DWORD dwResult; 
    DWORD dwLastError;
    HRESULT hr = S_OK;

    dwResult = GetFileAttributes(tszFSPath);

    if (dwResult == 0xFFFFFFFF) {

        dwLastError = GetLastError();

        if ((dwLastError == ERROR_FILE_NOT_FOUND) || (dwLastError == ERROR_PATH_NOT_FOUND)) {

            // The file or dir doesn't exist
            *pfExists = FALSE;

        } else {

            // Some other error occurred (access denied, etc.)
            hr = HRESULT_FROM_WIN32(dwLastError);
            *pfExists = FALSE;      // Callers shouldn't be looking at this
        }
        
    } else {

        // The file or dir is there
        *pfExists = TRUE;
    }

    return hr;
}


/*------------------------------------------------------------------
 * C N a m e T a b l e E n t r y
 */


/*===================================================================
CNameTableEntry::Init

Constructor

Parameters:
    tszName		Name to add to the table

Returns:
	E_OUTOFMEMORY if allocation failed
	S_OK on success
===================================================================*/
HRESULT CNameTableEntry::Init(LPCTSTR tszName) 
{
	ASSERT_STRING(tszName);

	m_tszName = new TCHAR[_tcslen(tszName) + 1];
	if (m_tszName == NULL) {
		return E_OUTOFMEMORY;
	}
	_tcscpy(m_tszName, tszName);

	return S_OK;
}


/*------------------------------------------------------------------
 * C N a m e T a b l e
 */

/*===================================================================
CNameTable::CNameTable

Constructor

Parameters:
	None

Returns:
	Nothing
===================================================================*/
CNameTable::CNameTable() 
{
	// Clear the hash table
	memset(m_rgpNameTable, 0, NAME_TABLE_HASH_SIZE * sizeof(CNameTableEntry *));
}

/*===================================================================
CNameTable::~CNameTable

Destructor

Parameters:
	None

Returns:
	Nothing
===================================================================*/
CNameTable::~CNameTable()
{
	int iIndex;
	CNameTableEntry *pCDelete;

	// For each hash table entry
	for (iIndex =0; iIndex < NAME_TABLE_HASH_SIZE; iIndex++) {
		// While the entry is not empty
		while (m_rgpNameTable[iIndex] != NULL) {
			// Nuke the first table entry
			ASSERT_POINTER(m_rgpNameTable[iIndex], CNameTableEntry);
			pCDelete = m_rgpNameTable[iIndex];
			m_rgpNameTable[iIndex] = pCDelete->m_pCHashNext;
			delete pCDelete;
		}
	}
}

/*===================================================================
CNameTable::IsCaseSenDup

Checks for a name table entry with the same case sensitive name.

Parameters:
	tszName		Name to check for a duplicate entry

Returns:
	TRUE if a duplicate entry is found
	FALSE otherwise
===================================================================*/
BOOL CNameTable::IsCaseSenDup(LPCTSTR tszName) 
{
	ASSERT_STRING(tszName);

	int iPos;
	CNameTableEntry *pCLoop;

	iPos = Hash(tszName);
	pCLoop = m_rgpNameTable[iPos];
	while (pCLoop != NULL) {
		ASSERT_POINTER(pCLoop, CNameTableEntry);
		ASSERT_STRING(pCLoop->m_tszName);
		if (_tcscmp(tszName, pCLoop->m_tszName) == 0) {
			return TRUE;
		}
		pCLoop = pCLoop->m_pCHashNext;
	}

	return FALSE;
}

/*===================================================================
CNameTable::IsCaseInsenDup

Checks for a name table entry with the same case insensitive name.

Parameters:
	tszName		Name to check for a duplicate entry

Returns:
	TRUE if a duplicate entry is found
	FALSE otherwise
===================================================================*/
BOOL CNameTable::IsCaseInsenDup(LPCTSTR tszName) 
{
	ASSERT_STRING(tszName);

	int iPos;
	CNameTableEntry *pCLoop;

	iPos = Hash(tszName);
	pCLoop = m_rgpNameTable[iPos];
	while (pCLoop != NULL) {
		ASSERT_POINTER(pCLoop, CNameTableEntry);
		ASSERT_STRING(pCLoop->m_tszName);
		if (_tcsicmp(tszName, pCLoop->m_tszName) == 0) {
			return TRUE;
		}
		pCLoop = pCLoop->m_pCHashNext;
	}

	return FALSE;
}

/*===================================================================
CNameTable::Add

Adds an entry to the name table

Parameters:
	tszName		Name to add to table

Returns:
	E_OUTOFMEMORY on allocation failure
	S_OK on success
===================================================================*/
HRESULT CNameTable::Add(LPCTSTR tszName)
{
	ASSERT_STRING(tszName);

	// Create an entry
	HRESULT hr;
	CNameTableEntry *pCNew;

	pCNew = new CNameTableEntry;
	if (pCNew == NULL) {
		return E_OUTOFMEMORY;
	}
	hr = pCNew->Init(tszName);
	if (FAILED(hr)){
		delete pCNew;
		return hr;
	}

	// Add it to the table
	int iPos;

	iPos = Hash(tszName);
	pCNew->m_pCHashNext = m_rgpNameTable[iPos];
	m_rgpNameTable[iPos] = pCNew;

	return S_OK;
}

/*===================================================================
CNameTable::Hash

Private hash function for the name table.  The hash is case 
insensitive.

Parameters:
	tszName		Name to hash

Returns:
	Hash value for the input name.
===================================================================*/
int CNameTable::Hash(LPCTSTR tszName)
{
	ASSERT_STRING(tszName);

	unsigned int uiSum;
	unsigned int uiIndex;

	uiSum = 0;
	for (uiIndex=0; uiIndex < _tcslen(tszName); uiIndex++) {
		// Make CharUpper do single character conversions
		uiSum += _totlower(tszName[uiIndex]);
	}

	return (uiSum % NAME_TABLE_HASH_SIZE);
}