/*++ Copyright (c) 1991 Microsoft Corporation Module Name: cmsubs.c Abstract: This module various support routines for the configuration manager. The routines in this module are not independent enough to be linked into any other program. The routines in cmsubs2.c are. Author: Bryan M. Willman (bryanwi) 12-Sep-1991 --*/ #include "cmp.h" FAST_MUTEX CmpKcbLock; FAST_MUTEX CmpPostLock; PCM_KEY_HASH *CmpCacheTable; ULONG CmpHashTableSize = 2048; ULONG CmpDelayedCloseSize = 512; PCM_KEY_CONTROL_BLOCK *CmpDelayedCloseTable; PCM_KEY_CONTROL_BLOCK *CmpDelayedCloseCurrent; ULONG_PTR CmpDelayedFreeIndex=0; PCM_NAME_HASH *CmpNameCacheTable; VOID CmpRemoveKeyHash(IN PCM_KEY_HASH KeyHash); PCM_KEY_CONTROL_BLOCK CmpInsertKeyHash(IN PCM_KEY_HASH KeyHash, IN BOOLEAN FakeKey); // private prototype for recursive worker VOID CmpDereferenceNameControlBlockWithLock(PCM_NAME_CONTROL_BLOCK Ncb); #ifdef KCB_TO_KEYBODY_LINK VOID CmpDumpKeyBodyList(IN PCM_KEY_CONTROL_BLOCK kcb, IN PULONG Count); #endif #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE,CmpSearchForOpenSubKeys) #pragma alloc_text(PAGE,CmpReferenceKeyControlBlock) #pragma alloc_text(PAGE,CmpGetNameControlBlock) #pragma alloc_text(PAGE,CmpDereferenceNameControlBlockWithLock) #pragma alloc_text(PAGE,CmpCleanUpSubKeyInfo) #pragma alloc_text(PAGE,CmpCleanUpKcbValueCache) #pragma alloc_text(PAGE,CmpCleanUpKcbCacheWithLock) #pragma alloc_text(PAGE,CmpConstructName) #pragma alloc_text(PAGE,CmpRemoveFromDelayedClose) #pragma alloc_text(PAGE,CmpCreateKeyControlBlock) #pragma alloc_text(PAGE,CmpSearchKeyControlBlockTree) #pragma alloc_text(PAGE,CmpDereferenceKeyControlBlock) #pragma alloc_text(PAGE,CmpDereferenceKeyControlBlockWithLock) #pragma alloc_text(PAGE,CmpRemoveKeyControlBlock) #pragma alloc_text(PAGE,CmpFreeKeyBody) #pragma alloc_text(PAGE,CmpInsertKeyHash) #pragma alloc_text(PAGE,CmpRemoveKeyHash) #pragma alloc_text(PAGE,CmpInitializeCache) #ifdef KCB_TO_KEYBODY_LINK #pragma alloc_text(PAGE,CmpDumpKeyBodyList) #pragma alloc_text(PAGE,CmpFlushNotifiesOnKeyBodyList) #endif #endif #ifdef KCB_TO_KEYBODY_LINK VOID CmpDumpKeyBodyList(IN PCM_KEY_CONTROL_BLOCK kcb, IN PULONG Count) { PCM_KEY_BODY KeyBody; PUNICODE_STRING Name; if( IsListEmpty(&(kcb->KeyBodyListHead)) == TRUE ) { // Nobody has this subkey open, but for sure some subkey must be open. nicely return. return; } Name = CmpConstructName(kcb); if( !Name ){ // oops, we're low on resources return; } // now iterate through the list of KEY_BODYs referencing this kcb KeyBody = (PCM_KEY_BODY)kcb->KeyBodyListHead.Flink; while( KeyBody != (PCM_KEY_BODY)(&(kcb->KeyBodyListHead)) ) { KeyBody = CONTAINING_RECORD(KeyBody, CM_KEY_BODY, KeyBodyList); // sanity check: this should be a KEY_BODY ASSERT_KEY_OBJECT(KeyBody); // dump it's name and owning process DbgPrint("Process %lx :: Key %wZ \n",KeyBody->Process,Name); // count it (*Count)++; KeyBody = (PCM_KEY_BODY)KeyBody->KeyBodyList.Flink; } ExFreePoolWithTag(Name, CM_NAME_TAG | PROTECTED_POOL); } VOID CmpFlushNotifiesOnKeyBodyList(IN PCM_KEY_CONTROL_BLOCK kcb) { PCM_KEY_BODY KeyBody; if( IsListEmpty(&(kcb->KeyBodyListHead)) == FALSE ) { // now iterate through the list of KEY_BODYs referencing this kcb KeyBody = (PCM_KEY_BODY)kcb->KeyBodyListHead.Flink; while( KeyBody != (PCM_KEY_BODY)(&(kcb->KeyBodyListHead)) ) { KeyBody = CONTAINING_RECORD(KeyBody, CM_KEY_BODY, KeyBodyList); // sanity check: this should be a KEY_BODY ASSERT_KEY_OBJECT(KeyBody); // flush any notifies that might be set on it CmpFlushNotify(KeyBody); KeyBody = (PCM_KEY_BODY)KeyBody->KeyBodyList.Flink; } } } #endif ULONG CmpSearchForOpenSubKeys(PCM_KEY_CONTROL_BLOCK KeyControlBlock, SUBKEY_SEARCH_TYPE SearchType) /*++ Routine Description: This routine searches the KCB tree for any open handles to keys that are subkeys of the given key. It is used by CmRestoreKey to verify that the tree being restored to has no open handles. Arguments: KeyControlBlock - Supplies the key control block for the key for which open subkeys are to be found. SearchType - the type of the search SearchIfExist - exits at the first open subkey found ==> returns 1 if any subkey is open SearchAndDeref - Forces the keys underneath the Key referenced KeyControlBlock to be marked as not referenced (see the REG_FORCE_RESTORE flag in CmRestoreKey) returns 1 if at least one deref was made SearchAndCount - Counts all open subkeys - returns the number of them Return Value: TRUE - open handles to subkeys of the given key exist FALSE - open handles to subkeys of the given key do not exist. --*/ { ULONG i; PCM_KEY_HASH *Current; PCM_KEY_CONTROL_BLOCK kcb; PCM_KEY_CONTROL_BLOCK Realkcb; PCM_KEY_CONTROL_BLOCK Parent; USHORT LevelDiff, l; ULONG Count = 0; // Registry lock should be held exclusively, so no need to KCB lock ASSERT_CM_LOCK_OWNED_EXCLUSIVE(); // First, clean up all subkeys in the cache for (i=0; iRefCount == 0) { // This kcb is in DelayClose case, remove it. CmpRemoveFromDelayedClose(kcb); CmpCleanUpKcbCacheWithLock(kcb); // The HashTable is changed, start over in this index again. Current = &CmpCacheTable[i]; continue; } Current = &kcb->NextHash; } } if (KeyControlBlock->RefCount == 1) { // There is only one open handle, so there must be no open subkeys. Count = 0; } else { // Now search for an open subkey handle. Count = 0; #ifdef KCB_TO_KEYBODY_LINK // dump the root first if we were asked to do so. if(SearchType == SearchAndCount) { CmpDumpKeyBodyList(KeyControlBlock,&Count); } #endif for (i=0; iTotalLevels > KeyControlBlock->TotalLevels) { LevelDiff = kcb->TotalLevels - KeyControlBlock->TotalLevels; Parent = kcb; for (l=0; lParentKcb; } if (Parent == KeyControlBlock) { // Found a match; if( SearchType == SearchIfExist ) { Count = 1; break; } else if(SearchType == SearchAndDeref) { // Mark the key as deleted, remove it from cache, but don't add it // to the Delay Close table (we want the key to be visible only to the one(s) that have open handles on it. ASSERT_CM_LOCK_OWNED_EXCLUSIVE(); // flush any pending notifies as the kcb won't be around any longer #ifdef KCB_TO_KEYBODY_LINK CmpFlushNotifiesOnKeyBodyList(kcb); #endif CmpCleanUpSubKeyInfo(kcb->ParentKcb); kcb->Delete = TRUE; CmpRemoveKeyControlBlock(kcb); kcb->KeyCell = HCELL_NIL; kcb->KeyNode = NULL; Count++; // Restart the search goto StartDeref; } else if(SearchType == SearchAndCount) { // here do the dumping and count incrementing stuff #ifdef KCB_TO_KEYBODY_LINK CmpDumpKeyBodyList(kcb,&Count); #else Count++; #endif } } } Current = &kcb->NextHash; } } } return Count; } BOOLEAN CmpReferenceKeyControlBlock(PCM_KEY_CONTROL_BLOCK KeyControlBlock) { if (KeyControlBlock->RefCount == 0) { CmpRemoveFromDelayedClose(KeyControlBlock); } if ((USHORT)(KeyControlBlock->RefCount + 1) == 0) { // We have maxed out the ref count on this key. // Probably some bogus app has opened the same key 64K times without ever closing it. // Just fail the call return (FALSE); } else { ++(KeyControlBlock->RefCount); return (TRUE); } } PCM_NAME_CONTROL_BLOCK CmpGetNameControlBlock(PUNICODE_STRING NodeName) { PCM_NAME_CONTROL_BLOCK Ncb; ULONG Cnt; WCHAR *Cp; WCHAR *Cp2; ULONG Index; ULONG i; ULONG Size; PCM_NAME_HASH CurrentName; ULONG rc; BOOLEAN NameFound = FALSE; USHORT NameSize; BOOLEAN NameCompressed; ULONG NameConvKey=0; LONG Result; // Calculate the ConvKey for this NadeName; Cp = NodeName->Buffer; for (Cnt=0; CntLength; Cnt += sizeof(WCHAR)) { if (*Cp != OBJ_NAME_PATH_SEPARATOR) { NameConvKey = 37 * NameConvKey + (ULONG) RtlUpcaseUnicodeChar(*Cp); } ++Cp; } // Find the Name Size; NameCompressed = TRUE; NameSize = NodeName->Length / sizeof(WCHAR); for (i=0;iLength/sizeof(WCHAR);i++) { if ((USHORT)NodeName->Buffer[i] > (UCHAR)-1) { NameSize = NodeName->Length; NameCompressed = FALSE; } } Index = GET_HASH_INDEX(NameConvKey); CurrentName = CmpNameCacheTable[Index]; while (CurrentName) { Ncb = CONTAINING_RECORD(CurrentName, CM_NAME_CONTROL_BLOCK, NameHash); if ((NameConvKey == CurrentName->ConvKey) && (NameSize == Ncb->NameLength)) { // Hash value matches, compare the names. NameFound = TRUE; if (Ncb->Compressed) { if (CmpCompareCompressedName(NodeName, Ncb->Name, NameSize)) { NameFound = FALSE; } } else { Cp = (WCHAR *) NodeName->Buffer; Cp2 = (WCHAR *) Ncb->Name; for (i=0 ;iNameLength; i+= sizeof(WCHAR)) { if (RtlUpcaseUnicodeChar(*Cp) != RtlUpcaseUnicodeChar(*Cp2)) { NameFound = FALSE; break; } ++Cp; ++Cp2; } } if (NameFound) { // Found it, increase the refcount. if ((USHORT) (Ncb->RefCount + 1) == 0) { // We have maxed out the ref count. // fail the call. Ncb = NULL; } else { ++Ncb->RefCount; } break; } } CurrentName = CurrentName->NextHash; } if (NameFound == FALSE) { // Now need to create one Name block for this string. Size = FIELD_OFFSET(CM_NAME_CONTROL_BLOCK, Name) + NameSize; Ncb = ExAllocatePoolWithTag(PagedPool, Size, CM_NAME_TAG | PROTECTED_POOL); if (Ncb == NULL) { return(NULL); } RtlZeroMemory(Ncb, Size); // Update all the info for this newly created Name block. if (NameCompressed) { Ncb->Compressed = TRUE; for (i=0;iName)[i] = (UCHAR)(NodeName->Buffer[i]); } } else { Ncb->Compressed = FALSE; RtlCopyMemory((PVOID)(Ncb->Name), (PVOID)(NodeName->Buffer), NameSize); } Ncb->ConvKey = NameConvKey; Ncb->RefCount = 1; Ncb->NameLength = NameSize; CurrentName = &(Ncb->NameHash); // Insert into Name Hash table. CurrentName->NextHash = CmpNameCacheTable[Index]; CmpNameCacheTable[Index] = CurrentName; } return(Ncb); } VOID CmpDereferenceNameControlBlockWithLock(PCM_NAME_CONTROL_BLOCK Ncb) { PCM_NAME_HASH *Prev; PCM_NAME_HASH Current; if (--Ncb->RefCount == 0) { // Remove it from the the Hash Table Prev = &(GET_HASH_ENTRY(CmpNameCacheTable, Ncb->ConvKey)); while (TRUE) { Current = *Prev; ASSERT(Current != NULL); if (Current == &(Ncb->NameHash)) { *Prev = Current->NextHash; break; } Prev = &Current->NextHash; } // Free storage ExFreePoolWithTag(Ncb, CM_NAME_TAG | PROTECTED_POOL); } } VOID CmpCleanUpSubKeyInfo(PCM_KEY_CONTROL_BLOCK KeyControlBlock) /*++ Routine Description: Clean up the subkey information cache due to create or delete keys. Registry is locked exclusively and no need to lock the KCB. Arguments: KeyControlBlock - pointer to a key control block. --*/ { ASSERT_CM_LOCK_OWNED_EXCLUSIVE(); if (KeyControlBlock->ExtFlags & (CM_KCB_NO_SUBKEY | CM_KCB_SUBKEY_ONE | CM_KCB_SUBKEY_HINT)) { if (KeyControlBlock->ExtFlags & (CM_KCB_SUBKEY_HINT)) { ExFreePoolWithTag(KeyControlBlock->IndexHint, CM_CACHE_INDEX_TAG | PROTECTED_POOL); } KeyControlBlock->ExtFlags &= ~((CM_KCB_NO_SUBKEY | CM_KCB_SUBKEY_ONE | CM_KCB_SUBKEY_HINT)); } } VOID CmpCleanUpKcbValueCache(PCM_KEY_CONTROL_BLOCK KeyControlBlock) /*++ Routine Description: Clean up cached value/data that are associated to this key. Arguments: KeyControlBlock - pointer to a key control block. --*/ { ULONG i; PULONG_PTR CachedList; PCELL_DATA pcell; ULONG realsize; BOOLEAN small; if (CMP_IS_CELL_CACHED(KeyControlBlock->ValueCache.ValueList)) { CachedList = (PULONG_PTR) CMP_GET_CACHED_CELLDATA(KeyControlBlock->ValueCache.ValueList); for (i = 0; i < KeyControlBlock->ValueCache.Count; i++) { if (CMP_IS_CELL_CACHED(CachedList[i])) { // Trying to catch the BAD guy who writes over our pool. CmpMakeSpecialPoolReadWrite( CMP_GET_CACHED_ADDRESS(CachedList[i]) ); ExFreePool((PVOID) CMP_GET_CACHED_ADDRESS(CachedList[i])); } } // Trying to catch the BAD guy who writes over our pool. CmpMakeSpecialPoolReadWrite( CMP_GET_CACHED_ADDRESS(KeyControlBlock->ValueCache.ValueList) ); ExFreePool((PVOID) CMP_GET_CACHED_ADDRESS(KeyControlBlock->ValueCache.ValueList)); // Mark the ValueList as NULL KeyControlBlock->ValueCache.ValueList = HCELL_NIL; } else if (KeyControlBlock->ExtFlags & CM_KCB_SYM_LINK_FOUND) { // This is a symbolic link key with symbolic name resolved. // Dereference to its real kcb and clear the bit. if ((KeyControlBlock->ValueCache.RealKcb->RefCount == 1) && !(KeyControlBlock->ValueCache.RealKcb->Delete)) { KeyControlBlock->ValueCache.RealKcb->ExtFlags |= CM_KCB_NO_DELAY_CLOSE; } CmpDereferenceKeyControlBlockWithLock(KeyControlBlock->ValueCache.RealKcb); KeyControlBlock->ExtFlags &= ~CM_KCB_SYM_LINK_FOUND; } } VOID CmpCleanUpKcbCacheWithLock(PCM_KEY_CONTROL_BLOCK KeyControlBlock) /*++ Routine Description: Clean up all cached allocations that are associated to this key. If the parent is still open just because of this one, Remove the parent as well. Arguments: KeyControlBlock - pointer to a key control block. --*/ { PCM_KEY_CONTROL_BLOCK Kcb; PCM_KEY_CONTROL_BLOCK ParentKcb; Kcb = KeyControlBlock; ASSERT(KeyControlBlock->RefCount == 0); while (Kcb->RefCount == 0) { // First, free allocations for Value/data. CmpCleanUpKcbValueCache(Kcb); // Free the kcb and dereference parentkcb and nameblock. CmpDereferenceNameControlBlockWithLock(Kcb->NameBlock); if (Kcb->ExtFlags & CM_KCB_SUBKEY_HINT) { // Now free the HintIndex allocation ExFreePoolWithTag(Kcb->IndexHint, CM_CACHE_INDEX_TAG | PROTECTED_POOL); } // Save the ParentKcb before we free the Kcb ParentKcb = Kcb->ParentKcb; // We cannot call CmpDereferenceKeyControlBlockWithLock so we can avoid recurrsion. if (!Kcb->Delete) { CmpRemoveKeyControlBlock(Kcb); } SET_KCB_SIGNATURE(Kcb, '4FmC'); ASSERT_KEYBODY_LIST_EMPTY(Kcb); ExFreePoolWithTag(Kcb, CM_KCB_TAG | PROTECTED_POOL); Kcb = ParentKcb; Kcb->RefCount--; } } PUNICODE_STRING CmpConstructName(PCM_KEY_CONTROL_BLOCK kcb) /*++ Routine Description: Construct the name given a kcb. Arguments: kcb - Kcb for the key Return Value: Pointer to the unicode string constructed. Caller is responsible to free this storage space. --*/ { PUNICODE_STRING FullName; PCM_KEY_CONTROL_BLOCK TmpKcb; USHORT Length; USHORT size; USHORT i; USHORT BeginPosition; WCHAR *w1, *w2; UCHAR *u2; // Calculate the total string length. Length = 0; TmpKcb = kcb; while (TmpKcb) { if (TmpKcb->NameBlock->Compressed) { Length += TmpKcb->NameBlock->NameLength * sizeof(WCHAR); } else { Length += TmpKcb->NameBlock->NameLength; } // Add the sapce for OBJ_NAME_PATH_SEPARATOR; Length += sizeof(WCHAR); TmpKcb = TmpKcb->ParentKcb; } // Allocate the pool for the unicode string size = sizeof(UNICODE_STRING) + Length; FullName = (PUNICODE_STRING) ExAllocatePoolWithTag(PagedPool, size, CM_NAME_TAG | PROTECTED_POOL); if (FullName) { FullName->Buffer = (USHORT *) ((ULONG_PTR) FullName + sizeof(UNICODE_STRING)); FullName->Length = Length; FullName->MaximumLength = Length; // Now fill the name into the buffer. TmpKcb = kcb; BeginPosition = Length; while (TmpKcb) { // Calculate the begin position of each subkey. Then fill in the char. if (TmpKcb->NameBlock->Compressed) { BeginPosition -= (TmpKcb->NameBlock->NameLength + 1) * sizeof(WCHAR); w1 = &(FullName->Buffer[BeginPosition/sizeof(WCHAR)]); *w1 = OBJ_NAME_PATH_SEPARATOR; w1++; u2 = (UCHAR *) &(TmpKcb->NameBlock->Name[0]); for (i=0; iNameBlock->NameLength; i++) { *w1 = (WCHAR)(*u2); w1++; u2++; } } else { BeginPosition -= (TmpKcb->NameBlock->NameLength + sizeof(WCHAR)); w1 = &(FullName->Buffer[BeginPosition/sizeof(WCHAR)]); *w1 = OBJ_NAME_PATH_SEPARATOR; w1++; w2 = TmpKcb->NameBlock->Name; for (i=0; iNameBlock->NameLength; i=i+sizeof(WCHAR)) { *w1 = *w2; w1++; w2++; } } TmpKcb = TmpKcb->ParentKcb; } } return (FullName); } VOID CmpRemoveFromDelayedClose(IN PCM_KEY_CONTROL_BLOCK kcb) { ULONG i; i = kcb->DelayedCloseIndex; ASSERT (CmpDelayedCloseTable[i] == kcb); ASSERT(i != CmpDelayedCloseSize); CmpDelayedCloseTable[i] = (PCM_KEY_CONTROL_BLOCK)CmpDelayedFreeIndex; CmpDelayedFreeIndex = i; kcb->DelayedCloseIndex = 0; } PCM_KEY_CONTROL_BLOCK CmpCreateKeyControlBlock( PHHIVE Hive, HCELL_INDEX Cell, PCM_KEY_NODE Node, PCM_KEY_CONTROL_BLOCK ParentKcb, BOOLEAN FakeKey, PUNICODE_STRING KeyName ) /*++ Routine Description: Allocate and initialize a key control block, insert it into the kcb tree. Full path will be BaseName + '\' + KeyName, unless BaseName NULL, in which case the full path is simply KeyName. RefCount of returned KCB WILL have been incremented to reflect callers ref. Arguments: Hive - Supplies Hive that holds the key we are creating a KCB for. Cell - Supplies Cell that contains the key we are creating a KCB for. Node - Supplies pointer to key node. ParentKcb - Parent kcb of the kcb to be created FakeKey - Whether the kcb to be create is a fake one or not KeyName - the subkey name to of the KCB to be created. NOTE: We need the parameter instead of just using the name in the KEY_NODE because there is no name in the root cell of a hive. Return Value: NULL - failure (insufficient memory) else a pointer to the new kcb. --*/ { PCM_KEY_CONTROL_BLOCK kcb; PCM_KEY_CONTROL_BLOCK kcbmatch=NULL; PCMHIVE CmHive; ULONG namelength; PUNICODE_STRING fullname; ULONG Size; ULONG i; UNICODE_STRING NodeName; ULONG ConvKey=0; ULONG Cnt; WCHAR *Cp; // ParentKCb has the base hash value. if (ParentKcb) { ConvKey = ParentKcb->ConvKey; } NodeName = *KeyName; while ((NodeName.Length > 0) && (NodeName.Buffer[0] == OBJ_NAME_PATH_SEPARATOR)) { // This must be the \REGISTRY. // Strip off the leading OBJ_NAME_PATH_SEPARATOR NodeName.Buffer++; NodeName.Length -= sizeof(WCHAR); } // Manually compute the hash to use. ASSERT(NodeName.Length > 0); if (NodeName.Length) { Cp = NodeName.Buffer; for (Cnt=0; CntDelete = FALSE; kcb->RefCount = 1; kcb->KeyHive = Hive; kcb->KeyCell = Cell; kcb->KeyNode = Node; kcb->ConvKey = ConvKey; } ASSERT_KCB(kcb); // Find location to insert kcb in kcb tree. LOCK_KCB_TREE(); // Add the KCB to the hash table kcbmatch = CmpInsertKeyHash(&kcb->KeyHash, FakeKey); if (kcbmatch != NULL) { // A match was found. ASSERT(!kcbmatch->Delete); SET_KCB_SIGNATURE(kcb, '1FmC'); ASSERT_KEYBODY_LIST_EMPTY(kcb); ExFreePoolWithTag(kcb, CM_KCB_TAG | PROTECTED_POOL); ASSERT_KCB(kcbmatch); kcb = kcbmatch; if (kcb->RefCount == 0) { // This kcb is on the delayed close list. Remove it from that list. CmpRemoveFromDelayedClose(kcb); } if ((USHORT)(kcb->RefCount + 1) == 0) { // We have maxed out the ref count on this key. Probably // some bogus app has opened the same key 64K times without // ever closing it. Just fail the open, they've got enough // handles already. ASSERT(kcb->RefCount + 1 != 0); kcb = NULL; } else { ++kcb->RefCount; } } else { // No kcb created previously, fill in all the data. // Now try to reference the parentkcb if (ParentKcb) { if (CmpReferenceKeyControlBlock(ParentKcb)) { kcb->ParentKcb = ParentKcb; kcb->TotalLevels = ParentKcb->TotalLevels + 1; } else { // We have maxed out the ref count on the parent. // Since it has been cached in the cachetable, remove it first before we free the allocation. CmpRemoveKeyControlBlock(kcb); SET_KCB_SIGNATURE(kcb, '2FmC'); ASSERT_KEYBODY_LIST_EMPTY(kcb); ExFreePoolWithTag(kcb, CM_KCB_TAG | PROTECTED_POOL); kcb = NULL; } } else { // It is the \REGISTRY node. kcb->ParentKcb = NULL; kcb->TotalLevels = 1; } if (kcb) { // Now try to find the Name Control block that has the name for this node. kcb->NameBlock = CmpGetNameControlBlock (&NodeName); if (kcb->NameBlock) { // Now fill in all the data needed for the cache. kcb->ValueCache.Count = Node->ValueList.Count; kcb->ValueCache.ValueList = (ULONG_PTR) Node->ValueList.List; kcb->Flags = Node->Flags; kcb->ExtFlags = 0; kcb->DelayedCloseIndex = 0; // Cache the security cells in the kcb kcb->Security = Node->Security; if (FakeKey) { // The KCb to be created is a fake one kcb->ExtFlags |= CM_KCB_KEY_NON_EXIST; } } else { // We have maxed out the ref count on the Name. // First dereference the parent KCB. CmpDereferenceKeyControlBlockWithLock(ParentKcb); CmpRemoveKeyControlBlock(kcb); SET_KCB_SIGNATURE(kcb, '3FmC'); ASSERT_KEYBODY_LIST_EMPTY(kcb); ExFreePoolWithTag(kcb, CM_KCB_TAG | PROTECTED_POOL); kcb = NULL; } } } UNLOCK_KCB_TREE(); return kcb; } VOID CmpSearchKeyControlBlockTree( PKCB_WORKER_ROUTINE WorkerRoutine, PVOID Context1, PVOID Context2 ) /*++ Routine Description: Traverse the kcb tree. We will visit all nodes unless WorkerRoutine tells us to stop part way through. For each node, call WorkerRoutine(..., Context1, Contex2). If it returns KCB_WORKER_DONE, we are done, simply return. If it returns KCB_WORKER_CONTINUE, just continue the search. If it returns KCB_WORKER_DELETE, the specified KCB is marked as deleted. This routine has the side-effect of removing all delayed-close KCBs. Arguments: WorkerRoutine - applied to nodes witch Match. Context1 - data we pass through Context2 - data we pass through --*/ { PCM_KEY_CONTROL_BLOCK Current; PCM_KEY_CONTROL_BLOCK Next; PCM_KEY_HASH *Prev; ULONG WorkerResult; ULONG i; // Walk the hash table for (i=0; iDelete); if (Current->RefCount == 0) { // This kcb is in DelayClose case, remove it. CmpRemoveFromDelayedClose(Current); CmpCleanUpKcbCacheWithLock(Current); // The HashTable is changed, start over in this index again. Prev = &CmpCacheTable[i]; continue; } WorkerResult = (WorkerRoutine)(Current, Context1, Context2); if (WorkerResult == KCB_WORKER_DONE) { return; } else if (WorkerResult == KCB_WORKER_DELETE) { ASSERT(Current->Delete); *Prev = Current->NextHash; continue; } else { ASSERT(WorkerResult == KCB_WORKER_CONTINUE); Prev = &Current->NextHash; } } } } VOID CmpDereferenceKeyControlBlock(PCM_KEY_CONTROL_BLOCK KeyControlBlock) /*++ Routine Description: Decrements the reference count on a key control block, and frees it if it becomes zero. It is expected that no notify control blocks remain if the reference count becomes zero. Arguments: KeyControlBlock - pointer to a key control block. --*/ { LOCK_KCB_TREE(); CmpDereferenceKeyControlBlockWithLock(KeyControlBlock) ; UNLOCK_KCB_TREE(); } VOID CmpDereferenceKeyControlBlockWithLock(PCM_KEY_CONTROL_BLOCK KeyControlBlock) { ULONG_PTR FreeIndex; PCM_KEY_CONTROL_BLOCK KcbToFree; ASSERT_KCB(KeyControlBlock); if (--KeyControlBlock->RefCount == 0) { // Remove kcb from the tree if (KeyControlBlock->ExtFlags & CM_KCB_NO_DELAY_CLOSE) { // Free storage directly so we can clean up junk quickly. // Need to free all cached Index List, Index Leaf, Value, etc. CmpCleanUpKcbCacheWithLock(KeyControlBlock); } else if (!KeyControlBlock->Delete) { // Put this kcb on our delayed close list. // First check the free list for a free slot. FreeIndex = CmpDelayedFreeIndex; if (FreeIndex != -1) { ASSERT(FreeIndex < CmpDelayedCloseSize); CmpDelayedFreeIndex = (ULONG_PTR)CmpDelayedCloseTable[FreeIndex]; KeyControlBlock->DelayedCloseIndex = (USHORT) FreeIndex; ASSERT((CmpDelayedFreeIndex == -1) || (CmpDelayedFreeIndex < CmpDelayedCloseSize)); CmpDelayedCloseTable[FreeIndex] = KeyControlBlock; } else { // Nothing is free, we have to get rid of something. ASSERT(*CmpDelayedCloseCurrent != NULL); ASSERT(!(*CmpDelayedCloseCurrent)->Delete); // Need to free all cached Index List, Index Leaf, Value, etc. // Change the code sequence for the recurrsive call to dereference the parent. KcbToFree = *CmpDelayedCloseCurrent; *CmpDelayedCloseCurrent = KeyControlBlock; KeyControlBlock->DelayedCloseIndex = (USHORT)(CmpDelayedCloseCurrent - CmpDelayedCloseTable); ++CmpDelayedCloseCurrent; if ((ULONG)(CmpDelayedCloseCurrent - CmpDelayedCloseTable) == CmpDelayedCloseSize) { CmpDelayedCloseCurrent = CmpDelayedCloseTable; } CmpCleanUpKcbCacheWithLock(KcbToFree); } } else { // Free storage directly as there is no point in putting this on our delayed close list. // Need to free all cached Index List, Index Leaf, Value, etc. CmpCleanUpKcbCacheWithLock(KeyControlBlock); } } } VOID CmpRemoveKeyControlBlock(PCM_KEY_CONTROL_BLOCK KeyControlBlock) /*++ Routine Description: Remove a key control block from the KCB tree. It is expected that no notify control blocks remain. The kcb will NOT be freed, call DereferenceKeyControlBlock for that. This call assumes the KCB tree is already locked or registry is locked exclusively. Arguments: KeyControlBlock - pointer to a key control block. --*/ { ASSERT_KCB(KeyControlBlock); // Remove the KCB from the hash table CmpRemoveKeyHash(&KeyControlBlock->KeyHash); } VOID CmpFreeKeyBody(PHHIVE Hive,HCELL_INDEX Cell) /*++ Routine Description: Free storage for the key entry Hive.Cell refers to, including its class and security data. Will NOT free child list or value list. Arguments: Hive - supplies a pointer to the hive control structure for the hive Cell - supplies index of key to free Return Value: NTSTATUS - Result code from call, among the following: --*/ { PCELL_DATA key; // map in the cell key = HvGetCell(Hive, Cell); if (!(key->u.KeyNode.Flags & KEY_HIVE_EXIT)) { if (key->u.KeyNode.Security != HCELL_NIL) { HvFreeCell(Hive, key->u.KeyNode.Security); } if (key->u.KeyNode.ClassLength > 0) { HvFreeCell(Hive, key->u.KeyNode.Class); } } // unmap the cell itself and free it HvFreeCell(Hive, Cell); } PCM_KEY_CONTROL_BLOCK CmpInsertKeyHash(IN PCM_KEY_HASH KeyHash, IN BOOLEAN FakeKey) /*++ Routine Description: Adds a key hash structure to the hash table. The hash table will be checked to see if a duplicate entry already exists. If a duplicate is found, its kcb will be returned. If a duplicate is not found, NULL will be returned. Arguments: KeyHash - Supplies the key hash structure to be added. Return Value: NULL - if the supplied key has was added PCM_KEY_HASH - The duplicate hash entry, if one was found --*/ { HASH_VALUE Hash; ULONG Index; PCM_KEY_HASH Current; ASSERT_KEY_HASH(KeyHash); Index = GET_HASH_INDEX(KeyHash->ConvKey); // If this is a fake key, we will use the cell and hive from its parent for uniqeness. // To deal with the case when the fake has the same ConvKey as its parent (in which case we cannot distingish between the two), we set the lowest bit of the fake key's cell. // It's possible (unlikely) that we cannot distingish two fake keys (when their Convkey's are the same) under the same key. // It is not breaking anything, we just cannot find the other one in cache lookup. if (FakeKey) { KeyHash->KeyCell++; } // First look for duplicates. Current = CmpCacheTable[Index]; while (Current) { ASSERT_KEY_HASH(Current); // We must check ConvKey since we can create a fake kcb for keys that does not exist. // We will use the Hive and Cell from the parent. if ((KeyHash->ConvKey == Current->ConvKey) && (KeyHash->KeyCell == Current->KeyCell) && (KeyHash->KeyHive == Current->KeyHive)) { // Found a match return(CONTAINING_RECORD(Current, CM_KEY_CONTROL_BLOCK, KeyHash)); } Current = Current->NextHash; } #if DBG // Make sure this key is not somehow cached in the wrong spot. { ULONG DbgIndex; PCM_KEY_CONTROL_BLOCK kcb; for (DbgIndex = 0; DbgIndex < CmpHashTableSize; DbgIndex++) { Current = CmpCacheTable[DbgIndex]; while (Current) { kcb = CONTAINING_RECORD(Current, CM_KEY_CONTROL_BLOCK, KeyHash); ASSERT_KEY_HASH(Current); ASSERT((KeyHash->KeyHive != Current->KeyHive) || FakeKey || (kcb->ExtFlags & CM_KCB_KEY_NON_EXIST) || (KeyHash->KeyCell != Current->KeyCell)); Current = Current->NextHash; } } } #endif // No duplicate was found, add this entry at the head of the list KeyHash->NextHash = CmpCacheTable[Index]; CmpCacheTable[Index] = KeyHash; return(NULL); } VOID CmpRemoveKeyHash(IN PCM_KEY_HASH KeyHash) /*++ Routine Description: Removes a key hash structure from the hash table. Arguments: KeyHash - Supplies the key hash structure to be deleted. --*/ { HASH_VALUE Hash; ULONG Index; PCM_KEY_HASH *Prev; PCM_KEY_HASH Current; ASSERT_KEY_HASH(KeyHash); Hash = HASH_KEY(KeyHash->ConvKey); Index = Hash % CmpHashTableSize; // Find this entry. Prev = &CmpCacheTable[Index]; while (TRUE) { Current = *Prev; ASSERT(Current != NULL); ASSERT_KEY_HASH(Current); if (Current == KeyHash) { *Prev = Current->NextHash; #if DBG if (*Prev) { ASSERT_KEY_HASH(*Prev); } #endif break; } Prev = &Current->NextHash; } } VOID CmpInitializeCache() { ULONG TotalCmCacheSize; ULONG i; TotalCmCacheSize = CmpHashTableSize * sizeof(PCM_KEY_HASH); CmpCacheTable = ExAllocatePoolWithTag(PagedPool, TotalCmCacheSize, 'aCMC'); if (CmpCacheTable == NULL) { KeBugCheckEx(CONFIG_INITIALIZATION_FAILED,6,1,0,0); return; } RtlZeroMemory(CmpCacheTable, TotalCmCacheSize); TotalCmCacheSize = CmpHashTableSize * sizeof(PCM_NAME_HASH); CmpNameCacheTable = ExAllocatePoolWithTag(PagedPool, TotalCmCacheSize, 'aCMC'); if (CmpNameCacheTable == NULL) { KeBugCheckEx(CONFIG_INITIALIZATION_FAILED,6,1,0,0); return; } RtlZeroMemory(CmpNameCacheTable, TotalCmCacheSize); CmpDelayedCloseTable = ExAllocatePoolWithTag(PagedPool, CmpDelayedCloseSize * sizeof(PCM_KEY_CONTROL_BLOCK), 'cDMC'); if (CmpDelayedCloseTable == NULL) { KeBugCheckEx(CONFIG_INITIALIZATION_FAILED,6,2,0,0); return; } CmpDelayedFreeIndex = 0; for (i=0; i