[NTOSKRNL]
[reactos.git] / reactos / ntoskrnl / config / cmapi.c
index 85f2ed9..4db60d7 100644 (file)
@@ -4,6 +4,7 @@
  * FILE:            ntoskrnl/config/cmapi.c
  * PURPOSE:         Configuration Manager - Internal Registry APIs
  * PROGRAMMERS:     Alex Ionescu (alex.ionescu@reactos.org)
+ *                  Eric Kohl
  */
 
 /* INCLUDES ******************************************************************/
 
 /* FUNCTIONS *****************************************************************/
 
+BOOLEAN
+NTAPI
+CmpIsHiveAlreadyLoaded(IN HANDLE KeyHandle,
+                       IN POBJECT_ATTRIBUTES SourceFile,
+                       OUT PCMHIVE *CmHive)
+{
+    NTSTATUS Status;
+    PCM_KEY_BODY KeyBody;
+    PCMHIVE Hive;
+    BOOLEAN Loaded = FALSE;
+    PAGED_CODE();
+
+    /* Sanity check */
+    CMP_ASSERT_EXCLUSIVE_REGISTRY_LOCK();
+
+    /* Reference the handle */
+    Status = ObReferenceObjectByHandle(KeyHandle,
+                                       0,
+                                       CmpKeyObjectType,
+                                       KernelMode,
+                                       (PVOID)&KeyBody,
+                                       NULL);
+    if (!NT_SUCCESS(Status)) return Loaded;
+
+    /* Don't touch deleted KCBs */
+    if (KeyBody->KeyControlBlock->Delete) return Loaded;
+
+    Hive = CONTAINING_RECORD(KeyBody->KeyControlBlock->KeyHive, CMHIVE, Hive);
+
+    /* Must be the root key */
+    if (!(KeyBody->KeyControlBlock->Flags & KEY_HIVE_ENTRY) ||
+        !(Hive->FileUserName.Buffer))
+    {
+        /* It isn't */
+        ObDereferenceObject(KeyBody);
+        return Loaded;
+    }
+
+    /* Now compare the name of the file */
+    if (!RtlCompareUnicodeString(&Hive->FileUserName,
+                                 SourceFile->ObjectName,
+                                 TRUE))
+    {
+        /* Same file found */
+        Loaded = TRUE;
+        *CmHive = Hive;
+        
+        /* If the hive is frozen, not sure what to do */
+        if (Hive->Frozen)
+        {
+            /* FIXME: TODO */
+            DPRINT1("ERROR: Hive is frozen\n");
+            while (TRUE);
+        }
+     }
+
+     /* Dereference and return result */
+     ObDereferenceObject(KeyBody);
+     return Loaded;
+ }
 BOOLEAN
 NTAPI
 CmpDoFlushAll(IN BOOLEAN ForceFlush)
@@ -39,16 +101,35 @@ CmpDoFlushAll(IN BOOLEAN ForceFlush)
         if (!(Hive->Hive.HiveFlags & HIVE_NOLAZYFLUSH))
         {
             /* Acquire the flusher lock */
-            ExAcquirePushLockExclusive((PVOID)&Hive->FlusherLock);
-
-            /* Do the sync */
-            Status = HvSyncHive(&Hive->Hive);
+            CmpLockHiveFlusherExclusive(Hive);
+            
+            /* Check for illegal state */
+            if ((ForceFlush) && (Hive->UseCount))
+            {
+                /* Registry needs to be locked down */
+                CMP_ASSERT_EXCLUSIVE_REGISTRY_LOCK();
+                DPRINT1("FIXME: Hive is damaged and needs fixup\n");
+                while (TRUE);
+            }
+            
+            /* Only sync if we are forced to or if it won't cause a hive shrink */
+            if ((ForceFlush) || (!HvHiveWillShrink(&Hive->Hive)))
+            {
+                /* Do the sync */
+                Status = HvSyncHive(&Hive->Hive);
 
-            /* If something failed - set the flag and continue looping*/
-            if (!NT_SUCCESS(Status)) Result = FALSE;
+                /* If something failed - set the flag and continue looping */
+                if (!NT_SUCCESS(Status)) Result = FALSE;
+            }
+            else
+            {
+                /* We won't flush if the hive might shrink */
+                Result = FALSE;
+                CmpForceForceFlush = TRUE;
+            }
 
             /* Release the flusher lock */
-            ExReleasePushLock((PVOID)&Hive->FlusherLock);
+            CmpUnlockHiveFlusher(Hive);
         }
 
         /* Try the next entry */
@@ -81,10 +162,14 @@ CmpSetValueKeyNew(IN PHHIVE Hive,
     {
         /* Then make sure it's valid and dirty it */
         ASSERT(Parent->ValueList.List != HCELL_NIL);
-        HvMarkCellDirty(Hive, Parent->ValueList.List, FALSE);
+        if (!HvMarkCellDirty(Hive, Parent->ValueList.List, FALSE))
+        {
+            /* Fail if we're out of space for log changes */
+            return STATUS_NO_LOG_SPACE;
+        }
     }
 
-    /* Allocate  avalue cell */
+    /* Allocate value cell */
     ValueCell = HvAllocateCell(Hive,
                                FIELD_OFFSET(CM_KEY_VALUE, Name) +
                                CmpNameSize(Hive, ValueName),
@@ -102,16 +187,33 @@ CmpSetValueKeyNew(IN PHHIVE Hive,
 
     /* Set it up and copy the name */
     CellData->u.KeyValue.Signature = CM_KEY_VALUE_SIGNATURE;
-    CellData->u.KeyValue.Flags = 0;
-    CellData->u.KeyValue.Type = Type;
-    CellData->u.KeyValue.NameLength = CmpCopyName(Hive,
-                                                  CellData->u.KeyValue.Name,
-                                                  ValueName);
+    _SEH2_TRY
+    {
+        /* This can crash since the name is coming from user-mode */
+        CellData->u.KeyValue.NameLength = CmpCopyName(Hive,
+                                                      CellData->u.KeyValue.Name,
+                                                      ValueName);
+    }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+    {
+        /* Fail */
+        DPRINT1("Invalid user data!\n");
+        HvFreeCell(Hive, ValueCell);
+        _SEH2_YIELD(return _SEH2_GetExceptionCode());
+    }
+    _SEH2_END;
+
+    /* Check for compressed name */
     if (CellData->u.KeyValue.NameLength < ValueName->Length)
     {
         /* This is a compressed name */
         CellData->u.KeyValue.Flags = VALUE_COMP_NAME;
     }
+    else
+    {
+        /* No flags to set */
+        CellData->u.KeyValue.Flags = 0;
+    }
 
     /* Check if this is a normal key */
     if (DataSize > CM_KEY_VALUE_SMALL)
@@ -140,6 +242,9 @@ CmpSetValueKeyNew(IN PHHIVE Hive,
         CellData->u.KeyValue.DataLength = DataSize + CM_KEY_VALUE_SPECIAL_SIZE;
         CellData->u.KeyValue.Data = SmallData;
     }
+    
+    /* Set the type now */
+    CellData->u.KeyValue.Type = Type;
 
     /* Add this value cell to the child list */
     Status = CmpAddValueToList(Hive,
@@ -149,7 +254,12 @@ CmpSetValueKeyNew(IN PHHIVE Hive,
                                &Parent->ValueList);
 
     /* If we failed, free the entire cell, including the data */
-    if (!NT_SUCCESS(Status)) CmpFreeValue(Hive, ValueCell);
+    if (!NT_SUCCESS(Status))
+    {
+        /* Overwrite the status with a known one */
+        CmpFreeValue(Hive, ValueCell);
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+    }
 
     /* Return Status */
     return Status;
@@ -170,9 +280,12 @@ CmpSetValueKeyExisting(IN PHHIVE Hive,
     PCELL_DATA CellData;
     ULONG Length;
     BOOLEAN WasSmall, IsSmall;
+    
+    /* Registry writes must be blocked */
+    CMP_ASSERT_FLUSH_LOCK(Hive);
 
     /* Mark the old child cell dirty */
-    HvMarkCellDirty(Hive, OldChild, FALSE);
+    if (!HvMarkCellDirty(Hive, OldChild, FALSE)) return STATUS_NO_LOG_SPACE;
 
     /* See if this is a small or normal key */
     WasSmall = CmpIsKeyValueSmall(&Length, Value->DataLength);
@@ -185,7 +298,7 @@ CmpSetValueKeyExisting(IN PHHIVE Hive,
     ASSERT_VALUE_BIG(Hive, DataSize);
 
     /* Mark the old value dirty */
-    CmpMarkValueDataDirty(Hive, Value);
+    if (!CmpMarkValueDataDirty(Hive, Value)) return STATUS_NO_LOG_SPACE;
 
     /* Check if we have a small key */
     if (IsSmall)
@@ -203,246 +316,507 @@ CmpSetValueKeyExisting(IN PHHIVE Hive,
         Value->Type = Type;
         return STATUS_SUCCESS;
     }
-    else
+    
+    /* We have a normal key. Was the old cell also normal and had data? */
+    if (!(WasSmall) && (Length > 0))
     {
-        /* We have a normal key. Was the old cell also normal and had data? */
-        if (!(WasSmall) && (Length > 0))
-        {
-            /* Get the current data cell and actual data inside it */
-            DataCell = Value->Data;
-            ASSERT(DataCell != HCELL_NIL);
-            CellData = HvGetCell(Hive, DataCell);
-            if (!CellData) return STATUS_INSUFFICIENT_RESOURCES;
+        /* Get the current data cell and actual data inside it */
+        DataCell = Value->Data;
+        ASSERT(DataCell != HCELL_NIL);
+        CellData = HvGetCell(Hive, DataCell);
+        if (!CellData) return STATUS_INSUFFICIENT_RESOURCES;
 
-            /* Immediately release the cell */
-            HvReleaseCell(Hive, DataCell);
+        /* Immediately release the cell */
+        HvReleaseCell(Hive, DataCell);
 
-            /* Make sure that the data cell actually has a size */
-            ASSERT(HvGetCellSize(Hive, CellData) > 0);
+        /* Make sure that the data cell actually has a size */
+        ASSERT(HvGetCellSize(Hive, CellData) > 0);
 
-            /* Check if the previous data cell could fit our new data */
-            if (DataSize <= (ULONG)(HvGetCellSize(Hive, CellData)))
-            {
-                /* Re-use it then */
-                NewCell = DataCell;
-            }
-            else
-            {
-                /* Otherwise, re-allocate the current data cell */
-                NewCell = HvReallocateCell(Hive, DataCell, DataSize);
-                if (NewCell == HCELL_NIL) return STATUS_INSUFFICIENT_RESOURCES;
-            }
+        /* Check if the previous data cell could fit our new data */
+        if (DataSize <= (ULONG)(HvGetCellSize(Hive, CellData)))
+        {
+            /* Re-use it then */
+            NewCell = DataCell;
         }
         else
         {
-            /* This was a small key, or a key with no data, allocate a cell */
-            NewCell = HvAllocateCell(Hive, DataSize, StorageType, HCELL_NIL);
+            /* Otherwise, re-allocate the current data cell */
+            NewCell = HvReallocateCell(Hive, DataCell, DataSize);
             if (NewCell == HCELL_NIL) return STATUS_INSUFFICIENT_RESOURCES;
         }
+    }
+    else
+    {
+        /* This was a small key, or a key with no data, allocate a cell */
+        NewCell = HvAllocateCell(Hive, DataSize, StorageType, HCELL_NIL);
+        if (NewCell == HCELL_NIL) return STATUS_INSUFFICIENT_RESOURCES;
+    }
 
-        /* Now get the actual data for our data cell */
-        CellData = HvGetCell(Hive, NewCell);
-        if (!CellData) ASSERT(FALSE);
+    /* Now get the actual data for our data cell */
+    CellData = HvGetCell(Hive, NewCell);
+    if (!CellData) ASSERT(FALSE);
 
-        /* Release it immediately */
-        HvReleaseCell(Hive, NewCell);
+    /* Release it immediately */
+    HvReleaseCell(Hive, NewCell);
 
-        /* Copy our data into the data cell's buffer, and set up the value */
-        RtlCopyMemory(CellData, Data, DataSize);
-        Value->Data = NewCell;
-        Value->DataLength = DataSize;
-        Value->Type = Type;
+    /* Copy our data into the data cell's buffer, and set up the value */
+    RtlCopyMemory(CellData, Data, DataSize);
+    Value->Data = NewCell;
+    Value->DataLength = DataSize;
+    Value->Type = Type;
 
-        /* Return success */
-        ASSERT(HvIsCellDirty(Hive, NewCell));
-        return STATUS_SUCCESS;
-    }
+    /* Return success */
+    ASSERT(HvIsCellDirty(Hive, NewCell));
+    return STATUS_SUCCESS;
 }
 
 NTSTATUS
 NTAPI
-CmSetValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
-              IN PUNICODE_STRING ValueName,
-              IN ULONG Type,
-              IN PVOID Data,
-              IN ULONG DataLength)
+CmpQueryKeyData(IN PHHIVE Hive,
+                IN PCM_KEY_NODE Node,
+                IN KEY_INFORMATION_CLASS KeyInformationClass,
+                IN OUT PVOID KeyInformation,
+                IN ULONG Length,
+                IN OUT PULONG ResultLength)
 {
-    PHHIVE Hive;
-    PCM_KEY_NODE Parent;
-    PCM_KEY_VALUE Value = NULL;
-    HCELL_INDEX CurrentChild, Cell;
     NTSTATUS Status;
-    BOOLEAN Found, Result;
-    ULONG Count, ChildIndex, SmallData, Storage;
-    VALUE_SEARCH_RETURN_TYPE SearchResult;
+    ULONG Size, SizeLeft, MinimumSize;
+    PKEY_INFORMATION Info = (PKEY_INFORMATION)KeyInformation;
+    USHORT NameLength;
 
-    /* Acquire hive lock */
-    CmpLockRegistry();
-    CmpAcquireKcbLockShared(Kcb);
-    
-    /* Sanity check */
-    ASSERT(sizeof(ULONG) == CM_KEY_VALUE_SMALL);
-    
-    /* Don't touch deleted KCBs */
-DoAgain:
-    if (Kcb->Delete)
-    {
-        /* Fail */
-        Status = STATUS_KEY_DELETED;
-        goto Quickie;
-    }
-    
-    /* Don't let anyone mess with symlinks */
-    if ((Kcb->Flags & KEY_SYM_LINK) &&
-        ((Type != REG_LINK) ||
-         !(ValueName) ||
-         !(RtlEqualUnicodeString(&CmSymbolicLinkValueName, ValueName, TRUE))))
+    /* Check if the value is compressed */
+    if (Node->Flags & KEY_COMP_NAME)
     {
-        /* Invalid modification of a symlink key */
-        Status = STATUS_ACCESS_DENIED;
-        goto Quickie;
-    }
-    
-    /* Search for the value */
-    SearchResult = CmpCompareNewValueDataAgainstKCBCache(Kcb,
-                                                         ValueName,
-                                                         Type,
-                                                         Data,
-                                                         DataLength);
-    if (SearchResult == SearchNeedExclusiveLock)
-    {
-        /* Try again with the exclusive lock */
-        CmpConvertKcbSharedToExclusive(Kcb);
-        goto DoAgain;
+        /* Get the compressed name size */
+        NameLength = CmpCompressedNameSize(Node->Name, Node->NameLength);
     }
-    else if (SearchResult == SearchSuccess)
+    else
     {
-        /* We don't actually need to do anything! */
-        Status = STATUS_SUCCESS;
-        goto Quickie;
+        /* Get the real size */
+        NameLength = Node->NameLength;
     }
 
-    /* We need the exclusive KCB lock now */
-    if (!(CmpIsKcbLockedExclusive(Kcb)) && !(CmpTryToConvertKcbSharedToExclusive(Kcb)))
+    /* Check what kind of information is being requested */
+    switch (KeyInformationClass)
     {
-        /* Acquire exclusive lock */
-        CmpConvertKcbSharedToExclusive(Kcb);
-    }
+        /* Basic information */
+        case KeyBasicInformation:
 
-    /* Get pointer to key cell */
-    Hive = Kcb->KeyHive;
-    Cell = Kcb->KeyCell;
+            /* This is the size we need */
+            Size = FIELD_OFFSET(KEY_BASIC_INFORMATION, Name) + NameLength;
 
-    /* Prepare to scan the key node */
-    Parent = (PCM_KEY_NODE)HvGetCell(Hive, Cell);
-    Count = Parent->ValueList.Count;
-    Found = FALSE;
-    if (Count > 0)
-    {
-        /* Try to find the existing name */
-        Result = CmpFindNameInList(Hive,
-                                   &Parent->ValueList,
-                                   ValueName,
-                                   &ChildIndex,
-                                   &CurrentChild);
-        if (!Result)
-        {
-            /* Fail */
-            Status = STATUS_INSUFFICIENT_RESOURCES;
-            goto Quickie;
-        }
+            /* And this is the minimum we can work with */
+            MinimumSize = FIELD_OFFSET(KEY_BASIC_INFORMATION, Name);
 
-        /* Check if we found something */
-        if (CurrentChild != HCELL_NIL)
-        {
-            /* Get its value */
-            Value = (PCM_KEY_VALUE)HvGetCell(Hive, CurrentChild);
-            if (!Value)
+            /* Let the caller know and assume success */
+            *ResultLength = Size;
+            Status = STATUS_SUCCESS;
+
+            /* Check if the bufer we got is too small */
+            if (Length < MinimumSize)
             {
-                /* Fail */
-                Status = STATUS_INSUFFICIENT_RESOURCES;
-                goto Quickie;
+                /* Let the caller know and fail */
+                Status = STATUS_BUFFER_TOO_SMALL;
+                break;
             }
 
-            /* Remember that we found it */
-            Found = TRUE;
-        }
-    }
-    else
-    {
-        /* No child list, we'll need to add it */
-        ChildIndex = 0;
-    }
-    
-    /* The KCB must be locked exclusive at this point */
-    ASSERT((CmpIsKcbLockedExclusive(Kcb) == TRUE) ||
-           (CmpTestRegistryLockExclusive() == TRUE));
-    
-    /* Mark the cell dirty */
-    HvMarkCellDirty(Hive, Cell, FALSE);
+            /* Copy the basic information */
+            Info->KeyBasicInformation.LastWriteTime = Node->LastWriteTime;
+            Info->KeyBasicInformation.TitleIndex = 0;
+            Info->KeyBasicInformation.NameLength = NameLength;
 
-    /* Get the storage type */
-    Storage = HvGetCellType(Cell);
+            /* Only the name is left */
+            SizeLeft = Length - MinimumSize;
+            Size = NameLength;
 
-    /* Check if this is small data */
-    SmallData = 0;
-    if ((DataLength <= CM_KEY_VALUE_SMALL) && (DataLength > 0))
-    {
-        /* Copy it */
-        RtlCopyMemory(&SmallData, Data, DataLength);
-    }
+            /* Check if we don't have enough space for the name */
+            if (SizeLeft < Size)
+            {
+                /* Truncate the name we'll return, and tell the caller */
+                Size = SizeLeft;
+                Status = STATUS_BUFFER_OVERFLOW;
+            }
 
-    /* Check if we didn't find a matching key */
-    if (!Found)
-    {
-        /* Call the internal routine */
-        Status = CmpSetValueKeyNew(Hive,
-                                   Parent,
-                                   ValueName,
-                                   ChildIndex,
-                                   Type,
-                                   Data,
-                                   DataLength,
-                                   Storage,
-                                   SmallData);
-    }
-    else
-    {
-        /* Call the internal routine */
-        Status = CmpSetValueKeyExisting(Hive,
-                                        CurrentChild,
-                                        Value,
-                                        Type,
-                                        Data,
-                                        DataLength,
-                                        Storage,
-                                        SmallData);
-    }
+            /* Check if this is a compressed key */
+            if (Node->Flags & KEY_COMP_NAME)
+            {
+                /* Copy the compressed name */
+                CmpCopyCompressedName(Info->KeyBasicInformation.Name,
+                                      SizeLeft,
+                                      Node->Name,
+                                      Node->NameLength);
+            }
+            else
+            {
+                /* Otherwise, copy the raw name */
+                RtlCopyMemory(Info->KeyBasicInformation.Name,
+                              Node->Name,
+                              Size);
+            }
+            break;
 
-    /* Check for success */
-    if (NT_SUCCESS(Status))
-    {
-        /* Check if the maximum value name length changed */
-        ASSERT(Parent->MaxValueNameLen == Kcb->KcbMaxValueNameLen);
-        if (Parent->MaxValueNameLen < ValueName->Length)
-        {
-            /* Set the new values */
-            Parent->MaxValueNameLen = ValueName->Length;
-            Kcb->KcbMaxValueNameLen = ValueName->Length;
-        }
-    
-        /* Check if the maximum data length changed */
-        ASSERT(Parent->MaxValueDataLen == Kcb->KcbMaxValueDataLen);
-        if (Parent->MaxValueDataLen < DataLength)
-        {
-            /* Update it */
-            Parent->MaxValueDataLen = DataLength;
-            Kcb->KcbMaxValueDataLen = Parent->MaxValueDataLen;
-        }
-        
+        /* Node information */
+        case KeyNodeInformation:
+
+            /* Calculate the size we need */
+            Size = FIELD_OFFSET(KEY_NODE_INFORMATION, Name) +
+                   NameLength +
+                   Node->ClassLength;
+
+            /* And the minimum size we can support */
+            MinimumSize = FIELD_OFFSET(KEY_NODE_INFORMATION, Name);
+
+            /* Return the size to the caller and assume succes */
+            *ResultLength = Size;
+            Status = STATUS_SUCCESS;
+
+            /* Check if the caller's buffer is too small */
+            if (Length < MinimumSize)
+            {
+                /* Let them know, and fail */
+                Status = STATUS_BUFFER_TOO_SMALL;
+                break;
+            }
+
+            /* Copy the basic information */
+            Info->KeyNodeInformation.LastWriteTime = Node->LastWriteTime;
+            Info->KeyNodeInformation.TitleIndex = 0;
+            Info->KeyNodeInformation.ClassLength = Node->ClassLength;
+            Info->KeyNodeInformation.NameLength = NameLength;
+
+            /* Now the name is left */
+            SizeLeft = Length - MinimumSize;
+            Size = NameLength;
+
+            /* Check if the name can fit entirely */
+            if (SizeLeft < Size)
+            {
+                /* It can't, we'll have to truncate. Tell the caller */
+                Size = SizeLeft;
+                Status = STATUS_BUFFER_OVERFLOW;
+            }
+
+            /* Check if the key node name is compressed */
+            if (Node->Flags & KEY_COMP_NAME)
+            {
+                /* Copy the compressed name */
+                CmpCopyCompressedName(Info->KeyNodeInformation.Name,
+                                      SizeLeft,
+                                      Node->Name,
+                                      Node->NameLength);
+            }
+            else
+            {
+                /* It isn't, so copy the raw name */
+                RtlCopyMemory(Info->KeyNodeInformation.Name,
+                              Node->Name,
+                              Size);
+            }
+
+            /* Check if the node has a class */
+            if (Node->ClassLength > 0)
+            {
+                /* It does. We don't support these yet */
+                ASSERTMSG("Classes not supported\n", FALSE);
+            }
+            else
+            {
+                /* It doesn't, so set offset to -1, not 0! */
+                Info->KeyNodeInformation.ClassOffset = 0xFFFFFFFF;
+            }
+            break;
+
+        /* Full information requsted */
+        case KeyFullInformation:
+
+            /* This is the size we need */
+            Size = FIELD_OFFSET(KEY_FULL_INFORMATION, Class) +
+                   Node->ClassLength;
+
+            /* This is what we can work with */
+            MinimumSize = FIELD_OFFSET(KEY_FULL_INFORMATION, Class);
+
+            /* Return it to caller and assume success */
+            *ResultLength = Size;
+            Status = STATUS_SUCCESS;
+
+            /* Check if the caller's buffer is to small */
+            if (Length < MinimumSize)
+            {
+                /* Let them know and fail */
+                Status = STATUS_BUFFER_TOO_SMALL;
+                break;
+            }
+
+            /* Now copy all the basic information */
+            Info->KeyFullInformation.LastWriteTime = Node->LastWriteTime;
+            Info->KeyFullInformation.TitleIndex = 0;
+            Info->KeyFullInformation.ClassLength = Node->ClassLength;
+            Info->KeyFullInformation.SubKeys = Node->SubKeyCounts[Stable] +
+                                               Node->SubKeyCounts[Volatile];
+            Info->KeyFullInformation.Values = Node->ValueList.Count;
+            Info->KeyFullInformation.MaxNameLen = Node->MaxNameLen;
+            Info->KeyFullInformation.MaxClassLen = Node->MaxClassLen;
+            Info->KeyFullInformation.MaxValueNameLen = Node->MaxValueNameLen;
+            Info->KeyFullInformation.MaxValueDataLen = Node->MaxValueDataLen;
+
+            /* Check if we have a class */
+            if (Node->ClassLength > 0)
+            {
+                /* We do, but we currently don't support this */
+                ASSERTMSG("Classes not supported\n", FALSE);
+            }
+            else
+            {
+                /* We don't have a class, so set offset to -1, not 0! */
+                Info->KeyNodeInformation.ClassOffset = 0xFFFFFFFF;
+            }
+            break;
+
+        /* Any other class that got sent here is invalid! */
+        default:
+
+            /* Set failure code */
+            Status = STATUS_INVALID_PARAMETER;
+            break;
+    }
+
+    /* Return status */
+    return Status;
+}
+
+NTSTATUS
+NTAPI
+CmSetValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
+              IN PUNICODE_STRING ValueName,
+              IN ULONG Type,
+              IN PVOID Data,
+              IN ULONG DataLength)
+{
+    PHHIVE Hive = NULL;
+    PCM_KEY_NODE Parent;
+    PCM_KEY_VALUE Value = NULL;
+    HCELL_INDEX CurrentChild, Cell;
+    NTSTATUS Status;
+    BOOLEAN Found, Result;
+    ULONG Count, ChildIndex, SmallData, Storage;
+    VALUE_SEARCH_RETURN_TYPE SearchResult;
+    BOOLEAN FirstTry = TRUE, FlusherLocked = FALSE;
+    HCELL_INDEX ParentCell = HCELL_NIL, ChildCell = HCELL_NIL;
+
+    /* Acquire hive and KCB lock */
+    CmpLockRegistry();
+    CmpAcquireKcbLockShared(Kcb);
+    
+    /* Sanity check */
+    ASSERT(sizeof(ULONG) == CM_KEY_VALUE_SMALL);
+    
+    /* Don't touch deleted KCBs */
+DoAgain:
+    if (Kcb->Delete)
+    {
+        /* Fail */
+        Status = STATUS_KEY_DELETED;
+        goto Quickie;
+    }
+    
+    /* Don't let anyone mess with symlinks */
+    if ((Kcb->Flags & KEY_SYM_LINK) &&
+        ((Type != REG_LINK) ||
+         !(ValueName) ||
+         !(RtlEqualUnicodeString(&CmSymbolicLinkValueName, ValueName, TRUE))))
+    {
+        /* Invalid modification of a symlink key */
+        Status = STATUS_ACCESS_DENIED;
+        goto Quickie;
+    }
+
+    /* Check if this is the first attempt */
+    if (FirstTry)
+    {
+        /* Search for the value in the cache */
+        SearchResult = CmpCompareNewValueDataAgainstKCBCache(Kcb,
+                                                             ValueName,
+                                                             Type,
+                                                             Data,
+                                                             DataLength);
+        if (SearchResult == SearchNeedExclusiveLock)
+        {
+            /* Try again with the exclusive lock */
+            CmpConvertKcbSharedToExclusive(Kcb);
+            goto DoAgain;
+        }
+        else if (SearchResult == SearchSuccess)
+        {
+            /* We don't actually need to do anything! */
+            Status = STATUS_SUCCESS;
+            goto Quickie;
+        }
+
+        /* We need the exclusive KCB lock now */
+        if (!(CmpIsKcbLockedExclusive(Kcb)) &&
+            !(CmpTryToConvertKcbSharedToExclusive(Kcb)))
+        {
+            /* Acquire exclusive lock */
+            CmpConvertKcbSharedToExclusive(Kcb);
+        }
+        
+        /* Cache lookup failed, so don't try it next time */
+        FirstTry = FALSE;
+        
+        /* Now grab the flush lock since the key will be modified */
+        ASSERT(FlusherLocked == FALSE);
+        CmpLockHiveFlusherShared((PCMHIVE)Kcb->KeyHive);
+        FlusherLocked = TRUE;
+        goto DoAgain;
+    }
+    else
+    {
+        /* Get pointer to key cell */
+        Hive = Kcb->KeyHive;
+        Cell = Kcb->KeyCell;
+
+        /* Get the parent */
+        Parent = (PCM_KEY_NODE)HvGetCell(Hive, Cell);
+        ASSERT(Parent);
+        ParentCell = Cell;
+        
+        /* Prepare to scan the key node */
+        Count = Parent->ValueList.Count;
+        Found = FALSE;
+        if (Count > 0)
+        {
+            /* Try to find the existing name */
+            Result = CmpFindNameInList(Hive,
+                                       &Parent->ValueList,
+                                       ValueName,
+                                       &ChildIndex,
+                                       &CurrentChild);
+            if (!Result)
+            {
+                /* Fail */
+                Status = STATUS_INSUFFICIENT_RESOURCES;
+                goto Quickie;
+            }
+
+            /* Check if we found something */
+            if (CurrentChild != HCELL_NIL)
+            {
+                /* Release existing child */
+                if (ChildCell != HCELL_NIL)
+                {
+                    HvReleaseCell(Hive, ChildCell);
+                    ChildCell = HCELL_NIL;
+                }
+                
+                /* Get its value */
+                Value = (PCM_KEY_VALUE)HvGetCell(Hive, CurrentChild);
+                if (!Value)
+                {
+                    /* Fail */
+                    Status = STATUS_INSUFFICIENT_RESOURCES;
+                    goto Quickie;
+                }
+
+                /* Remember that we found it */
+                ChildCell = CurrentChild;
+                Found = TRUE;
+            }
+        }
+        else
+        {
+            /* No child list, we'll need to add it */
+            ChildIndex = 0;
+        }
+    }
+    
+    /* Should only get here on the second pass */
+    ASSERT(FirstTry == FALSE);
+    
+    /* The KCB must be locked exclusive at this point */
+    CMP_ASSERT_KCB_LOCK(Kcb);
+    
+    /* Mark the cell dirty */
+    if (!HvMarkCellDirty(Hive, Cell, FALSE))
+    {
+        /* Not enough log space, fail */
+        Status = STATUS_NO_LOG_SPACE;
+        goto Quickie;
+    }
+
+    /* Get the storage type */
+    Storage = HvGetCellType(Cell);
+
+    /* Check if this is small data */
+    SmallData = 0;
+    if ((DataLength <= CM_KEY_VALUE_SMALL) && (DataLength > 0))
+    {
+        /* Need SEH because user data may be invalid */
+        _SEH2_TRY
+        {
+            /* Copy it */
+            RtlCopyMemory(&SmallData, Data, DataLength);
+        }
+        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+        {
+            /* Return failure code */
+            Status = _SEH2_GetExceptionCode();
+            _SEH2_YIELD(goto Quickie);
+        }
+        _SEH2_END;
+    }
+
+    /* Check if we didn't find a matching key */
+    if (!Found)
+    {
+        /* Call the internal routine */
+        Status = CmpSetValueKeyNew(Hive,
+                                   Parent,
+                                   ValueName,
+                                   ChildIndex,
+                                   Type,
+                                   Data,
+                                   DataLength,
+                                   Storage,
+                                   SmallData);
+    }
+    else
+    {
+        /* Call the internal routine */
+        Status = CmpSetValueKeyExisting(Hive,
+                                        CurrentChild,
+                                        Value,
+                                        Type,
+                                        Data,
+                                        DataLength,
+                                        Storage,
+                                        SmallData);
+    }
+
+    /* Check for success */
+    if (NT_SUCCESS(Status))
+    {
+        /* Check if the maximum value name length changed */
+        ASSERT(Parent->MaxValueNameLen == Kcb->KcbMaxValueNameLen);
+        if (Parent->MaxValueNameLen < ValueName->Length)
+        {
+            /* Set the new values */
+            Parent->MaxValueNameLen = ValueName->Length;
+            Kcb->KcbMaxValueNameLen = ValueName->Length;
+        }
+    
+        /* Check if the maximum data length changed */
+        ASSERT(Parent->MaxValueDataLen == Kcb->KcbMaxValueDataLen);
+        if (Parent->MaxValueDataLen < DataLength)
+        {
+            /* Update it */
+            Parent->MaxValueDataLen = DataLength;
+            Kcb->KcbMaxValueDataLen = Parent->MaxValueDataLen;
+        }
+        
         /* Save the write time */
         KeQuerySystemTime(&Parent->LastWriteTime);
-        KeQuerySystemTime(&Kcb->KcbLastWriteTime);
+        Kcb->KcbLastWriteTime = Parent->LastWriteTime;
         
         /* Check if the cell is cached */
         if ((Found) && (CMP_IS_CELL_CACHED(Kcb->ValueCache.ValueList)))
@@ -463,10 +837,21 @@ DoAgain:
             Kcb->ValueCache.Count = Parent->ValueList.Count;
             Kcb->ValueCache.ValueList = Parent->ValueList.List;
         }
+        
+        /* Notify registered callbacks */
+        CmpReportNotify(Kcb,
+                        Hive,
+                        Kcb->KeyCell,
+                        REG_NOTIFY_CHANGE_LAST_SET);
     }
     
+    /* Release the cells */
 Quickie:
+    if ((ParentCell != HCELL_NIL) && (Hive)) HvReleaseCell(Hive, ParentCell);
+    if ((ChildCell != HCELL_NIL) && (Hive)) HvReleaseCell(Hive, ChildCell);
+
     /* Release the locks */
+    if (FlusherLocked) CmpUnlockHiveFlusher((PCMHIVE)Hive);
     CmpReleaseKcbLock(Kcb);
     CmpUnlockRegistry();
     return Status;
@@ -504,15 +889,13 @@ CmDeleteValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
     /* Get the hive and the cell index */
     Hive = Kcb->KeyHive;
     Cell = Kcb->KeyCell;
+    
+    /* Lock flushes */
+    CmpLockHiveFlusherShared((PCMHIVE)Hive);
 
     /* Get the parent key node */
     Parent = (PCM_KEY_NODE)HvGetCell(Hive, Cell);
-    if (!Parent)
-    {
-        /* Fail */
-        Status = STATUS_INSUFFICIENT_RESOURCES;
-        goto Quickie;
-    }
+    ASSERT(Parent);
 
     /* Get the value list and check if it has any entries */
     ChildList = &Parent->ValueList;
@@ -535,16 +918,26 @@ CmDeleteValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
         if (ChildCell == HCELL_NIL) goto Quickie;
 
         /* We found the value, mark all relevant cells dirty */
-        HvMarkCellDirty(Hive, Cell, FALSE);
-        HvMarkCellDirty(Hive, Parent->ValueList.List, FALSE);
-        HvMarkCellDirty(Hive, ChildCell, FALSE);
+        if (!((HvMarkCellDirty(Hive, Cell, FALSE)) &&
+              (HvMarkCellDirty(Hive, Parent->ValueList.List, FALSE)) &&
+              (HvMarkCellDirty(Hive, ChildCell, FALSE))))
+        {
+            /* Not enough log space, fail */
+            Status = STATUS_NO_LOG_SPACE;
+            goto Quickie;
+        }
 
         /* Get the key value */
         Value = (PCM_KEY_VALUE)HvGetCell(Hive,ChildCell);
-        if (!Value) ASSERT(FALSE);
+        ASSERT(Value);
 
         /* Mark it and all related data as dirty */
-        CmpMarkValueDataDirty(Hive, Value);
+        if (!CmpMarkValueDataDirty(Hive, Value))
+        {
+            /* Not enough log space, fail */
+            Status = STATUS_NO_LOG_SPACE;
+            goto Quickie;
+        }
 
         /* Ssanity checks */
         ASSERT(HvIsCellDirty(Hive, Parent->ValueList.List));
@@ -552,7 +945,12 @@ CmDeleteValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
 
         /* Remove the value from the child list */
         Status = CmpRemoveValueFromList(Hive, ChildIndex, ChildList);
-        if(!NT_SUCCESS(Status)) goto Quickie;
+        if (!NT_SUCCESS(Status))
+        {
+            /* Set known error */
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto Quickie;
+        }
 
         /* Remove the value and its data itself */
         if (!CmpFreeValue(Hive, ChildCell))
@@ -564,7 +962,7 @@ CmDeleteValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
 
         /* Set the last write time */
         KeQuerySystemTime(&Parent->LastWriteTime);
-        KeQuerySystemTime(&Kcb->KcbLastWriteTime);
+        Kcb->KcbLastWriteTime = Parent->LastWriteTime;
 
         /* Sanity check */
         ASSERT(Parent->MaxValueNameLen == Kcb->KcbMaxValueNameLen);
@@ -591,6 +989,9 @@ CmDeleteValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
         /* Set the value cache */
         Kcb->ValueCache.Count = ChildList->Count;
         Kcb->ValueCache.ValueList = ChildList->List;
+        
+        /* Notify registered callbacks */
+        CmpReportNotify(Kcb, Hive, Cell, REG_NOTIFY_CHANGE_LAST_SET);
 
         /* Change default Status to success */
         Status = STATUS_SUCCESS;
@@ -609,6 +1010,7 @@ Quickie:
     }
 
     /* Release locks */
+    CmpUnlockHiveFlusher((PCMHIVE)Hive);
     CmpReleaseKcbLock(Kcb);
     CmpUnlockRegistry();
     return Status;
@@ -621,118 +1023,16 @@ CmQueryValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
                 IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
                 IN PVOID KeyValueInformation,
                 IN ULONG Length,
-                IN PULONG ResultLength)
-{
-    NTSTATUS Status;
-    PCM_KEY_VALUE ValueData;
-    ULONG Index;
-    BOOLEAN ValueCached = FALSE;
-    PCM_CACHED_VALUE *CachedValue;
-    HCELL_INDEX CellToRelease;
-    VALUE_SEARCH_RETURN_TYPE Result;
-    PHHIVE Hive;
-    PAGED_CODE();
-
-    /* Acquire hive lock */
-    CmpLockRegistry();
-    
-    /* Lock the KCB shared */
-    CmpAcquireKcbLockShared(Kcb);
-    
-    /* Don't touch deleted keys */
-DoAgain:
-    if (Kcb->Delete)
-    {
-        /* Undo everything */
-        CmpReleaseKcbLock(Kcb);
-        CmpUnlockRegistry();
-        return STATUS_KEY_DELETED;
-    }
-    
-    /* We don't deal with this yet */
-    if (Kcb->ExtFlags & CM_KCB_SYM_LINK_FOUND)
-    {
-        /* Shouldn't happen */
-        ASSERT(FALSE);
-    }
-
-    /* Get the hive */
-    Hive = Kcb->KeyHive;
-
-    /* Find the key value */
-    Result = CmpFindValueByNameFromCache(Kcb,
-                                         &ValueName,
-                                         &CachedValue,
-                                         &Index,
-                                         &ValueData,
-                                         &ValueCached,
-                                         &CellToRelease);
-    if (Result == SearchNeedExclusiveLock)
-    {
-        /* Check if we need an exclusive lock */
-        ASSERT(CellToRelease == HCELL_NIL);
-        ASSERT(ValueData == NULL);
-        
-        /* Try with exclusive KCB lock */
-        CmpConvertKcbSharedToExclusive(Kcb);
-        goto DoAgain;
-    }
-    
-    if (Result == SearchSuccess)
-    {
-        /* Sanity check */
-        ASSERT(ValueData != NULL);
-
-        /* Query the information requested */
-        Result = CmpQueryKeyValueData(Kcb,
-                                      CachedValue,
-                                      ValueData,
-                                      ValueCached,
-                                      KeyValueInformationClass,
-                                      KeyValueInformation,
-                                      Length,
-                                      ResultLength,
-                                      &Status);
-        if (Result == SearchNeedExclusiveLock)
-        {            
-            /* Try with exclusive KCB lock */
-            CmpConvertKcbSharedToExclusive(Kcb);
-            goto DoAgain;
-        }
-    }
-    else
-    {
-        /* Failed to find the value */
-        Status = STATUS_OBJECT_NAME_NOT_FOUND;
-    }
-
-    /* If we have a cell to release, do so */
-    if (CellToRelease != HCELL_NIL) HvReleaseCell(Hive, CellToRelease);
-
-    /* Release locks */
-    CmpReleaseKcbLock(Kcb);
-    CmpUnlockRegistry();
-    return Status;
-}
-
-NTSTATUS
-NTAPI
-CmEnumerateValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
-                    IN ULONG Index,
-                    IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
-                    IN PVOID KeyValueInformation,
-                    IN ULONG Length,
-                    IN PULONG ResultLength)
+                IN PULONG ResultLength)
 {
     NTSTATUS Status;
-    PHHIVE Hive;
-    PCM_KEY_NODE Parent;
-    HCELL_INDEX CellToRelease = HCELL_NIL, CellToRelease2 = HCELL_NIL;
-    VALUE_SEARCH_RETURN_TYPE Result;
-    BOOLEAN IndexIsCached, ValueIsCached = FALSE;
-    PCELL_DATA CellData;
+    PCM_KEY_VALUE ValueData;
+    ULONG Index;
+    BOOLEAN ValueCached = FALSE;
     PCM_CACHED_VALUE *CachedValue;
-    PCM_KEY_VALUE ValueData = NULL;
+    HCELL_INDEX CellToRelease;
+    VALUE_SEARCH_RETURN_TYPE Result;
+    PHHIVE Hive;
     PAGED_CODE();
 
     /* Acquire hive lock */
@@ -740,7 +1040,7 @@ CmEnumerateValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
     
     /* Lock the KCB shared */
     CmpAcquireKcbLockShared(Kcb);
-
+    
     /* Don't touch deleted keys */
 DoAgain:
     if (Kcb->Delete)
@@ -750,26 +1050,6 @@ DoAgain:
         CmpUnlockRegistry();
         return STATUS_KEY_DELETED;
     }
-
-    /* Get the hive and parent */
-    Hive = Kcb->KeyHive;
-    Parent = (PCM_KEY_NODE)HvGetCell(Hive, Kcb->KeyCell);
-    if (!Parent)
-    {
-        /* Fail */
-        Status = STATUS_INSUFFICIENT_RESOURCES;
-        goto Quickie;
-    }
-
-    /* Make sure the index is valid */
-    //if (Index >= Kcb->ValueCache.Count)
-    if (Index >= Parent->ValueList.Count)
-    {
-        /* Release the cell and fail */
-        HvReleaseCell(Hive, Kcb->KeyCell);
-        Status = STATUS_NO_MORE_ENTRIES;
-        goto Quickie;
-    }
     
     /* We don't deal with this yet */
     if (Kcb->ExtFlags & CM_KCB_SYM_LINK_FOUND)
@@ -778,11 +1058,17 @@ DoAgain:
         ASSERT(FALSE);
     }
 
-    /* Find the value list */
-    Result = CmpGetValueListFromCache(Kcb,
-                                      &CellData,
-                                      &IndexIsCached,
-                                      &CellToRelease);
+    /* Get the hive */
+    Hive = Kcb->KeyHive;
+
+    /* Find the key value */
+    Result = CmpFindValueByNameFromCache(Kcb,
+                                         &ValueName,
+                                         &CachedValue,
+                                         &Index,
+                                         &ValueData,
+                                         &ValueCached,
+                                         &CellToRelease);
     if (Result == SearchNeedExclusiveLock)
     {
         /* Check if we need an exclusive lock */
@@ -793,68 +1079,54 @@ DoAgain:
         CmpConvertKcbSharedToExclusive(Kcb);
         goto DoAgain;
     }
-    else if (Result != SearchSuccess)
-    {
-        /* Sanity check */
-        ASSERT(CellData == NULL);
-
-        /* Release the cell and fail */
-        Status = STATUS_INSUFFICIENT_RESOURCES;
-        goto Quickie;
-    }
-
-    /* Now get the key value */
-    Result = CmpGetValueKeyFromCache(Kcb,
-                                     CellData,
-                                     Index,
-                                     &CachedValue,
-                                     &ValueData,
-                                     IndexIsCached,
-                                     &ValueIsCached,
-                                     &CellToRelease2);
-    if (Result == SearchNeedExclusiveLock)
-    {
-        /* Try with exclusive KCB lock */
-        CmpConvertKcbSharedToExclusive(Kcb);
-        goto DoAgain;
-    }
-    else if (Result != SearchSuccess)
+    
+    if (Result == SearchSuccess)
     {
         /* Sanity check */
-        ASSERT(ValueData == NULL);
+        ASSERT(ValueData != NULL);
 
-        /* Release the cells and fail */
-        Status = STATUS_INSUFFICIENT_RESOURCES;
-        goto Quickie;
+        /* User data, protect against exceptions */
+        _SEH2_TRY
+        {
+            /* Query the information requested */
+            Result = CmpQueryKeyValueData(Kcb,
+                                          CachedValue,
+                                          ValueData,
+                                          ValueCached,
+                                          KeyValueInformationClass,
+                                          KeyValueInformation,
+                                          Length,
+                                          ResultLength,
+                                          &Status);
+            if (Result == SearchNeedExclusiveLock)
+            {            
+                /* Release the value cell */
+                if (CellToRelease != HCELL_NIL)
+                {
+                    HvReleaseCell(Hive, CellToRelease);
+                    CellToRelease = HCELL_NIL;
+                }
+                
+                /* Try with exclusive KCB lock */
+                CmpConvertKcbSharedToExclusive(Kcb);
+                goto DoAgain;
+            }
+        }
+        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+        {
+            Status = _SEH2_GetExceptionCode();
+        }
+        _SEH2_END;
     }
-
-    /* Query the information requested */
-    Result = CmpQueryKeyValueData(Kcb,
-                                  CachedValue,
-                                  ValueData,
-                                  ValueIsCached,
-                                  KeyValueInformationClass,
-                                  KeyValueInformation,
-                                  Length,
-                                  ResultLength,
-                                  &Status);
-    if (Result == SearchNeedExclusiveLock)
+    else
     {
-        /* Try with exclusive KCB lock */
-        CmpConvertKcbSharedToExclusive(Kcb);
-        goto DoAgain;
+        /* Failed to find the value */
+        Status = STATUS_OBJECT_NAME_NOT_FOUND;
     }
 
-Quickie:
     /* If we have a cell to release, do so */
     if (CellToRelease != HCELL_NIL) HvReleaseCell(Hive, CellToRelease);
 
-    /* Release the parent cell */
-    HvReleaseCell(Hive, Kcb->KeyCell);
-
-    /* If we have a cell to release, do so */
-    if (CellToRelease2 != HCELL_NIL) HvReleaseCell(Hive, CellToRelease2);
-
     /* Release locks */
     CmpReleaseKcbLock(Kcb);
     CmpUnlockRegistry();
@@ -863,216 +1135,172 @@ Quickie:
 
 NTSTATUS
 NTAPI
-CmpQueryKeyData(IN PHHIVE Hive,
-                IN PCM_KEY_NODE Node,
-                IN KEY_INFORMATION_CLASS KeyInformationClass,
-                IN OUT PVOID KeyInformation,
-                IN ULONG Length,
-                IN OUT PULONG ResultLength)
+CmEnumerateValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
+                    IN ULONG Index,
+                    IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
+                    IN PVOID KeyValueInformation,
+                    IN ULONG Length,
+                    IN PULONG ResultLength)
 {
     NTSTATUS Status;
-    ULONG Size, SizeLeft, MinimumSize;
-    PKEY_INFORMATION Info = (PKEY_INFORMATION)KeyInformation;
-    USHORT NameLength;
-
-    /* Check if the value is compressed */
-    if (Node->Flags & KEY_COMP_NAME)
-    {
-        /* Get the compressed name size */
-        NameLength = CmpCompressedNameSize(Node->Name, Node->NameLength);
-    }
-    else
-    {
-        /* Get the real size */
-        NameLength = Node->NameLength;
-    }
-
-    /* Check what kind of information is being requested */
-    switch (KeyInformationClass)
-    {
-        /* Basic information */
-        case KeyBasicInformation:
-
-            /* This is the size we need */
-            Size = FIELD_OFFSET(KEY_BASIC_INFORMATION, Name) + NameLength;
-
-            /* And this is the minimum we can work with */
-            MinimumSize = FIELD_OFFSET(KEY_BASIC_INFORMATION, Name);
-
-            /* Let the caller know and assume success */
-            *ResultLength = Size;
-            Status = STATUS_SUCCESS;
-
-            /* Check if the bufer we got is too small */
-            if (Length < MinimumSize)
-            {
-                /* Let the caller know and fail */
-                Status = STATUS_BUFFER_TOO_SMALL;
-                break;
-            }
-
-            /* Copy the basic information */
-            Info->KeyBasicInformation.LastWriteTime = Node->LastWriteTime;
-            Info->KeyBasicInformation.TitleIndex = 0;
-            Info->KeyBasicInformation.NameLength = NameLength;
-
-            /* Only the name is left */
-            SizeLeft = Length - MinimumSize;
-            Size = NameLength;
-
-            /* Check if we don't have enough space for the name */
-            if (SizeLeft < Size)
-            {
-                /* Truncate the name we'll return, and tell the caller */
-                Size = SizeLeft;
-                Status = STATUS_BUFFER_OVERFLOW;
-            }
-
-            /* Check if this is a compressed key */
-            if (Node->Flags & KEY_COMP_NAME)
-            {
-                /* Copy the compressed name */
-                CmpCopyCompressedName(Info->KeyBasicInformation.Name,
-                                      SizeLeft,
-                                      Node->Name,
-                                      Node->NameLength);
-            }
-            else
-            {
-                /* Otherwise, copy the raw name */
-                RtlCopyMemory(Info->KeyBasicInformation.Name,
-                              Node->Name,
-                              Size);
-            }
-            break;
-
-        /* Node information */
-        case KeyNodeInformation:
-
-            /* Calculate the size we need */
-            Size = FIELD_OFFSET(KEY_NODE_INFORMATION, Name) +
-                   NameLength +
-                   Node->ClassLength;
-
-            /* And the minimum size we can support */
-            MinimumSize = FIELD_OFFSET(KEY_NODE_INFORMATION, Name);
-
-            /* Return the size to the caller and assume succes */
-            *ResultLength = Size;
-            Status = STATUS_SUCCESS;
-
-            /* Check if the caller's buffer is too small */
-            if (Length < MinimumSize)
-            {
-                /* Let them know, and fail */
-                Status = STATUS_BUFFER_TOO_SMALL;
-                break;
-            }
-
-            /* Copy the basic information */
-            Info->KeyNodeInformation.LastWriteTime = Node->LastWriteTime;
-            Info->KeyNodeInformation.TitleIndex = 0;
-            Info->KeyNodeInformation.ClassLength = Node->ClassLength;
-            Info->KeyNodeInformation.NameLength = NameLength;
-
-            /* Now the name is left */
-            SizeLeft = Length - MinimumSize;
-            Size = NameLength;
-
-            /* Check if the name can fit entirely */
-            if (SizeLeft < Size)
-            {
-                /* It can't, we'll have to truncate. Tell the caller */
-                Size = SizeLeft;
-                Status = STATUS_BUFFER_OVERFLOW;
-            }
+    PHHIVE Hive;
+    PCM_KEY_NODE Parent;
+    HCELL_INDEX CellToRelease = HCELL_NIL, CellToRelease2 = HCELL_NIL;
+    VALUE_SEARCH_RETURN_TYPE Result;
+    BOOLEAN IndexIsCached, ValueIsCached = FALSE;
+    PCELL_DATA CellData;
+    PCM_CACHED_VALUE *CachedValue;
+    PCM_KEY_VALUE ValueData = NULL;
+    PAGED_CODE();
 
-            /* Check if the key node name is compressed */
-            if (Node->Flags & KEY_COMP_NAME)
-            {
-                /* Copy the compressed name */
-                CmpCopyCompressedName(Info->KeyNodeInformation.Name,
-                                      SizeLeft,
-                                      Node->Name,
-                                      Node->NameLength);
-            }
-            else
-            {
-                /* It isn't, so copy the raw name */
-                RtlCopyMemory(Info->KeyNodeInformation.Name,
-                              Node->Name,
-                              Size);
-            }
+    /* Acquire hive lock */
+    CmpLockRegistry();
+    
+    /* Lock the KCB shared */
+    CmpAcquireKcbLockShared(Kcb);
 
-            /* Check if the node has a class */
-            if (Node->ClassLength > 0)
-            {
-                /* It does. We don't support these yet */
-                ASSERTMSG("Classes not supported\n", FALSE);
-            }
-            else
-            {
-                /* It doesn't, so set offset to -1, not 0! */
-                Info->KeyNodeInformation.ClassOffset = 0xFFFFFFFF;
-            }
-            break;
+    /* Don't touch deleted keys */
+DoAgain:
+    if (Kcb->Delete)
+    {
+        /* Undo everything */
+        CmpReleaseKcbLock(Kcb);
+        CmpUnlockRegistry();
+        return STATUS_KEY_DELETED;
+    }
 
-        /* Full information requsted */
-        case KeyFullInformation:
+    /* Get the hive and parent */
+    Hive = Kcb->KeyHive;
+    Parent = (PCM_KEY_NODE)HvGetCell(Hive, Kcb->KeyCell);
+    ASSERT(Parent);
 
-            /* This is the size we need */
-            Size = FIELD_OFFSET(KEY_FULL_INFORMATION, Class) +
-                   Node->ClassLength;
+    /* FIXME: Lack of cache? */
+    if (Kcb->ValueCache.Count != Parent->ValueList.Count)
+    {
+        DPRINT1("HACK: Overriding value cache count\n");
+        Kcb->ValueCache.Count = Parent->ValueList.Count;
+    }
 
-            /* This is what we can work with */
-            MinimumSize = FIELD_OFFSET(KEY_FULL_INFORMATION, Class);
+    /* Make sure the index is valid */    
+    if (Index >= Kcb->ValueCache.Count)
+    {
+        /* Release the cell and fail */
+        HvReleaseCell(Hive, Kcb->KeyCell);
+        Status = STATUS_NO_MORE_ENTRIES;
+        goto Quickie;
+    }
+    
+    /* We don't deal with this yet */
+    if (Kcb->ExtFlags & CM_KCB_SYM_LINK_FOUND)
+    {
+        /* Shouldn't happen */
+        ASSERT(FALSE);
+    }
 
-            /* Return it to caller and assume success */
-            *ResultLength = Size;
-            Status = STATUS_SUCCESS;
+    /* Find the value list */
+    Result = CmpGetValueListFromCache(Kcb,
+                                      &CellData,
+                                      &IndexIsCached,
+                                      &CellToRelease);
+    if (Result == SearchNeedExclusiveLock)
+    {
+        /* Check if we need an exclusive lock */
+        ASSERT(CellToRelease == HCELL_NIL);
+        HvReleaseCell(Hive, Kcb->KeyCell);
+        
+        /* Try with exclusive KCB lock */
+        CmpConvertKcbSharedToExclusive(Kcb);
+        goto DoAgain;
+    }
+    else if (Result != SearchSuccess)
+    {
+        /* Sanity check */
+        ASSERT(CellData == NULL);
 
-            /* Check if the caller's buffer is to small */
-            if (Length < MinimumSize)
-            {
-                /* Let them know and fail */
-                Status = STATUS_BUFFER_TOO_SMALL;
-                break;
-            }
+        /* Release the cell and fail */
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto Quickie;
+    }
 
-            /* Now copy all the basic information */
-            Info->KeyFullInformation.LastWriteTime = Node->LastWriteTime;
-            Info->KeyFullInformation.TitleIndex = 0;
-            Info->KeyFullInformation.ClassLength = Node->ClassLength;
-            Info->KeyFullInformation.SubKeys = Node->SubKeyCounts[Stable] +
-                                               Node->SubKeyCounts[Volatile];
-            Info->KeyFullInformation.Values = Node->ValueList.Count;
-            Info->KeyFullInformation.MaxNameLen = Node->MaxNameLen;
-            Info->KeyFullInformation.MaxClassLen = Node->MaxClassLen;
-            Info->KeyFullInformation.MaxValueNameLen = Node->MaxValueNameLen;
-            Info->KeyFullInformation.MaxValueDataLen = Node->MaxValueDataLen;
+    /* Now get the key value */
+    Result = CmpGetValueKeyFromCache(Kcb,
+                                     CellData,
+                                     Index,
+                                     &CachedValue,
+                                     &ValueData,
+                                     IndexIsCached,
+                                     &ValueIsCached,
+                                     &CellToRelease2);
+    if (Result == SearchNeedExclusiveLock)
+    {
+        /* Cleanup state */
+        ASSERT(CellToRelease2 == HCELL_NIL);
+        if (CellToRelease)
+        {
+            HvReleaseCell(Hive, CellToRelease);
+            CellToRelease = HCELL_NIL;
+        }
+        HvReleaseCell(Hive, Kcb->KeyCell);
 
-            /* Check if we have a class */
-            if (Node->ClassLength > 0)
-            {
-                /* We do, but we currently don't support this */
-                ASSERTMSG("Classes not supported\n", FALSE);
-            }
-            else
-            {
-                /* We don't have a class, so set offset to -1, not 0! */
-                Info->KeyNodeInformation.ClassOffset = 0xFFFFFFFF;
-            }
-            break;
+        /* Try with exclusive KCB lock */
+        CmpConvertKcbSharedToExclusive(Kcb);
+        goto DoAgain;
+    }
+    else if (Result != SearchSuccess)
+    {
+        /* Sanity check */
+        ASSERT(ValueData == NULL);
 
-        /* Any other class that got sent here is invalid! */
-        default:
+        /* Release the cells and fail */
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto Quickie;
+    }
+    
+    /* User data, need SEH */
+    _SEH2_TRY
+    {
+        /* Query the information requested */
+        Result = CmpQueryKeyValueData(Kcb,
+                                      CachedValue,
+                                      ValueData,
+                                      ValueIsCached,
+                                      KeyValueInformationClass,
+                                      KeyValueInformation,
+                                      Length,
+                                      ResultLength,
+                                      &Status);
+        if (Result == SearchNeedExclusiveLock)
+        {
+            /* Cleanup state */
+            if (CellToRelease2) HvReleaseCell(Hive, CellToRelease2);
+            HvReleaseCell(Hive, Kcb->KeyCell);
+            if (CellToRelease) HvReleaseCell(Hive, CellToRelease);
 
-            /* Set failure code */
-            Status = STATUS_INVALID_PARAMETER;
-            break;
+            /* Try with exclusive KCB lock */
+            CmpConvertKcbSharedToExclusive(Kcb);
+            goto DoAgain;
+        }
+    }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+    {
+        /* Get exception code */
+        Status = _SEH2_GetExceptionCode();
     }
+    _SEH2_END;
 
-    /* Return status */
+Quickie:
+    /* If we have a cell to release, do so */
+    if (CellToRelease != HCELL_NIL) HvReleaseCell(Hive, CellToRelease);
+
+    /* Release the parent cell */
+    HvReleaseCell(Hive, Kcb->KeyCell);
+
+    /* If we have a cell to release, do so */
+    if (CellToRelease2 != HCELL_NIL) HvReleaseCell(Hive, CellToRelease2);
+
+    /* Release locks */
+    CmpReleaseKcbLock(Kcb);
+    CmpUnlockRegistry();
     return Status;
 }
 
@@ -1087,6 +1315,7 @@ CmQueryKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
     NTSTATUS Status;
     PHHIVE Hive;
     PCM_KEY_NODE Parent;
+    HV_TRACK_CELL_REF CellReferences = {0};
 
     /* Acquire hive lock */
     CmpLockRegistry();
@@ -1094,16 +1323,6 @@ CmQueryKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
     /* Lock KCB shared */
     CmpAcquireKcbLockShared(Kcb);
 
-    /* Get the hive and parent */
-    Hive = Kcb->KeyHive;
-    Parent = (PCM_KEY_NODE)HvGetCell(Hive, Kcb->KeyCell);
-    if (!Parent)
-    {
-        /* Fail */
-        Status = STATUS_INSUFFICIENT_RESOURCES;
-        goto Quickie;
-    }
-    
     /* Don't touch deleted keys */
     if (Kcb->Delete)
     {
@@ -1120,13 +1339,27 @@ CmQueryKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
         case KeyBasicInformation:
         case KeyNodeInformation:
 
-            /* Call the internal API */
-            Status = CmpQueryKeyData(Hive,
-                                     Parent,
-                                     KeyInformationClass,
-                                     KeyInformation,
-                                     Length,
-                                     ResultLength);
+            /* Get the hive and parent */
+            Hive = Kcb->KeyHive;
+            Parent = (PCM_KEY_NODE)HvGetCell(Hive, Kcb->KeyCell);
+            ASSERT(Parent);
+            
+            /* Track cell references */
+            if (!HvTrackCellRef(&CellReferences, Hive, Kcb->KeyCell))
+            {
+                /* Not enough memory to track references */
+                Status = STATUS_INSUFFICIENT_RESOURCES;
+            }
+            else
+            {
+                /* Call the internal API */
+                Status = CmpQueryKeyData(Hive,
+                                         Parent,
+                                         KeyInformationClass,
+                                         KeyInformation,
+                                         Length,
+                                         ResultLength);
+            }
             break;
 
         /* Unsupported classes for now */
@@ -1149,6 +1382,9 @@ CmQueryKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
     }
 
 Quickie:
+    /* Release references */
+    HvReleaseFreeCellRefArray(&CellReferences);
+
     /* Release locks */
     CmpReleaseKcbLock(Kcb);
     CmpUnlockRegistry();
@@ -1168,6 +1404,7 @@ CmEnumerateKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
     PHHIVE Hive;
     PCM_KEY_NODE Parent, Child;
     HCELL_INDEX ChildCell;
+    HV_TRACK_CELL_REF CellReferences = {0};
 
     /* Acquire hive lock */
     CmpLockRegistry();
@@ -1179,20 +1416,14 @@ CmEnumerateKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
     if (Kcb->Delete)
     {
         /* Undo everything */
-        CmpReleaseKcbLock(Kcb);
-        CmpUnlockRegistry();
-        return STATUS_KEY_DELETED;
+        Status = STATUS_KEY_DELETED;
+        goto Quickie;
     }
 
     /* Get the hive and parent */
     Hive = Kcb->KeyHive;
     Parent = (PCM_KEY_NODE)HvGetCell(Hive, Kcb->KeyCell);
-    if (!Parent)
-    {
-        /* Fail */
-        Status = STATUS_INSUFFICIENT_RESOURCES;
-        goto Quickie;
-    }
+    ASSERT(Parent);
 
     /* Get the child cell */
     ChildCell = CmpFindSubKeyByNumber(Hive, Parent, Index);
@@ -1210,22 +1441,39 @@ CmEnumerateKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
 
     /* Now get the actual child node */
     Child = (PCM_KEY_NODE)HvGetCell(Hive, ChildCell);
-    if (!Child)
+    ASSERT(Child);
+    
+    /* Track references */
+    if (!HvTrackCellRef(&CellReferences, Hive, ChildCell))
     {
-        /* Fail */
+        /* Can't allocate memory for tracking */
         Status = STATUS_INSUFFICIENT_RESOURCES;
         goto Quickie;
     }
 
-    /* Query the data requested */
-    Status = CmpQueryKeyData(Hive,
-                             Child,
-                             KeyInformationClass,
-                             KeyInformation,
-                             Length,
-                             ResultLength);
+    /* Data can be user-mode, use SEH */
+    _SEH2_TRY
+    {
+        /* Query the data requested */
+        Status = CmpQueryKeyData(Hive,
+                                 Child,
+                                 KeyInformationClass,
+                                 KeyInformation,
+                                 Length,
+                                 ResultLength);
+    }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+    {
+        /* Fail with exception code */
+        Status = _SEH2_GetExceptionCode();
+        _SEH2_YIELD(goto Quickie);
+    }
+    _SEH2_END;
 
 Quickie:
+    /* Release references */
+    HvReleaseFreeCellRefArray(&CellReferences);
+
     /* Release locks */
     CmpReleaseKcbLock(Kcb);
     CmpUnlockRegistry();
@@ -1271,14 +1519,12 @@ CmDeleteKey(IN PCM_KEY_BODY KeyBody)
     Hive = Kcb->KeyHive;
     Cell = Kcb->KeyCell;
     
+    /* Lock flushes */
+    CmpLockHiveFlusherShared((PCMHIVE)Hive);
+    
     /* Get the key node */
     Node = (PCM_KEY_NODE)HvGetCell(Hive, Cell);
-    if (!Node)
-    {
-        /* Fail */
-        Status = STATUS_INSUFFICIENT_RESOURCES;
-        goto Quickie;
-    }
+    ASSERT(Node);
    
     /* Sanity check */
     ASSERT(Node->Flags == Kcb->Flags);
@@ -1287,11 +1533,17 @@ CmDeleteKey(IN PCM_KEY_BODY KeyBody)
     if (!(Node->SubKeyCounts[Stable] + Node->SubKeyCounts[Volatile]) &&
         !(Node->Flags & KEY_NO_DELETE))
     {
+        /* Send notification to registered callbacks */
+        CmpReportNotify(Kcb, Hive, Cell, REG_NOTIFY_CHANGE_NAME);
+        
         /* Get the parent and free the cell */
         ParentCell = Node->Parent;
         Status = CmpFreeKeyByCell(Hive, Cell, TRUE);
         if (NT_SUCCESS(Status))
         {
+            /* Flush any notifications */
+            CmpFlushNotifiesOnKeyBodyList(Kcb, FALSE);
+            
             /* Clean up information we have on the subkey */
             CmpCleanUpSubKeyInfo(Kcb->ParentKcb);
 
@@ -1327,10 +1579,12 @@ CmDeleteKey(IN PCM_KEY_BODY KeyBody)
         Status = STATUS_CANNOT_DELETE;
     }
     
-Quickie:
     /* Release the cell */
     HvReleaseCell(Hive, Cell);
-    
+
+    /* Release flush lock */
+    CmpUnlockHiveFlusher((PCMHIVE)Hive);
+
     /* Release the KCB locks */
 Quickie2:
     CmpReleaseTwoKcbLockByKey(Kcb->ConvKey, Kcb->ParentKcb->ConvKey);
@@ -1364,12 +1618,36 @@ CmFlushKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
     }
     else
     {
+        /* Don't touch the hive */
+        CmpLockHiveFlusherExclusive(CmHive);
+        ASSERT(CmHive->ViewLock);
+        KeAcquireGuardedMutex(CmHive->ViewLock);
+        CmHive->ViewLockOwner = KeGetCurrentThread();
+        
+        /* Will the hive shrink? */
+        if (HvHiveWillShrink(Hive))
+        {
+            /* I don't believe the current Hv does shrinking */
+            ASSERT(FALSE);
+        }
+        else
+        {
+            /* Now we can release views */
+            ASSERT(CmHive->ViewLock);
+            CMP_ASSERT_EXCLUSIVE_REGISTRY_LOCK_OR_LOADING(CmHive);
+            ASSERT(KeGetCurrentThread() == CmHive->ViewLockOwner);
+            KeReleaseGuardedMutex(CmHive->ViewLock);
+        }
+        
         /* Flush only this hive */
         if (!HvSyncHive(Hive))
         {
             /* Fail */
             Status = STATUS_REGISTRY_IO_FAILED;
         }
+        
+        /* Release the flush lock */
+        CmpUnlockHiveFlusher((PCMHIVE)Hive);
     }
 
     /* Return the status */
@@ -1387,8 +1665,9 @@ CmLoadKey(IN POBJECT_ATTRIBUTES TargetKey,
     SECURITY_CLIENT_CONTEXT ClientSecurityContext;
     HANDLE KeyHandle;
     BOOLEAN Allocate = TRUE;
-    PCMHIVE CmHive;
+    PCMHIVE CmHive, LoadedHive;
     NTSTATUS Status;
+    CM_PARSE_CONTEXT ParseContext;
     
     /* Check if we have a trust key */
     if (KeyBody)
@@ -1415,9 +1694,21 @@ CmLoadKey(IN POBJECT_ATTRIBUTES TargetKey,
     }
     
     /* Open the target key */
+#if 0
     Status = ZwOpenKey(&KeyHandle, KEY_READ, TargetKey);
+#else
+    RtlZeroMemory(&ParseContext, sizeof(ParseContext));
+    ParseContext.CreateOperation = FALSE;
+    Status = ObOpenObjectByName(TargetKey,
+                                CmpKeyObjectType,
+                                KernelMode,
+                                NULL,
+                                KEY_READ,
+                                &ParseContext,
+                                &KeyHandle);
+#endif
     if (!NT_SUCCESS(Status)) KeyHandle = NULL;
-    
+
     /* Open the hive */
     Status = CmpCmdHiveOpen(SourceFile,
                             &ClientSecurityContext,
@@ -1437,21 +1728,29 @@ CmLoadKey(IN POBJECT_ATTRIBUTES TargetKey,
             /* Lock the registry */
             CmpLockRegistryExclusive();
             
-            /* FIXME: Check if we are already loaded */
-            
+            /* Check if we are already loaded */
+            if (CmpIsHiveAlreadyLoaded(KeyHandle, SourceFile, &LoadedHive))
+            {
+                /* That's okay then */
+                ASSERT(LoadedHive);
+                Status = STATUS_SUCCESS;
+            }
+
             /* Release the registry */
             CmpUnlockRegistry();
         }
         
         /* Close the key handle if we had one */
         if (KeyHandle) ZwClose(KeyHandle);
-        DPRINT1("Failed: %lx\n", Status);
         return Status;
     }
     
     /* Lock the registry shared */
     CmpLockRegistry();
     
+    /* Lock loading */
+    ExAcquirePushLockExclusive(&CmpLoadHiveLock);
+    
     /* Lock the hive to this thread */
     CmHive->Hive.HiveFlags |= HIVE_IS_UNLOADING;
     CmHive->CreatorOwner = KeGetCurrentThread();
@@ -1467,23 +1766,37 @@ CmLoadKey(IN POBJECT_ATTRIBUTES TargetKey,
                                  TargetKey->SecurityDescriptor);
     if (NT_SUCCESS(Status))
     {
-        /* FIXME: Add to HiveList key */
+        /* Add to HiveList key */
+        CmpAddToHiveFileList(CmHive);
         
         /* Sync the hive if necessary */
         if (Allocate)
         {
-            /* Sync it */
+            /* Sync it under the flusher lock */
+            CmpLockHiveFlusherExclusive(CmHive);
             HvSyncHive(&CmHive->Hive);
+            CmpUnlockHiveFlusher(CmHive);
         }
         
         /* Release the hive */
         CmHive->Hive.HiveFlags &= ~HIVE_IS_UNLOADING;
         CmHive->CreatorOwner = NULL;
+        
+        /* Allow loads */
+        ExReleasePushLock(&CmpLoadHiveLock);
     }
     else
     {
         /* FIXME: TODO */
-        
+        ASSERT(FALSE);
+    }
+    
+    /* Is this first profile load? */
+    if (!(CmpProfileLoaded) && !(CmpWasSetupBoot))
+    {
+        /* User is now logged on, set quotas */
+        CmpProfileLoaded = TRUE;
+        CmpSetGlobalQuotaAllowed();
     }
     
     /* Unlock the registry */
@@ -1502,3 +1815,86 @@ CmUnloadKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
     UNIMPLEMENTED;
     return STATUS_NOT_IMPLEMENTED;
 }
+
+ULONG
+NTAPI
+CmCountOpenSubKeys(IN PCM_KEY_CONTROL_BLOCK RootKcb,
+                   IN BOOLEAN RemoveEmptyCacheEntries)
+{
+    PCM_KEY_HASH Entry;
+    PCM_KEY_CONTROL_BLOCK CachedKcb;
+    PCM_KEY_CONTROL_BLOCK ParentKcb;
+    USHORT ParentKeyCount;
+    USHORT j;
+    ULONG i;
+    ULONG SubKeys = 0;
+
+    DPRINT("CmCountOpenSubKeys() called\n");
+
+    /* The root key is the only referenced key. There are no refereced sub keys. */
+    if (RootKcb->RefCount == 1)
+    {
+        DPRINT("open sub keys: 0\n");
+        return 0;
+    }
+
+    /* Enumerate all hash lists */
+    for (i = 0; i < CmpHashTableSize; i++)
+    {
+        /* Get the first cache entry */
+        Entry = CmpCacheTable[i].Entry;
+
+        /* Enumerate all cache entries */
+        while (Entry)
+        {
+            /* Get the KCB of the current cache entry */
+            CachedKcb = CONTAINING_RECORD(Entry, CM_KEY_CONTROL_BLOCK, KeyHash);
+
+            /* Check keys only that are subkeys to our root key */
+            if (CachedKcb->TotalLevels > RootKcb->TotalLevels)
+            {
+                /* Calculate the number of parent keys to the root key */
+                ParentKeyCount = CachedKcb->TotalLevels - RootKcb->TotalLevels;
+
+                /* Find a parent key that could be the root key */
+                ParentKcb = CachedKcb;
+                for (j = 0; j < ParentKeyCount; j++)
+                {
+                    ParentKcb = ParentKcb->ParentKcb;
+                }
+
+                /* Check whether the parent is the root key */
+                if (ParentKcb == RootKcb)
+                {
+                    DPRINT("Found a sub key \n");
+                    DPRINT("RefCount = %u\n", CachedKcb->RefCount);
+
+                    if (CachedKcb->RefCount > 0)
+                    {
+                        /* Count the current hash entry if it is in use */
+                        SubKeys++;
+                    }
+                    else if ((CachedKcb->RefCount == 0) && (RemoveEmptyCacheEntries == TRUE))
+                    {
+                        /* Remove the current key from the delayed close list */
+                        CmpRemoveFromDelayedClose(CachedKcb);
+
+                        /* Remove the current cache entry */
+                        CmpCleanUpKcbCacheWithLock(CachedKcb, TRUE);
+
+                        /* Restart, because the hash list has changed */
+                        Entry = CmpCacheTable[i].Entry;
+                        continue;
+                    }
+                }
+            }
+
+            /* Get the next cache entry */
+            Entry = Entry->NextHash;
+        }
+    }
+
+    DPRINT("open sub keys: %u\n", SubKeys);
+
+    return SubKeys;
+}