/*=================================================================== 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 *pCErrorCol = NULL; ATLTRY(pCErrorCol = new CComObject); 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 *pCErrorCol, METADATA_HANDLE hMDMachine, LPTSTR tszMachine) { ASSERT_POINTER(pCErrorCol, CComObject); 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 (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 *pCErrorCol, METADATA_HANDLE hMDMachine, LPTSTR tszMachine) { ASSERT_POINTER(pCErrorCol, CComObject); 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 (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 *pCErrorCol, METADATA_HANDLE hMDMachine, LPTSTR tszMachine) { ASSERT_POINTER(pCErrorCol, CComObject); 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 *pCErrorCol, METADATA_HANDLE hMDClassKey, LPTSTR tszClassKey, LPTSTR tszClassSubKey) { ASSERT_POINTER(pCErrorCol, CComObject); 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 *pCErrorCol = NULL; ATLTRY(pCErrorCol = new CComObject); 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 (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 (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 (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 *pCErrorCol, long lId, long lSeverity, LPCTSTR tszKey, LPCTSTR tszSubKey, DWORD dwProperty) { ASSERT_POINTER(pCErrorCol, CComObject); 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 *pCErrorCol, METADATA_HANDLE hMDKey, LPTSTR tszKey) { ASSERT_POINTER(pCErrorCol, CComObject); 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 (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); }