2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: ntoskrnl/config/cmkcbncb.c
5 * PURPOSE: Routines for handling KCBs, NCBs, as well as key hashes.
6 * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
9 /* INCLUDES ******************************************************************/
15 /* GLOBALS *******************************************************************/
17 ULONG CmpHashTableSize
= 2048;
18 PCM_KEY_HASH_TABLE_ENTRY CmpCacheTable
;
19 PCM_NAME_HASH_TABLE_ENTRY CmpNameCacheTable
;
21 BOOLEAN CmpHoldLazyFlush
;
23 /* FUNCTIONS *****************************************************************/
27 CmpInitializeCache(VOID
)
31 /* Calculate length for the table */
32 Length
= CmpHashTableSize
* sizeof(CM_KEY_HASH_TABLE_ENTRY
);
35 CmpCacheTable
= ExAllocatePoolWithTag(PagedPool
, Length
, TAG_CM
);
38 /* Take the system down */
39 KeBugCheckEx(CONFIG_INITIALIZATION_FAILED
, 3, 1, 0, 0);
42 /* Zero out the table */
43 RtlZeroMemory(CmpCacheTable
, Length
);
45 /* Initialize the locks */
46 for (i
= 0;i
< CmpHashTableSize
; i
++)
48 /* Setup the pushlock */
49 ExInitializePushLock((PULONG_PTR
)&CmpCacheTable
[i
].Lock
);
52 /* Calculate length for the name cache */
53 Length
= CmpHashTableSize
* sizeof(CM_NAME_HASH_TABLE_ENTRY
);
55 /* Now allocate the name cache table */
56 CmpNameCacheTable
= ExAllocatePoolWithTag(PagedPool
, Length
, TAG_CM
);
57 if (!CmpNameCacheTable
)
59 /* Take the system down */
60 KeBugCheckEx(CONFIG_INITIALIZATION_FAILED
, 3, 3, 0, 0);
63 /* Zero out the table */
64 RtlZeroMemory(CmpNameCacheTable
, Length
);
66 /* Initialize the locks */
67 for (i
= 0;i
< CmpHashTableSize
; i
++)
69 /* Setup the pushlock */
70 ExInitializePushLock((PULONG_PTR
)&CmpNameCacheTable
[i
].Lock
);
73 /* Setup the delayed close table */
74 CmpInitializeDelayedCloseTable();
79 CmpRemoveKeyHash(IN PCM_KEY_HASH KeyHash
)
84 /* Lookup all the keys in this index entry */
85 Prev
= &GET_HASH_ENTRY(CmpCacheTable
, KeyHash
->ConvKey
).Entry
;
88 /* Save the current one and make sure it's valid */
90 ASSERT(Current
!= NULL
);
92 /* Check if it matches */
93 if (Current
== KeyHash
)
95 /* Then write the previous one */
96 *Prev
= Current
->NextHash
;
100 /* Otherwise, keep going */
101 Prev
= &Current
->NextHash
;
105 PCM_KEY_CONTROL_BLOCK
107 CmpInsertKeyHash(IN PCM_KEY_HASH KeyHash
,
113 /* Get the hash index */
114 i
= GET_HASH_INDEX(KeyHash
->ConvKey
);
116 /* If this is a fake key, increase the key cell to use the parent data */
117 if (IsFake
) KeyHash
->KeyCell
++;
119 /* Loop the hash table */
120 Entry
= CmpCacheTable
[i
].Entry
;
123 /* Check if this matches */
124 if ((KeyHash
->ConvKey
== Entry
->ConvKey
) &&
125 (KeyHash
->KeyCell
== Entry
->KeyCell
) &&
126 (KeyHash
->KeyHive
== Entry
->KeyHive
))
129 return CONTAINING_RECORD(Entry
, CM_KEY_CONTROL_BLOCK
, KeyHash
);
133 Entry
= Entry
->NextHash
;
136 /* No entry found, add this one and return NULL since none existed */
137 KeyHash
->NextHash
= CmpCacheTable
[i
].Entry
;
138 CmpCacheTable
[i
].Entry
= KeyHash
;
142 PCM_NAME_CONTROL_BLOCK
144 CmpGetNameControlBlock(IN PUNICODE_STRING NodeName
)
146 PCM_NAME_CONTROL_BLOCK Ncb
= NULL
;
150 BOOLEAN IsCompressed
= TRUE
, Found
= FALSE
;
151 PCM_NAME_HASH HashEntry
;
152 ULONG Length
, NcbSize
;
155 p
= NodeName
->Buffer
;
156 for (i
= 0; i
< NodeName
->Length
; i
+= sizeof(WCHAR
))
158 /* Make sure it's not a slash */
159 if (*p
!= OBJ_NAME_PATH_SEPARATOR
)
161 /* Add it to the hash */
162 ConvKey
= 37 * ConvKey
+ RtlUpcaseUnicodeChar(*p
);
169 /* Set assumed lengh and loop to check */
170 Length
= NodeName
->Length
/ sizeof(WCHAR
);
171 for (i
= 0; i
< (NodeName
->Length
/ sizeof(WCHAR
)); i
++)
173 /* Check if this is a valid character */
174 if (*NodeName
->Buffer
> (UCHAR
)-1)
176 /* This is the actual size, and we know we're not compressed */
177 Length
= NodeName
->Length
;
178 IsCompressed
= FALSE
;
182 /* Lock the NCB entry */
183 CmpAcquireNcbLockExclusiveByKey(ConvKey
);
185 /* Get the hash entry */
186 HashEntry
= GET_HASH_ENTRY(CmpNameCacheTable
, ConvKey
).Entry
;
189 /* Get the current NCB */
190 Ncb
= CONTAINING_RECORD(HashEntry
, CM_NAME_CONTROL_BLOCK
, NameHash
);
192 /* Check if the hash matches */
193 if ((ConvKey
= HashEntry
->ConvKey
) && (Length
= Ncb
->NameLength
))
198 /* If the NCB is compressed, do a compressed name compare */
202 if (CmpCompareCompressedName(NodeName
, Ncb
->Name
, Length
))
210 /* Do a manual compare */
211 p
= NodeName
->Buffer
;
213 for (i
= 0; i
< Ncb
->NameLength
; i
+= sizeof(WCHAR
))
215 /* Compare the character */
216 if (RtlUpcaseUnicodeChar(*p
) != RtlUpcaseUnicodeChar(*pp
))
229 /* Check if we found a name */
237 /* Go to the next hash */
238 HashEntry
= HashEntry
->NextHash
;
241 /* Check if we didn't find it */
245 NcbSize
= FIELD_OFFSET(CM_NAME_CONTROL_BLOCK
, Name
) + Length
;
246 Ncb
= ExAllocatePoolWithTag(PagedPool
, NcbSize
, TAG_CM
);
249 /* Release the lock and fail */
250 CmpReleaseNcbLockByKey(ConvKey
);
255 RtlZeroMemory(Ncb
, NcbSize
);
257 /* Check if the name was compressed */
260 /* Copy the compressed name */
261 Ncb
->Compressed
= TRUE
;
262 for (i
= 0; i
< Length
; i
++)
264 /* Copy Unicode to ANSI */
265 ((PCHAR
)Ncb
->Name
)[i
] = (CHAR
)RtlUpcaseUnicodeChar(NodeName
->Buffer
[i
]);
270 /* Copy the name directly */
271 Ncb
->Compressed
= FALSE
;
272 for (i
= 0; i
< Length
; i
++)
274 /* Copy each unicode character */
275 Ncb
->Name
[i
] = RtlUpcaseUnicodeChar(NodeName
->Buffer
[i
]);
279 /* Setup the rest of the NCB */
280 Ncb
->ConvKey
= ConvKey
;
282 Ncb
->NameLength
= Length
;
284 /* Insert the name in the hash table */
285 HashEntry
= &Ncb
->NameHash
;
286 HashEntry
->NextHash
= GET_HASH_ENTRY(CmpNameCacheTable
, ConvKey
).Entry
;
287 GET_HASH_ENTRY(CmpNameCacheTable
, ConvKey
).Entry
= HashEntry
;
290 /* Release NCB lock */
291 CmpReleaseNcbLockByKey(ConvKey
);
293 /* Return the NCB found */
299 CmpRemoveKeyControlBlock(IN PCM_KEY_CONTROL_BLOCK Kcb
)
301 /* Make sure that the registry and KCB are utterly locked */
302 ASSERT((CmpIsKcbLockedExclusive(Kcb
) == TRUE
) ||
303 (CmpTestRegistryLockExclusive() == TRUE
));
305 /* Remove the key hash */
306 CmpRemoveKeyHash(&Kcb
->KeyHash
);
311 CmpDereferenceNameControlBlockWithLock(IN PCM_NAME_CONTROL_BLOCK Ncb
)
313 PCM_NAME_HASH Current
, *Next
;
314 ULONG ConvKey
= Ncb
->ConvKey
;
317 CmpAcquireNcbLockExclusiveByKey(ConvKey
);
319 /* Decrease the reference count */
320 if (!(--Ncb
->RefCount
))
322 /* Find the NCB in the table */
323 Next
= &GET_HASH_ENTRY(CmpNameCacheTable
, Ncb
->ConvKey
).Entry
;
326 /* Check the current entry */
328 ASSERT(Current
!= NULL
);
329 if (Current
== &Ncb
->NameHash
)
332 *Next
= Current
->NextHash
;
336 /* Get to the next one */
337 Next
= &Current
->NextHash
;
340 /* Found it, now free it */
344 /* Release the lock */
345 CmpReleaseNcbLockByKey(ConvKey
);
350 CmpReferenceKeyControlBlock(IN PCM_KEY_CONTROL_BLOCK Kcb
)
352 /* Check if this is the KCB's first reference */
353 if (Kcb
->RefCount
== 0)
355 /* Check if the KCB is locked in shared mode */
356 if (!CmpIsKcbLockedExclusive(Kcb
))
358 /* Convert it to exclusive */
359 if (!CmpTryToConvertKcbSharedToExclusive(Kcb
))
361 /* Set the delayed close index so that we can be ignored */
362 Kcb
->DelayedCloseIndex
= 1;
364 /* Increase the reference count while we release the lock */
365 InterlockedIncrement((PLONG
)&Kcb
->RefCount
);
367 /* Go from shared to exclusive */
368 CmpConvertKcbSharedToExclusive(Kcb
);
370 /* Decrement the reference count; the lock is now held again */
371 InterlockedDecrement((PLONG
)&Kcb
->RefCount
);
373 /* Check if we still control the index */
374 if (Kcb
->DelayedCloseIndex
== 1)
377 Kcb
->DelayedCloseIndex
= 0;
382 ASSERT((Kcb
->DelayedCloseIndex
== CmpDelayedCloseSize
) ||
383 (Kcb
->DelayedCloseIndex
== 0));
389 /* Increase the reference count */
390 if ((InterlockedIncrement((PLONG
)&Kcb
->RefCount
) & 0xFFFF) == 0)
392 /* We've overflown to 64K references, bail out */
393 InterlockedDecrement((PLONG
)&Kcb
->RefCount
);
397 /* Check if this was the last close index */
398 if (!Kcb
->DelayedCloseIndex
)
400 /* Check if the KCB is locked in shared mode */
401 if (!CmpIsKcbLockedExclusive(Kcb
))
403 /* Convert it to exclusive */
404 if (!CmpTryToConvertKcbSharedToExclusive(Kcb
))
406 /* Go from shared to exclusive */
407 CmpConvertKcbSharedToExclusive(Kcb
);
411 /* If we're still the last entry, remove us */
412 if (!Kcb
->DelayedCloseIndex
) CmpRemoveFromDelayedClose(Kcb
);
421 CmpCleanUpKcbValueCache(IN PCM_KEY_CONTROL_BLOCK Kcb
)
423 PULONG_PTR CachedList
;
427 ASSERT((CmpIsKcbLockedExclusive(Kcb
) == TRUE
) ||
428 (CmpTestRegistryLockExclusive() == TRUE
));
430 /* Check if the value list is cached */
431 if (CMP_IS_CELL_CACHED(Kcb
->ValueCache
.ValueList
))
433 /* Get the cache list */
434 CachedList
= (PULONG_PTR
)CMP_GET_CACHED_DATA(Kcb
->ValueCache
.ValueList
);
435 for (i
= 0; i
< Kcb
->ValueCache
.Count
; i
++)
437 /* Check if this cell is cached */
438 if (CMP_IS_CELL_CACHED(CachedList
[i
]))
441 ExFreePool((PVOID
)CMP_GET_CACHED_CELL(CachedList
[i
]));
445 /* Now free the list */
446 ExFreePool((PVOID
)CMP_GET_CACHED_CELL(Kcb
->ValueCache
.ValueList
));
447 Kcb
->ValueCache
.ValueList
= HCELL_NIL
;
449 else if (Kcb
->ExtFlags
& CM_KCB_SYM_LINK_FOUND
)
451 /* This is a sym link, check if there's only one reference left */
452 if ((((PCM_KEY_CONTROL_BLOCK
)Kcb
->ValueCache
.RealKcb
)->RefCount
== 1) &&
453 !(((PCM_KEY_CONTROL_BLOCK
)Kcb
->ValueCache
.RealKcb
)->Delete
))
455 /* Disable delay close for the KCB */
456 ((PCM_KEY_CONTROL_BLOCK
)Kcb
->ValueCache
.RealKcb
)->ExtFlags
|= CM_KCB_NO_DELAY_CLOSE
;
459 /* Dereference the KCB */
460 CmpDelayDerefKeyControlBlock((PCM_KEY_CONTROL_BLOCK
)Kcb
->ValueCache
.RealKcb
);
461 Kcb
->ExtFlags
&= ~CM_KCB_SYM_LINK_FOUND
;
467 CmpCleanUpKcbCacheWithLock(IN PCM_KEY_CONTROL_BLOCK Kcb
,
468 IN BOOLEAN LockHeldExclusively
)
470 PCM_KEY_CONTROL_BLOCK Parent
;
474 ASSERT((CmpIsKcbLockedExclusive(Kcb
) == TRUE
) ||
475 (CmpTestRegistryLockExclusive() == TRUE
));
476 ASSERT(Kcb
->RefCount
== 0);
478 /* Cleanup the value cache */
479 CmpCleanUpKcbValueCache(Kcb
);
481 /* Reference the NCB */
482 CmpDereferenceNameControlBlockWithLock(Kcb
->NameBlock
);
484 /* Check if we have an index hint block and free it */
485 if (Kcb
->ExtFlags
& CM_KCB_SUBKEY_HINT
) ExFreePool(Kcb
->IndexHint
);
487 /* Check if we were already deleted */
488 Parent
= Kcb
->ParentKcb
;
489 if (!Kcb
->Delete
) CmpRemoveKeyControlBlock(Kcb
);
491 /* Free the KCB as well */
492 CmpFreeKeyControlBlock(Kcb
);
494 /* Check if we have a parent */
497 /* Dereference the parent */
498 LockHeldExclusively
?
499 CmpDereferenceKeyControlBlockWithLock(Kcb
,LockHeldExclusively
) :
500 CmpDelayDerefKeyControlBlock(Kcb
);
506 CmpDereferenceKeyControlBlock(IN PCM_KEY_CONTROL_BLOCK Kcb
)
508 LONG OldRefCount
, NewRefCount
;
511 /* Get the ref count and update it */
512 OldRefCount
= *(PLONG
)&Kcb
->RefCount
;
513 NewRefCount
= OldRefCount
- 1;
515 /* Check if we still have references */
516 if( (NewRefCount
& 0xFFFF) > 0)
518 /* Do the dereference */
519 if (InterlockedCompareExchange((PLONG
)&Kcb
->RefCount
,
521 OldRefCount
) == OldRefCount
)
529 ConvKey
= Kcb
->ConvKey
;
531 /* Do the dereference inside the lock */
532 CmpAcquireKcbLockExclusive(Kcb
);
533 CmpDereferenceKeyControlBlockWithLock(Kcb
, FALSE
);
534 CmpReleaseKcbLockByKey(ConvKey
);
539 CmpDereferenceKeyControlBlockWithLock(IN PCM_KEY_CONTROL_BLOCK Kcb
,
540 IN BOOLEAN LockHeldExclusively
)
543 ASSERT((CmpIsKcbLockedExclusive(Kcb
) == TRUE
) ||
544 (CmpTestRegistryLockExclusive() == TRUE
));
546 /* Check if this is the last reference */
547 if ((InterlockedDecrement((PLONG
)&Kcb
->RefCount
) & 0xFFFF) == 0)
549 /* Check if we should do a direct delete */
550 if (((CmpHoldLazyFlush
) &&
551 !(Kcb
->ExtFlags
& CM_KCB_SYM_LINK_FOUND
) &&
552 !(Kcb
->Flags
& KEY_SYM_LINK
)) ||
553 (Kcb
->ExtFlags
& CM_KCB_NO_DELAY_CLOSE
) ||
556 /* Clean up the KCB*/
557 CmpCleanUpKcbCacheWithLock(Kcb
, LockHeldExclusively
);
561 /* Otherwise, use delayed close */
562 CmpAddToDelayedClose(Kcb
, LockHeldExclusively
);
569 InitializeKCBKeyBodyList(IN PCM_KEY_CONTROL_BLOCK Kcb
)
571 /* Initialize the list */
572 InitializeListHead(&Kcb
->KeyBodyListHead
);
574 /* Clear the bodies */
575 Kcb
->KeyBodyArray
[0] =
576 Kcb
->KeyBodyArray
[1] =
577 Kcb
->KeyBodyArray
[2] =
578 Kcb
->KeyBodyArray
[3] = NULL
;
581 PCM_KEY_CONTROL_BLOCK
583 CmpCreateKeyControlBlock(IN PHHIVE Hive
,
584 IN HCELL_INDEX Index
,
585 IN PCM_KEY_NODE Node
,
586 IN PCM_KEY_CONTROL_BLOCK Parent
,
588 IN PUNICODE_STRING KeyName
)
590 PCM_KEY_CONTROL_BLOCK Kcb
, FoundKcb
= NULL
;
591 UNICODE_STRING NodeName
;
592 ULONG ConvKey
= 0, i
;
593 BOOLEAN IsFake
, HashLock
;
596 /* Make sure we own this hive in case it's being unloaded */
597 if ((Hive
->HiveFlags
& HIVE_IS_UNLOADING
) &&
598 (((PCMHIVE
)Hive
)->CreatorOwner
!= KeGetCurrentThread()))
604 /* Check if this is a fake KCB */
605 IsFake
= Flags
& CMP_CREATE_FAKE_KCB
? TRUE
: FALSE
;
607 /* If we have a parent, use its ConvKey */
608 if (Parent
) ConvKey
= Parent
->ConvKey
;
610 /* Make a copy of the name */
613 /* Remove leading slash */
614 while ((NodeName
.Length
) && (*NodeName
.Buffer
== OBJ_NAME_PATH_SEPARATOR
))
616 /* Move the buffer by one */
618 NodeName
.Length
-= sizeof(WCHAR
);
621 /* Make sure we didn't get just a slash or something */
622 ASSERT(NodeName
.Length
> 0);
624 /* Now setup the hash */
626 for (i
= 0; i
< NodeName
.Length
; i
+= sizeof(WCHAR
))
628 /* Make sure it's a valid character */
629 if (*p
!= OBJ_NAME_PATH_SEPARATOR
)
631 /* Add this key to the hash */
632 ConvKey
= 37 * ConvKey
+ RtlUpcaseUnicodeChar(*p
);
639 /* Allocate the KCB */
640 Kcb
= CmpAllocateKeyControlBlock();
641 if (!Kcb
) return NULL
;
643 /* Initailize the key list */
644 InitializeKCBKeyBodyList(Kcb
);
650 Kcb
->KeyCell
= Index
;
651 Kcb
->ConvKey
= ConvKey
;
652 Kcb
->DelayedCloseIndex
= CmpDelayedCloseSize
;
653 Kcb
->InDelayClose
= 0;
655 /* Check if we have two hash entires */
656 HashLock
= Flags
& CMP_LOCK_HASHES_FOR_KCB
? TRUE
: FALSE
;
659 /* It's not locked, do we have a parent? */
662 /* Lock the parent KCB and ourselves */
663 CmpAcquireTwoKcbLocksExclusiveByKey(ConvKey
, Parent
->ConvKey
);
667 /* Lock only ourselves */
668 CmpAcquireKcbLockExclusive(Kcb
);
672 /* Check if we already have a KCB */
673 FoundKcb
= CmpInsertKeyHash(&Kcb
->KeyHash
, IsFake
);
677 ASSERT(!FoundKcb
->Delete
);
679 /* Free the one we allocated and reference this one */
680 CmpFreeKeyControlBlock(Kcb
);
682 if (!CmpReferenceKeyControlBlock(Kcb
))
684 /* We got too many handles */
685 ASSERT(Kcb
->RefCount
+ 1 != 0);
690 /* Check if we're not creating a fake one, but it used to be fake */
691 if ((Kcb
->ExtFlags
& CM_KCB_KEY_NON_EXIST
) && !(IsFake
))
693 /* Set the hive and cell */
695 Kcb
->KeyCell
= Index
;
697 /* This means that our current information is invalid */
698 Kcb
->ExtFlags
= CM_KCB_INVALID_CACHED_INFO
;
701 /* Check if we didn't have any valid data */
702 if (!(Kcb
->ExtFlags
& (CM_KCB_NO_SUBKEY
|
704 CM_KCB_SUBKEY_HINT
)))
706 /* Calculate the index hint */
707 Kcb
->SubKeyCount
= Node
->SubKeyCounts
[Stable
] +
708 Node
->SubKeyCounts
[Volatile
];
710 /* Cached information is now valid */
711 Kcb
->ExtFlags
&= ~CM_KCB_INVALID_CACHED_INFO
;
714 /* Setup the other data */
715 Kcb
->KcbLastWriteTime
= Node
->LastWriteTime
;
716 Kcb
->KcbMaxNameLen
= (USHORT
)Node
->MaxNameLen
;
717 Kcb
->KcbMaxValueNameLen
= (USHORT
)Node
->MaxValueNameLen
;
718 Kcb
->KcbMaxValueDataLen
= Node
->MaxValueDataLen
;
723 /* No KCB, do we have a parent? */
726 /* Reference the parent */
727 if (((Parent
->TotalLevels
+ 1) < 512) &&
728 (CmpReferenceKeyControlBlock(Parent
)))
731 Kcb
->ParentKcb
= Parent
;
732 Kcb
->TotalLevels
= Parent
->TotalLevels
+ 1;
736 /* Remove the KCB and free it */
737 CmpRemoveKeyControlBlock(Kcb
);
738 CmpFreeKeyControlBlock(Kcb
);
744 /* No parent, this is the root node */
745 Kcb
->ParentKcb
= NULL
;
746 Kcb
->TotalLevels
= 1;
749 /* Check if we have a KCB */
753 Kcb
->NameBlock
= CmpGetNameControlBlock(&NodeName
);
757 Kcb
->ValueCache
.Count
= Node
->ValueList
.Count
;
758 Kcb
->ValueCache
.ValueList
= Node
->ValueList
.List
;
759 Kcb
->Flags
= Node
->Flags
;
761 Kcb
->DelayedCloseIndex
= CmpDelayedCloseSize
;
763 /* Remember if this is a fake key */
764 if (IsFake
) Kcb
->ExtFlags
|= CM_KCB_KEY_NON_EXIST
;
766 /* Setup the other data */
767 Kcb
->SubKeyCount
= Node
->SubKeyCounts
[Stable
] +
768 Node
->SubKeyCounts
[Volatile
];
769 Kcb
->KcbLastWriteTime
= Node
->LastWriteTime
;
770 Kcb
->KcbMaxNameLen
= (USHORT
)Node
->MaxNameLen
;
771 Kcb
->KcbMaxValueNameLen
= (USHORT
)Node
->MaxValueNameLen
;
772 Kcb
->KcbMaxValueDataLen
= (USHORT
)Node
->MaxValueDataLen
;
776 /* Dereference the KCB */
777 CmpDereferenceKeyControlBlockWithLock(Parent
, FALSE
);
779 /* Remove the KCB and free it */
780 CmpRemoveKeyControlBlock(Kcb
);
781 CmpFreeKeyControlBlock(Kcb
);
788 ASSERT((!Kcb
) || (Kcb
->Delete
== FALSE
));
790 /* Check if we had locked the hashes */
793 /* We locked them manually, do we have a parent? */
796 /* Unlock the parent KCB and ourselves */
797 CmpReleaseTwoKcbLockByKey(ConvKey
, Parent
->ConvKey
);
801 /* Unlock only ourselves */
802 CmpReleaseKcbLockByKey(ConvKey
);
812 EnlistKeyBodyWithKCB(IN PCM_KEY_BODY KeyBody
,
816 ASSERT(KeyBody
->KeyControlBlock
!= NULL
);
818 /* Initialize the list entry */
819 InitializeListHead(&KeyBody
->KeyBodyList
);
821 /* FIXME: Implement once we don't link parents to children anymore */