|
|
#include "nt.h"
#include "ntdef.h"
#include "ntrtl.h"
#include "nturtl.h"
#include "stdio.h"
#include "sxs-rtl.h"
#undef INVALID_HANDLE_VALUE
#include "windows.h"
#include "environment.h"
#include "manifestcooked.h"
#include "assemblygac.h"
#include "bcl_common.h"
#include "bcl_w32unicodeinlinestringbuffer.h"
#include "search.h"
//
// Crypto stack
//
#include "hashers.h"
#include "digesters.h"
CEnv::StatusCode FsCopyFileWithHashGeneration( const CEnv::CConstantUnicodeStringPair &Source, const CEnv::CConstantUnicodeStringPair &Target, CHashObject &HashObjectTarget, CDigestMethod &DigestMethod ) { CEnv::StatusCode Result; CEnv::CByteRegion TempAllocation(NULL, 0); HANDLE SourceHandle = INVALID_HANDLE_VALUE; HANDLE TargetHandle = INVALID_HANDLE_VALUE; SIZE_T cbDidRead, cbOffset;
//
// Force initialization of the hasher and digester.
//
HashObjectTarget.Initialize(); DigestMethod.Initialize(HashObjectTarget);
//
// Snag some memory to perform the copies
//
Result = CEnv::AllocateHeap(2048, TempAllocation, NULL); if (CEnv::DidFail(Result)) goto Exit; //
// Obtain handles for both files
//
Result = CEnv::GetFileHandle(&SourceHandle, Source, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING); if (CEnv::DidFail(Result)) goto Exit;
Result = CEnv::GetFileHandle(&TargetHandle, Target, GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS); if (CEnv::DidFail(Result)) goto Exit;
//
// Now spin through the file reading blocks, digesting them.
//
cbOffset = 0; do { Result = CEnv::ReadFile(SourceHandle, TempAllocation, cbDidRead); if (CEnv::DidFail(Result)) goto Exit;
if (cbDidRead == 0) break;
Result = DigestMethod.DigestExtent(HashObjectTarget, cbOffset, TempAllocation); if (CEnv::DidFail(Result)) goto Exit;
cbOffset += cbDidRead;
Result = CEnv::WriteFile(TargetHandle, TempAllocation, cbDidRead); if (CEnv::DidFail(Result)) goto Exit; } while (true);
//
// Ok, we're done.
//
Exit: if (TempAllocation.GetPointer()) CEnv::FreeHeap(TempAllocation.GetPointer(), NULL);
if (SourceHandle != INVALID_HANDLE_VALUE) CEnv::CloseHandle(SourceHandle);
if (TargetHandle != INVALID_HANDLE_VALUE) CEnv::CloseHandle(TargetHandle); return Result; }
COSAssemblyCache* CDotNetSxsAssemblyCache::CreateSelf( ULONG ulFlags, const GUID *pCacheIdent ) { CDotNetSxsAssemblyCache *pAllocation = NULL; CEnv::StatusCode status; if (!pCacheIdent || (*pCacheIdent != CacheIdentifier)) return NULL;
status = CEnv::AllocateHeap(sizeof(CDotNetSxsAssemblyCache), (PVOID*)&pAllocation, NULL); if (CEnv::DidFail(status)) return NULL;
pAllocation->CDotNetSxsAssemblyCache::CDotNetSxsAssemblyCache(ulFlags); return pAllocation; }
CDotNetSxsAssemblyCache::CDotNetSxsAssemblyCache( ULONG ulFlags ) { }
CDotNetSxsAssemblyCache::~CDotNetSxsAssemblyCache() { }
CEnv::StatusCode CDotNetSxsAssemblyCache::IdentityToTargetPath( const CAssemblyIdentity& Ident, CEnv::CStringBuffer &PathSegment ) { typedef CEnv::CConstantUnicodeStringPair CStringPair;
enum { Identity_ProcArch = 0, Identity_Name, Identity_PublicKeyToken, Identity_Version, Identity_Language, };
static const struct { CStringPair Namespace; CStringPair Name; CStringPair DefaultValue; } PathComponents[] = { { CStringPair(), CStringPair(L"processorArchitecture", NUMBER_OF(L"processorArchitecture") - 1), CStringPair(L"data", 4) }, { CStringPair(), CStringPair(L"name", NUMBER_OF(L"name") - 1), CStringPair() }, { CStringPair(), CStringPair(L"publicKeyToken", NUMBER_OF(L"publicKeyToken") - 1), CStringPair(L"no-public-key-token", NUMBER_OF(L"no-public-key-token") - 1) }, { CStringPair(), CStringPair(L"version", NUMBER_OF(L"version") - 1), CStringPair(L"0.0.0.0", 7) }, { CStringPair(), CStringPair(L"language", NUMBER_OF(L"language") - 1), CStringPair(L"x-ww", 4) }, };
CEnv::StatusCode Result = CEnv::SuccessCode; CStringPair FoundComponents[NUMBER_OF(PathComponents) * 2]; SIZE_T i;
PathSegment.Clear(); //
// Look for these identity components in order
//
for (i = 0; i < NUMBER_OF(PathComponents); i++) { Result = Ident.FindAttribute(PathComponents[i].Namespace, PathComponents[i].Name, FoundComponents[2*i]); if (Result == CEnv::NotFound) { FoundComponents[2*i] = PathComponents[i].DefaultValue; } else if (CEnv::DidFail(Result)) { goto Exit; }
FoundComponents[2*i+1].SetPointerAndCount(L"_", 1); }
//
// Meddle with the 'name' string to ensure that it falls under the filesystem limits
//
// TODO: Fix name string
//
if (!PathSegment.Assign(NUMBER_OF(FoundComponents), FoundComponents)) { //
// I think we need to fix the string APIs such that on win32 they return lasterror
// rather than returning BOOL all the time - it'd make transitioning into the RTL
// much easier...
//
Result = CEnv::OutOfMemory; PathSegment.Clear(); goto Exit; }
Result = CEnv::SuccessCode; Exit: return Result; }
CEnv::StatusCode CDotNetSxsAssemblyCache::Initialize() { this->m_WindowsDirectory = CEnv::StringFrom(USER_SHARED_DATA->NtSystemRoot); return CEnv::SuccessCode; }
CEnv::StatusCode CDotNetSxsAssemblyCache::EnsurePathsAvailable() { CEnv::CConstantUnicodeStringPair PathBuilder[3] = { m_WindowsDirectory, s_BaseDirectory, s_ManifestsPath }; CEnv::StatusCode Result = CEnv::SuccessCode;
//
// Ensure that the WinSxS store is available:
// {windir}\{winsxs2}
// {windir}\{winsxs2}\{manifests}
//
// This call has the side-effect of building all three levels.
//
if (CEnv::DidFail(Result = CEnv::CreateDirectory(3, PathBuilder))) goto Exit;
Result = CEnv::SuccessCode; Exit: return Result; }
/*
Installation has these phases:
- Create mandatory directories in the store - Store path - Manifests - Create the target assembly identity - If the target assembly is a policy - Write the .policy file into the right place - Notify the store metadata manager about the new policy - Done - If the target assembly is not already installed - Create '%storeroot%\%asmpath%-temp' to store files - Stream files into install target path, validating hashes - Rename path to remove -temp marker - Place the .manifest - Notify the store metadata manager about the new manifest - Done - If the target assembly is already installed - Notify the store metadata manager about another reference - Done
Unless: - If the "refresh" flag is set, then the bits are copied in again */
CEnv::StatusCode CDotNetSxsAssemblyCache::InstallAssembly( ULONG Flags, PMANIFEST_COOKED_DATA ManifestData, const CEnv::CConstantUnicodeStringPair &FilePath ) { CEnv::StatusCode Result = CEnv::SuccessCode; CNtEnvironment::StatusCode NtResult; CEnv::CStringBuffer FirstFile, SecondFile; CAssemblyIdentity ThisIdentity;
//
// First, let's ensure that the required directories are present
//
if (CEnv::DidFail(Result = EnsurePathsAvailable())) goto Exit;
//
// Now let's stage the files in question into an installtemp location, validating as we
// copy. We will never store the actual manifest, we will only store the solidified blob
// that we got after parsing and cooking it. But first, let's turn that set of identity
// values into an actual identity...
//
// Result = ThisIdentity.C
Result = CEnv::SuccessCode; Exit: return Result; }
CEnv::StatusCode CDotNetSxsAssemblyCache::UninstallAssembly( ULONG Flags, PMANIFEST_COOKED_DATA ManifestData, UninstallResult & Result ) { return STATUS_NOT_IMPLEMENTED; }
const GUID CDotNetSxsAssemblyCache::CacheIdentifier = { /* 37e3c37d-667f-4aee-8dab-a0d117acfa68 */ 0x37e3c37d, 0x667f, 0x4aee, {0x8d, 0xab, 0xa0, 0xd1, 0x17, 0xac, 0xfa, 0x68} };
const CEnv::CConstantUnicodeStringPair CDotNetSxsAssemblyCache::s_PoliciesPath(L"Policies", 8); const CEnv::CConstantUnicodeStringPair CDotNetSxsAssemblyCache::s_ManifestsPath(L"Manifests", 9); const CEnv::CConstantUnicodeStringPair CDotNetSxsAssemblyCache::s_InstallTemp(L"InstallTemp", 11); const CEnv::CConstantUnicodeStringPair CDotNetSxsAssemblyCache::s_BaseDirectory(L"WinSxS2", 7);
//
// Assembly identity stuff - should get split into another file
//
CAssemblyIdentity::CAssemblyIdentity() : m_cIdentityValues(0), m_IdentityValues(NULL), m_fFrozen(false), m_fSorted(true), m_fHashDirtyV1(true), m_fHashDirtyV2(true), m_ulHashV1(0), m_cAvailableIdentitySlots(0) { ZeroMemory(m_IdentityShaHash, sizeof(m_IdentityShaHash)); }
CAssemblyIdentity::~CAssemblyIdentity() { DeleteAllValues(); }
CEnv::StatusCode CAssemblyIdentity::DeleteAllValues() { //
// Let's keep the table around, just because that's handy
//
if (m_IdentityValues != NULL) { for (SIZE_T c = 0; c < m_cIdentityValues; c++) { CEnv::FreeHeap(m_IdentityValues[c], NULL); m_IdentityValues[c] = NULL; } m_cIdentityValues = 0; }
this->m_fHashDirtyV1 = this->m_fHashDirtyV2 = true; this->m_ulHashV1 = 0; return CEnv::SuccessCode; }
CEnv::StatusCode CAssemblyIdentity::SetAttribute( const CStringPair &Namespace, const CStringPair &Name, const CStringPair &Value, bool fReplace ) { CEnv::StatusCode Result; SIZE_T cIndex;
//
// If looking for this value had some other problem aside from not being found,
// then exit.
//
Result = this->InternalFindValue(Namespace, Name, cIndex); if (CEnv::DidFail(Result) && (Result != CEnv::NotFound)) goto Exit;
//
// Easy - create a new attribute to hold this value.
//
if (Result == CEnv::NotFound) { CIdentityValue *NewValue = NULL; SIZE_T c;
if (CEnv::DidFail(Result = this->InternalAllocateValue(Namespace, Name, Value, NewValue))) goto Exit;
//
// After we've allocated one, go insert it into the table. If that failed,
// the clean up and exit
//
if (CEnv::DidFail(Result = this->InternalInsertValue(NewValue))) { this->InternalDestroyValue(NewValue); goto Exit; } } else { CIdentityValue *ThisValue = m_IdentityValues[cIndex]; const SIZE_T cbRequiredSize = ((Namespace.GetCount() + Name.GetCount() + Value.GetCount()) * sizeof(WCHAR)) + sizeof(CIdentityValue);
ASSERT(ThisValue != NULL);
//
// Spiffy, there's enough space in the existing value to hold the data from
// the input value.
//
if (cbRequiredSize < ThisValue->cbAllocationSize) { if (CEnv::DidFail(Result = ThisValue->WriteValues(Namespace, Name, Value))) goto Exit; } //
// Bah, have to reallocate this entry
//
else { CIdentityValue *NewValue = NULL;
if (CEnv::DidFail(Result = this->InternalAllocateValue(Namespace, Name, Value, NewValue))) goto Exit;
m_IdentityValues[cIndex] = NewValue; NewValue = NULL;
if (CEnv::DidFail(Result = this->InternalDestroyValue(ThisValue))) goto Exit; } }
//
// Now clear all the relevant flags
//
this->m_fHashDirtyV1 = this->m_fHashDirtyV2 = true; this->m_fSorted = false;
Result = CEnv::SuccessCode; Exit: return Result; }
CEnv::StatusCode CAssemblyIdentity::DeleteAttribute( const CStringPair &Namespace, const CStringPair &Name ) { SIZE_T cIndex; CEnv::StatusCode Result; CIdentityValue *Victim = NULL;
//
// InternalFindValue will return NotFound if it didn't find it.
//
if (CEnv::DidFail(Result = this->InternalFindValue(Namespace, Name, cIndex))) goto Exit;
//
// Remember the one we found, clear its slot, delete the allocation
//
Victim = m_IdentityValues[cIndex]; m_IdentityValues[cIndex] = NULL;
//
// And clear the state flags before we delete it
//
this->m_fHashDirtyV1 = this->m_fHashDirtyV2 = true; this->m_fSorted = false; if (CEnv::DidFail(Result = this->InternalDestroyValue(Victim))) goto Exit;
Result = CEnv::SuccessCode; Exit: return Result; }
CEnv::StatusCode CAssemblyIdentity::CIdentityValue::WriteValues( const CStringPair & InNamespace, const CStringPair & InName, const CStringPair & InValue ) { PWSTR pwszWriteCursor = (PWSTR)(this + 1); const SIZE_T cRequired = ((InNamespace.GetCount() + InName.GetCount() + InValue.GetCount()) * sizeof(WCHAR)) * sizeof(*this);
if (cRequired < this->cbAllocationSize) { return CEnv::NotEnoughBuffer; }
this->HashV1Valid = false; this->HashV1 = 0;
this->Namespace.SetPointerAndCount(pwszWriteCursor, InNamespace.GetCount()); memcpy(pwszWriteCursor, InNamespace.GetPointer(), InNamespace.GetCount() * sizeof(WCHAR)); pwszWriteCursor += InNamespace.GetCount();
this->Name.SetPointerAndCount(pwszWriteCursor, InName.GetCount()); memcpy(pwszWriteCursor, InName.GetPointer(), InName.GetCount() * sizeof(WCHAR)); pwszWriteCursor += InName.GetCount();
this->Value.SetPointerAndCount(pwszWriteCursor, InValue.GetCount()); memcpy(pwszWriteCursor, InValue.GetPointer(), InValue.GetCount() * sizeof(WCHAR));
return CEnv::SuccessCode; }
CEnv::StatusCode CAssemblyIdentity::CIdentityValue::Compare( const CAssemblyIdentity::CIdentityValue& Other, int &iResult ) const { CEnv::StatusCode Result; int iMyResult = 0;
iResult = -1; //
// this == this
//
if (this == &Other) { iResult = 0; return CEnv::SuccessCode; }
//
// Namespace first, then name.
//
if (CEnv::DidFail(Result = CEnv::CompareStrings(this->Namespace, Other.Namespace, iMyResult))) goto Exit;
//
// Only bother if the namespaces match
//
if (iMyResult == 0) { if (CEnv::DidFail(Result = CEnv::CompareStringsCaseInsensitive(this->Name, Other.Name, iMyResult))) goto Exit; }
iResult = iMyResult;
Result = CEnv::SuccessCode; Exit: return Result; }
CEnv::StatusCode CAssemblyIdentity::InternalDestroyValue( CIdentityValue *Victim ) { //
// This just calls heapfree on the attribute
//
return CEnv::FreeHeap((PVOID)Victim, NULL); }
CEnv::StatusCode CAssemblyIdentity::InternalAllocateValue( const CStringPair &Namespace, const CStringPair &Name, const CStringPair &Value, CAssemblyIdentity::CIdentityValue* &pCreated ) { const SIZE_T cbRequired = ((Namespace.GetCount() + Name.GetCount() + Value.GetCount()) * sizeof(WCHAR)) + sizeof(CIdentityValue); CIdentityValue *pTempCreated = NULL; CEnv::StatusCode Result;
pCreated = NULL;
if (CEnv::DidFail(Result = CEnv::AllocateHeap(cbRequired, (PVOID*)&pTempCreated, NULL))) goto Exit;
if (CEnv::DidFail(Result = pTempCreated->WriteValues(Namespace, Name, Value))) goto Exit;
pCreated = pTempCreated; pTempCreated = NULL; Result = CEnv::SuccessCode; Exit: if (pTempCreated) { CEnv::FreeHeap(pTempCreated, NULL); } return Result; }
//
// Non-const version can sort.
//
CEnv::StatusCode CAssemblyIdentity::InternalFindValue( const CStringPair & Namespace, const CStringPair & Name, SIZE_T &cIndex ) { CEnv::StatusCode Result; cIndex = -1;
if (!this->m_fSorted) { if (CEnv::DidFail(Result = this->SortIdentityAttributes())) goto Exit; }
//
// Use the built-in searcher on the const one.
//
Result = (const_cast<const CAssemblyIdentity&>(*this)).InternalFindValue(Namespace, Name, cIndex);
Exit: return Result; }
CEnv::StatusCode CAssemblyIdentity::FindAttribute( const CStringPair & Namespace, const CStringPair & Name, CStringPair & Value ) { CEnv::StatusCode Result; SIZE_T cIndex;
Value.SetPointerAndCount(NULL, 0);
Result = this->InternalFindValue(Namespace, Name, cIndex); if (!CEnv::DidFail(Result)) { Value = this->m_IdentityValues[cIndex]->Value; Result = CEnv::SuccessCode; }
return Result; }
CEnv::StatusCode CAssemblyIdentity::FindAttribute( const CStringPair &Namespace, const CStringPair &Name, CStringPair& Value ) const { CEnv::StatusCode Result; SIZE_T cIndex;
Value.SetPointerAndCount(NULL, 0);
Result = this->InternalFindValue(Namespace, Name, cIndex);
if (!CEnv::DidFail(Result)) { Value = this->m_IdentityValues[cIndex]->Value; Result = CEnv::SuccessCode; }
return Result; }
CEnv::StatusCode CAssemblyIdentity::InternalFindValue( const CStringPair &Namespace, const CStringPair &Name, SIZE_T &cIndex ) const { CEnv::StatusCode Result; SIZE_T cLow, cHigh; CIdentityValue ComparisonDump;
cIndex = -1; ComparisonDump.Namespace = Namespace; ComparisonDump.Name = Name;
if (!this->m_fSorted) { //
// Ick, linear search.
//
for (cLow = 0; cLow < this->m_cIdentityValues; cLow++) { int iResult = 0; CIdentityValue *pFound = this->m_IdentityValues[cLow];
//
// Possible to have holes in the const linear-search version
//
if (!pFound) continue;
if (CEnv::DidFail(Result = pFound->Compare(ComparisonDump, iResult))) goto Exit;
if (iResult == 0) { cIndex = cLow; Result = CEnv::SuccessCode; goto Exit; } } } else { //
// We're doing our own bsearch here.
//
cLow = 0; cHigh = this->m_cIdentityValues; while (cLow < cHigh) { SIZE_T cMiddle = (cHigh - cLow) / 2; int iResult = 0; CIdentityValue *pFound = this->m_IdentityValues[cMiddle];
//
// The sorting should have reorganized the attributes so NULL slots
// were past the number of in-use identity values.
//
ASSERT(pFound != NULL);
if (CEnv::DidFail(Result = pFound->Compare(ComparisonDump, iResult))) goto Exit;
if (iResult == 0) { cIndex = cMiddle; Result = CEnv::SuccessCode; goto Exit; } else if (iResult < 0) { cHigh = cMiddle; continue; } else if (iResult > 0) { cLow = cMiddle; continue; } } } Result = CEnv::NotFound; Exit: return Result; }
#define ASSEMBLY_IDENTITY_TABLE_EXPANDO_FACTOR (20)
CEnv::StatusCode CAssemblyIdentity::Freeze() { CEnv::StatusCode Result;
if (m_fFrozen) return CEnv::SuccessCode; if (CEnv::DidFail(Result = SortIdentityAttributes())) return Result;
if (CEnv::DidFail(Result = RegenerateHash())) return Result;
m_fFrozen = true;
return CEnv::SuccessCode; }
CEnv::StatusCode CAssemblyIdentity::RegenerateHash() { return CEnv::NotImplemented; }
CEnv::StatusCode CAssemblyIdentity::InternalInsertValue( CAssemblyIdentity::CIdentityValue * NewValue ) { CEnv::StatusCode Result; //
// If the number of values is the same as the available slots, then we
// have to expand the internal table.
//
if (m_cIdentityValues == m_cAvailableIdentitySlots) { //
// Simple logic - increase by 20 slots each time
//
CIdentityValue** ppNewTable = NULL; CIdentityValue** ppOldTable = m_IdentityValues; const SIZE_T cNewSlots = m_cAvailableIdentitySlots + ASSEMBLY_IDENTITY_TABLE_EXPANDO_FACTOR; const SIZE_T cbRequired = sizeof(CIdentityValue*) * cNewSlots;
if (CEnv::DidFail(Result = CEnv::AllocateHeap(cbRequired, (PVOID*)&ppNewTable, NULL))) goto Exit;
//
// Pointers make this easy
//
if (ppOldTable) { memcpy(ppNewTable, ppOldTable, sizeof(CIdentityValue*) * m_cAvailableIdentitySlots); }
//
// Clear out the data in these slots
//
for (SIZE_T i = m_cAvailableIdentitySlots; i < cNewSlots; i++) { ppNewTable[i] = NULL; } m_IdentityValues = ppNewTable; m_cAvailableIdentitySlots = cNewSlots;
//
// Free the old table
//
if (ppOldTable) { CEnv::FreeHeap((PVOID)ppOldTable, NULL); } }
ASSERT(m_IdentityValues != NULL); ASSERT(m_cIdentityValues < m_cAvailableIdentitySlots); m_IdentityValues[m_cIdentityValues++] = NewValue; Result = CEnv::SuccessCode; Exit: return Result; }
int __cdecl CAssemblyIdentity::SortingCallback( const CAssemblyIdentity::CIdentityValue **left, const CAssemblyIdentity::CIdentityValue **right ) { const CIdentityValue *pLeft = *left; const CIdentityValue *pRight = *right; CEnv::StatusCode Result = CEnv::SuccessCode; int iResult = 0;
//
// Percolate NULL slots towards the 'end'
//
if (!left && !right) { return 0; } else if (!left && right) { return 1; } else if (left && !right) { return -1; }
ASSERT(pLeft && pRight);
Result = pLeft->Compare(*pRight, iResult); ASSERT(!CEnv::DidFail(Result));
return iResult; }
CEnv::StatusCode CAssemblyIdentity::SortIdentityAttributes() { CEnv::StatusCode Result;
if (this->m_cIdentityValues == 0) { m_fSorted = true; }
if (m_fSorted) { return CEnv::SuccessCode; }
qsort( this->m_IdentityValues, this->m_cAvailableIdentitySlots, sizeof(this->m_IdentityValues[0]), (int (__cdecl*)(const void*, const void*))SortingCallback );
//
// Sorted now, but we've invalidated the hash of the overall object.
//
m_fSorted = true; m_fHashDirtyV1 = m_fHashDirtyV2 = true;
return CEnv::SuccessCode; }
/*
class CAssemblyIdentity { public: typedef CEnv::CConstantUnicodeStringPair CStringPair; CAssemblyIdentity(); ~CAssemblyIdentity();
CEnv::StatusCode SetAttribute(const CStringPair &Namespace, const CStringPair &Name, const CStringPair &Value, bool fReplace = true); CEnv::StatusCode SetAttribute(const CStringPair &Name, const CStringPair& Value, bool fReplace = true) { return SetAttribute(CStringPair(), Name, Value, fReplace); } CEnv::StatusCode DeleteAttribute(const CStringPair &Namespace, const CStringPair &Name);
//
// The const version will do a linear or a bsearch depending on the sorted state. The non-const
// version will sort the internal attribute list first if necessary before looking up the value
//
CEnv::StatusCode FindAttribute(const CStringPair &Namespace, const CStringPair &Name, CStringPair& Value) const; CEnv::StatusCode FindAttribute(const CStringPair &Namespace, const CStringPair &Name, CStringPair& Value); unsigned long IdentityHash() const; unsigned long IdentityHash(); unsigned long long IdentityHashV2() const; unsigned long long IdentityHashV2();
//
// Maintainence stuff
//
CEnv::StatusCode Freeze(); CEnv::StatusCode DeleteAllValues();
static CEnv::StatusCode ConstructFromCookedData( CAssemblyIdentity& Target, PMANIFEST_COOKED_IDENTITY IdentityData );
protected:
//
// This is an all-in-one allocation blob
//
typedef struct { CStringPair Namespace; CStringPair Name; CStringPair Value; } CIdentityValue;
SIZE_T m_cIdentityValues; CIdentityValue** m_IdentityValues;
bool m_fFrozen; bool m_fSorted; bool m_fHashDirty;
CEnv::StatusCode RegenerateHash(); CEnv::StatusCode SortIdentityAttributes(); int SortingCallback(const CIdentityValue *left, const CIdentityValue *right); }; */
|