[NTOS:CM] Implement more support for force-unloading registry hives.
[reactos.git] / ntoskrnl / config / cmapi.c
index a274206..84e0f11 100644 (file)
@@ -5,6 +5,7 @@
  * PURPOSE:         Configuration Manager - Internal Registry APIs
  * PROGRAMMERS:     Alex Ionescu (alex.ionescu@reactos.org)
  *                  Eric Kohl
+ *                  Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
  */
 
 /* INCLUDES ******************************************************************/
@@ -61,7 +62,7 @@ CmpIsHiveAlreadyLoaded(IN HANDLE KeyHandle,
         /* Same file found */
         Loaded = TRUE;
         *CmHive = Hive;
-        
+
         /* If the hive is frozen, not sure what to do */
         if (Hive->Frozen)
         {
@@ -102,7 +103,7 @@ CmpDoFlushAll(IN BOOLEAN ForceFlush)
         {
             /* Acquire the flusher lock */
             CmpLockHiveFlusherExclusive(Hive);
-            
+
             /* Check for illegal state */
             if ((ForceFlush) && (Hive->UseCount))
             {
@@ -111,7 +112,7 @@ CmpDoFlushAll(IN BOOLEAN ForceFlush)
                 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)))
             {
@@ -242,7 +243,7 @@ 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;
 
@@ -280,7 +281,7 @@ CmpSetValueKeyExisting(IN PHHIVE Hive,
     PCELL_DATA CellData;
     ULONG Length;
     BOOLEAN WasSmall, IsSmall;
-    
+
     /* Registry writes must be blocked */
     CMP_ASSERT_FLUSH_LOCK(Hive);
 
@@ -316,7 +317,7 @@ CmpSetValueKeyExisting(IN PHHIVE Hive,
         Value->Type = Type;
         return STATUS_SUCCESS;
     }
-    
+
     /* We have a normal key. Was the old cell also normal and had data? */
     if (!(WasSmall) && (Length > 0))
     {
@@ -380,9 +381,10 @@ CmpQueryKeyData(IN PHHIVE Hive,
                 IN OUT PULONG ResultLength)
 {
     NTSTATUS Status;
-    ULONG Size, SizeLeft, MinimumSize;
+    ULONG Size, SizeLeft, MinimumSize, Offset;
     PKEY_INFORMATION Info = (PKEY_INFORMATION)KeyInformation;
     USHORT NameLength;
+    PVOID ClassData;
 
     /* Check if the value is compressed */
     if (Node->Flags & KEY_COMP_NAME)
@@ -516,8 +518,36 @@ CmpQueryKeyData(IN PHHIVE Hive,
             /* 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);
+                /* Set the class offset */
+                Offset = FIELD_OFFSET(KEY_NODE_INFORMATION, Name) + NameLength;
+                Offset = ALIGN_UP_BY(Offset, sizeof(ULONG));
+                Info->KeyNodeInformation.ClassOffset = Offset;
+
+                /* Get the class data */
+                ClassData = HvGetCell(Hive, Node->Class);
+                if (ClassData == NULL)
+                {
+                    Status = STATUS_INSUFFICIENT_RESOURCES;
+                    break;
+                }
+
+                /* Check if we can copy anything */
+                if (Length > Offset)
+                {
+                    /* Copy the class data */
+                    RtlCopyMemory((PUCHAR)Info + Offset,
+                                  ClassData,
+                                  min(Node->ClassLength, Length - Offset));
+                }
+
+                /* Check if the buffer was large enough */
+                if (Length < Offset + Node->ClassLength)
+                {
+                    Status = STATUS_BUFFER_OVERFLOW;
+                }
+
+                /* Release the class cell */
+                HvReleaseCell(Hive, Node->Class);
             }
             else
             {
@@ -563,13 +593,37 @@ CmpQueryKeyData(IN PHHIVE Hive,
             /* 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);
+                /* Set the class offset */
+                Offset = FIELD_OFFSET(KEY_FULL_INFORMATION, Class);
+                Info->KeyFullInformation.ClassOffset = Offset;
+
+                /* Get the class data */
+                ClassData = HvGetCell(Hive, Node->Class);
+                if (ClassData == NULL)
+                {
+                    Status = STATUS_INSUFFICIENT_RESOURCES;
+                    break;
+                }
+
+                /* Copy the class data */
+                ASSERT(Length >= Offset);
+                RtlCopyMemory(Info->KeyFullInformation.Class,
+                              ClassData,
+                              min(Node->ClassLength, Length - Offset));
+
+                /* Check if the buffer was large enough */
+                if (Length < Offset + Node->ClassLength)
+                {
+                    Status = STATUS_BUFFER_OVERFLOW;
+                }
+
+                /* Release the class cell */
+                HvReleaseCell(Hive, Node->Class);
             }
             else
             {
                 /* We don't have a class, so set offset to -1, not 0! */
-                Info->KeyNodeInformation.ClassOffset = 0xFFFFFFFF;
+                Info->KeyFullInformation.ClassOffset = 0xFFFFFFFF;
             }
             break;
 
@@ -607,10 +661,10 @@ CmSetValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
     /* 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)
@@ -619,7 +673,7 @@ DoAgain:
         Status = STATUS_KEY_DELETED;
         goto Quickie;
     }
-    
+
     /* Don't let anyone mess with symlinks */
     if ((Kcb->Flags & KEY_SYM_LINK) &&
         ((Type != REG_LINK) ||
@@ -660,10 +714,10 @@ DoAgain:
             /* 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);
@@ -680,7 +734,7 @@ DoAgain:
         Parent = (PCM_KEY_NODE)HvGetCell(Hive, Cell);
         ASSERT(Parent);
         ParentCell = Cell;
-        
+
         /* Prepare to scan the key node */
         Count = Parent->ValueList.Count;
         Found = FALSE;
@@ -708,7 +762,7 @@ DoAgain:
                     HvReleaseCell(Hive, ChildCell);
                     ChildCell = HCELL_NIL;
                 }
-                
+
                 /* Get its value */
                 Value = (PCM_KEY_VALUE)HvGetCell(Hive, CurrentChild);
                 if (!Value)
@@ -729,13 +783,13 @@ DoAgain:
             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))
     {
@@ -804,7 +858,7 @@ DoAgain:
             Parent->MaxValueNameLen = ValueName->Length;
             Kcb->KcbMaxValueNameLen = ValueName->Length;
         }
-    
+
         /* Check if the maximum data length changed */
         ASSERT(Parent->MaxValueDataLen == Kcb->KcbMaxValueDataLen);
         if (Parent->MaxValueDataLen < DataLength)
@@ -813,11 +867,11 @@ DoAgain:
             Parent->MaxValueDataLen = DataLength;
             Kcb->KcbMaxValueDataLen = Parent->MaxValueDataLen;
         }
-        
+
         /* Save the write time */
         KeQuerySystemTime(&Parent->LastWriteTime);
         Kcb->KcbLastWriteTime = Parent->LastWriteTime;
-        
+
         /* Check if the cell is cached */
         if ((Found) && (CMP_IS_CELL_CACHED(Kcb->ValueCache.ValueList)))
         {
@@ -832,19 +886,19 @@ DoAgain:
             /* Sanity checks */
             ASSERT(!(CMP_IS_CELL_CACHED(Kcb->ValueCache.ValueList)));
             ASSERT(!(Kcb->ExtFlags & CM_KCB_SYM_LINK_FOUND));
-            
+
             /* Set the value cache */
             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);
@@ -873,10 +927,10 @@ CmDeleteValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
 
     /* Acquire hive lock */
     CmpLockRegistry();
-    
+
     /* Lock KCB exclusively */
     CmpAcquireKcbLockExclusive(Kcb);
-    
+
     /* Don't touch deleted keys */
     if (Kcb->Delete)
     {
@@ -889,7 +943,7 @@ 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);
 
@@ -928,7 +982,7 @@ CmDeleteValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
         }
 
         /* Get the key value */
-        Value = (PCM_KEY_VALUE)HvGetCell(Hive,ChildCell);
+        Value = (PCM_KEY_VALUE)HvGetCell(Hive, ChildCell);
         ASSERT(Value);
 
         /* Mark it and all related data as dirty */
@@ -939,7 +993,7 @@ CmDeleteValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
             goto Quickie;
         }
 
-        /* Ssanity checks */
+        /* Sanity checks */
         ASSERT(HvIsCellDirty(Hive, Parent->ValueList.List));
         ASSERT(HvIsCellDirty(Hive, ChildCell));
 
@@ -978,18 +1032,18 @@ CmDeleteValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
             Kcb->KcbMaxValueNameLen = 0;
             Kcb->KcbMaxValueDataLen = 0;
         }
-        
+
         /* Cleanup the value cache */
         CmpCleanUpKcbValueCache(Kcb);
-        
+
         /* Sanity checks */
         ASSERT(!(CMP_IS_CELL_CACHED(Kcb->ValueCache.ValueList)));
         ASSERT(!(Kcb->ExtFlags & CM_KCB_SYM_LINK_FOUND));
-        
+
         /* 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);
 
@@ -1037,10 +1091,10 @@ CmQueryValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
 
     /* Acquire hive lock */
     CmpLockRegistry();
-    
+
     /* Lock the KCB shared */
     CmpAcquireKcbLockShared(Kcb);
-    
+
     /* Don't touch deleted keys */
 DoAgain:
     if (Kcb->Delete)
@@ -1050,7 +1104,7 @@ DoAgain:
         CmpUnlockRegistry();
         return STATUS_KEY_DELETED;
     }
-    
+
     /* We don't deal with this yet */
     if (Kcb->ExtFlags & CM_KCB_SYM_LINK_FOUND)
     {
@@ -1074,12 +1128,12 @@ DoAgain:
         /* 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 */
@@ -1106,7 +1160,7 @@ DoAgain:
                     HvReleaseCell(Hive, CellToRelease);
                     CellToRelease = HCELL_NIL;
                 }
-                
+
                 /* Try with exclusive KCB lock */
                 CmpConvertKcbSharedToExclusive(Kcb);
                 _SEH2_YIELD(goto DoAgain);
@@ -1155,7 +1209,7 @@ CmEnumerateValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
 
     /* Acquire hive lock */
     CmpLockRegistry();
-    
+
     /* Lock the KCB shared */
     CmpAcquireKcbLockShared(Kcb);
 
@@ -1189,7 +1243,7 @@ DoAgain:
         Status = STATUS_NO_MORE_ENTRIES;
         goto Quickie;
     }
-    
+
     /* We don't deal with this yet */
     if (Kcb->ExtFlags & CM_KCB_SYM_LINK_FOUND)
     {
@@ -1207,7 +1261,7 @@ DoAgain:
         /* Check if we need an exclusive lock */
         ASSERT(CellToRelease == HCELL_NIL);
         HvReleaseCell(Hive, Kcb->KeyCell);
-        
+
         /* Try with exclusive KCB lock */
         CmpConvertKcbSharedToExclusive(Kcb);
         goto DoAgain;
@@ -1255,7 +1309,7 @@ DoAgain:
         Status = STATUS_INSUFFICIENT_RESOURCES;
         goto Quickie;
     }
-    
+
     /* User data, need SEH */
     _SEH2_TRY
     {
@@ -1304,13 +1358,265 @@ Quickie:
     return Status;
 }
 
+static
+NTSTATUS
+CmpQueryKeyDataFromCache(
+    _In_ PCM_KEY_CONTROL_BLOCK Kcb,
+    _Out_ PKEY_CACHED_INFORMATION KeyCachedInfo,
+    _In_ ULONG Length,
+    _Out_ PULONG ResultLength)
+{
+    PCM_KEY_NODE Node;
+    PHHIVE KeyHive;
+    HCELL_INDEX KeyCell;
+    USHORT NameLength;
+    PAGED_CODE();
+
+    /* Get the hive and cell index */
+    KeyHive = Kcb->KeyHash.KeyHive;
+    KeyCell = Kcb->KeyHash.KeyCell;
+
+#if DBG
+    /* Get the cell node */
+    Node = HvGetCell(KeyHive, KeyCell);
+    if (Node != NULL)
+    {
+        ULONG SubKeyCount;
+        ASSERT(Node->ValueList.Count == Kcb->ValueCache.Count);
+
+        if (!(Kcb->ExtFlags & CM_KCB_INVALID_CACHED_INFO))
+        {
+            SubKeyCount = Node->SubKeyCounts[0] + Node->SubKeyCounts[1];
+            if (Kcb->ExtFlags & CM_KCB_NO_SUBKEY)
+            {
+                ASSERT(SubKeyCount == 0);
+            }
+            else if (Kcb->ExtFlags & CM_KCB_SUBKEY_ONE)
+            {
+                ASSERT(SubKeyCount == 1);
+            }
+            else if (Kcb->ExtFlags & CM_KCB_SUBKEY_HINT)
+            {
+                ASSERT(SubKeyCount == Kcb->IndexHint->Count);
+            }
+            else
+            {
+                ASSERT(SubKeyCount == Kcb->SubKeyCount);
+            }
+        }
+
+        ASSERT(Node->LastWriteTime.QuadPart == Kcb->KcbLastWriteTime.QuadPart);
+        ASSERT(Node->MaxNameLen == Kcb->KcbMaxNameLen);
+        ASSERT(Node->MaxValueNameLen == Kcb->KcbMaxValueNameLen);
+        ASSERT(Node->MaxValueDataLen == Kcb->KcbMaxValueDataLen);
+
+        /* Release the cell */
+        HvReleaseCell(KeyHive, KeyCell);
+    }
+#endif // DBG
+
+    /* Make sure we have a name block */
+    if (Kcb->NameBlock == NULL)
+    {
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    /* Check for compressed name */
+    if (Kcb->NameBlock->Compressed)
+    {
+        /* Calculate the name size */
+        NameLength = CmpCompressedNameSize(Kcb->NameBlock->NameHash.Name,
+                                           Kcb->NameBlock->NameHash.NameLength);
+    }
+    else
+    {
+        /* Use the stored name size */
+        NameLength = Kcb->NameBlock->NameHash.NameLength;
+    }
+
+    /* Validate buffer length (we do not copy the name!) */
+    *ResultLength = sizeof(*KeyCachedInfo);
+    if (Length < *ResultLength)
+    {
+        return STATUS_BUFFER_TOO_SMALL;
+    }
+
+    /* Fill the structure */
+    KeyCachedInfo->LastWriteTime = Kcb->KcbLastWriteTime;
+    KeyCachedInfo->TitleIndex = 0;
+    KeyCachedInfo->NameLength = NameLength;
+    KeyCachedInfo->Values = Kcb->ValueCache.Count;
+    KeyCachedInfo->MaxNameLen = Kcb->KcbMaxNameLen;
+    KeyCachedInfo->MaxValueNameLen = Kcb->KcbMaxValueNameLen;
+    KeyCachedInfo->MaxValueDataLen = Kcb->KcbMaxValueDataLen;
+
+    /* Check the ExtFlags for what we have */
+    if (Kcb->ExtFlags & CM_KCB_INVALID_CACHED_INFO)
+    {
+        /* Cache is not valid, do a full lookup */
+        DPRINT1("Kcb cache incoherency detected, kcb = %p\n", Kcb);
+
+        /* Get the cell node */
+        Node = HvGetCell(KeyHive, KeyCell);
+        if (Node == NULL)
+        {
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+
+        /* Calculate number of subkeys */
+        KeyCachedInfo->SubKeys = Node->SubKeyCounts[0] + Node->SubKeyCounts[1];
+
+        /* Release the cell */
+        HvReleaseCell(KeyHive, KeyCell);
+    }
+    else if (Kcb->ExtFlags & CM_KCB_NO_SUBKEY)
+    {
+        /* There are no subkeys */
+        KeyCachedInfo->SubKeys = 0;
+    }
+    else if (Kcb->ExtFlags & CM_KCB_SUBKEY_ONE)
+    {
+        /* There is exactly one subley */
+        KeyCachedInfo->SubKeys = 1;
+    }
+    else if (Kcb->ExtFlags & CM_KCB_SUBKEY_HINT)
+    {
+        /* Get the number of subkeys from the subkey hint */
+        KeyCachedInfo->SubKeys = Kcb->IndexHint->Count;
+    }
+    else
+    {
+        /* No subkey hint, use the key count field */
+        KeyCachedInfo->SubKeys = Kcb->SubKeyCount;
+    }
+
+    return STATUS_SUCCESS;
+}
+
+static
+NTSTATUS
+CmpQueryFlagsInformation(
+    _In_ PCM_KEY_CONTROL_BLOCK Kcb,
+    _Out_ PKEY_USER_FLAGS_INFORMATION KeyFlagsInfo,
+    _In_ ULONG Length,
+    _In_ PULONG ResultLength)
+{
+    /* Validate the buffer size */
+    *ResultLength = sizeof(*KeyFlagsInfo);
+    if (Length < *ResultLength)
+    {
+        return STATUS_BUFFER_TOO_SMALL;
+    }
+
+    /* Copy the user flags */
+    KeyFlagsInfo->UserFlags = Kcb->KcbUserFlags;
+
+    return STATUS_SUCCESS;
+}
+
+static
+NTSTATUS
+CmpQueryNameInformation(
+    _In_ PCM_KEY_CONTROL_BLOCK Kcb,
+    _Out_opt_ PKEY_NAME_INFORMATION KeyNameInfo,
+    _In_ ULONG Length,
+    _Out_ PULONG ResultLength)
+{
+    ULONG NeededLength;
+    PCM_KEY_CONTROL_BLOCK CurrentKcb;
+
+    NeededLength = 0;
+    CurrentKcb = Kcb;
+
+    /* Count the needed buffer size */
+    while (CurrentKcb)
+    {
+        if (CurrentKcb->NameBlock->Compressed)
+            NeededLength += CmpCompressedNameSize(CurrentKcb->NameBlock->Name, CurrentKcb->NameBlock->NameLength);
+        else
+            NeededLength += CurrentKcb->NameBlock->NameLength;
+
+        NeededLength += sizeof(OBJ_NAME_PATH_SEPARATOR);
+
+        CurrentKcb = CurrentKcb->ParentKcb;
+    }
+
+    _SEH2_TRY
+    {
+        *ResultLength = FIELD_OFFSET(KEY_NAME_INFORMATION, Name) + NeededLength;
+        if (Length < RTL_SIZEOF_THROUGH_FIELD(KEY_NAME_INFORMATION, NameLength))
+            _SEH2_YIELD(return STATUS_BUFFER_TOO_SMALL);
+        if (Length < *ResultLength)
+        {
+            KeyNameInfo->NameLength = NeededLength;
+            _SEH2_YIELD(return STATUS_BUFFER_OVERFLOW);
+        }
+    }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+    {
+        _SEH2_YIELD(return _SEH2_GetExceptionCode());
+    }
+    _SEH2_END;
+
+    /* Do the real copy */
+    KeyNameInfo->NameLength = 0;
+    CurrentKcb = Kcb;
+
+    _SEH2_TRY
+    {
+        while (CurrentKcb)
+        {
+            ULONG NameLength;
+
+            if (CurrentKcb->NameBlock->Compressed)
+            {
+                NameLength = CmpCompressedNameSize(CurrentKcb->NameBlock->Name, CurrentKcb->NameBlock->NameLength);
+                /* Copy the compressed name */
+                CmpCopyCompressedName(&KeyNameInfo->Name[(NeededLength - NameLength)/sizeof(WCHAR)],
+                                      NameLength,
+                                      CurrentKcb->NameBlock->Name,
+                                      CurrentKcb->NameBlock->NameLength);
+            }
+            else
+            {
+                NameLength = CurrentKcb->NameBlock->NameLength;
+                /* Otherwise, copy the raw name */
+                RtlCopyMemory(&KeyNameInfo->Name[(NeededLength - NameLength)/sizeof(WCHAR)],
+                              CurrentKcb->NameBlock->Name,
+                              NameLength);
+            }
+
+            NeededLength -= NameLength;
+            NeededLength -= sizeof(OBJ_NAME_PATH_SEPARATOR);
+            /* Add path separator */
+            KeyNameInfo->Name[NeededLength/sizeof(WCHAR)] = OBJ_NAME_PATH_SEPARATOR;
+            KeyNameInfo->NameLength += NameLength + sizeof(OBJ_NAME_PATH_SEPARATOR);
+
+            CurrentKcb = CurrentKcb->ParentKcb;
+        }
+    }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+    {
+        _SEH2_YIELD(return _SEH2_GetExceptionCode());
+    }
+    _SEH2_END;
+
+    /* Make sure we copied everything */
+    ASSERT(NeededLength == 0);
+    ASSERT(KeyNameInfo->Name[0] == OBJ_NAME_PATH_SEPARATOR);
+
+    /* We're done */
+    return STATUS_SUCCESS;
+}
+
+
 NTSTATUS
 NTAPI
-CmQueryKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
-           IN KEY_INFORMATION_CLASS KeyInformationClass,
-           IN PVOID KeyInformation,
-           IN ULONG Length,
-           IN PULONG ResultLength)
+CmQueryKey(_In_ PCM_KEY_CONTROL_BLOCK Kcb,
+           _In_ KEY_INFORMATION_CLASS KeyInformationClass,
+           _Out_opt_ PVOID KeyInformation,
+           _In_ ULONG Length,
+           _Out_ PULONG ResultLength)
 {
     NTSTATUS Status;
     PHHIVE Hive;
@@ -1319,7 +1625,7 @@ CmQueryKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
 
     /* Acquire hive lock */
     CmpLockRegistry();
-    
+
     /* Lock KCB shared */
     CmpAcquireKcbLockShared(Kcb);
 
@@ -1331,55 +1637,88 @@ CmQueryKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
         goto Quickie;
     }
 
-    /* Check what class we got */
-    switch (KeyInformationClass)
+    /* Data can be user-mode, use SEH */
+    _SEH2_TRY
     {
-        /* Typical information */
-        case KeyFullInformation:
-        case KeyBasicInformation:
-        case KeyNodeInformation:
-
-            /* 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))
+        /* Check what class we got */
+        switch (KeyInformationClass)
+        {
+            /* Typical information */
+            case KeyFullInformation:
+            case KeyBasicInformation:
+            case KeyNodeInformation:
             {
-                /* Not enough memory to track references */
-                Status = STATUS_INSUFFICIENT_RESOURCES;
+                /* 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;
             }
-            else
+
+            case KeyCachedInformation:
             {
                 /* Call the internal API */
-                Status = CmpQueryKeyData(Hive,
-                                         Parent,
-                                         KeyInformationClass,
-                                         KeyInformation,
-                                         Length,
-                                         ResultLength);
+                Status = CmpQueryKeyDataFromCache(Kcb,
+                                                  KeyInformation,
+                                                  Length,
+                                                  ResultLength);
+                break;
             }
-            break;
-
-        /* Unsupported classes for now */
-        case KeyNameInformation:
-        case KeyCachedInformation:
-        case KeyFlagsInformation:
 
-            /* Print message and fail */
-            DPRINT1("Unsupported class: %d!\n", KeyInformationClass);
-            Status = STATUS_NOT_IMPLEMENTED;
-            break;
+            case KeyFlagsInformation:
+            {
+                /* Call the internal API */
+                Status = CmpQueryFlagsInformation(Kcb,
+                                                  KeyInformation,
+                                                  Length,
+                                                  ResultLength);
+                break;
+            }
 
-        /* Illegal classes */
-        default:
+            case KeyNameInformation:
+            {
+                /* Call the internal API */
+                Status = CmpQueryNameInformation(Kcb,
+                                                 KeyInformation,
+                                                 Length,
+                                                 ResultLength);
+                break;
+            }
 
-            /* Print message and fail */
-            DPRINT1("Unsupported class: %d!\n", KeyInformationClass);
-            Status = STATUS_INVALID_INFO_CLASS;
-            break;
+            /* Illegal classes */
+            default:
+            {
+                /* Print message and fail */
+                DPRINT1("Unsupported class: %d!\n", KeyInformationClass);
+                Status = STATUS_INVALID_INFO_CLASS;
+                break;
+            }
+        }
+    }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+    {
+        /* Fail with exception code */
+        Status = _SEH2_GetExceptionCode();
+        _SEH2_YIELD(goto Quickie);
     }
+    _SEH2_END;
 
 Quickie:
     /* Release references */
@@ -1408,10 +1747,10 @@ CmEnumerateKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
 
     /* Acquire hive lock */
     CmpLockRegistry();
-    
+
     /* Lock the KCB shared */
     CmpAcquireKcbLockShared(Kcb);
-    
+
     /* Don't touch deleted keys */
     if (Kcb->Delete)
     {
@@ -1442,7 +1781,7 @@ CmEnumerateKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
     /* Now get the actual child node */
     Child = (PCM_KEY_NODE)HvGetCell(Hive, ChildCell);
     ASSERT(Child);
-    
+
     /* Track references */
     if (!HvTrackCellRef(&CellReferences, Hive, ChildCell))
     {
@@ -1492,10 +1831,10 @@ CmDeleteKey(IN PCM_KEY_BODY KeyBody)
 
     /* Acquire hive lock */
     CmpLockRegistry();
-    
+
     /* Get the kcb */
     Kcb = KeyBody->KeyControlBlock;
-    
+
     /* Don't allow deleting the root */
     if (!Kcb->ParentKcb)
     {
@@ -1503,29 +1842,29 @@ CmDeleteKey(IN PCM_KEY_BODY KeyBody)
         CmpUnlockRegistry();
         return STATUS_CANNOT_DELETE;
     }
-    
+
     /* Lock parent and child */
     CmpAcquireTwoKcbLocksExclusiveByKey(Kcb->ConvKey, Kcb->ParentKcb->ConvKey);
-    
+
     /* Check if we're already being deleted */
     if (Kcb->Delete)
     {
         /* Don't do it twice */
         Status = STATUS_SUCCESS;
-        goto Quickie2;
+        goto Quickie;
     }
 
     /* Get the hive and node */
     Hive = Kcb->KeyHive;
     Cell = Kcb->KeyCell;
-    
+
     /* Lock flushes */
     CmpLockHiveFlusherShared((PCMHIVE)Hive);
-    
+
     /* Get the key node */
     Node = (PCM_KEY_NODE)HvGetCell(Hive, Cell);
     ASSERT(Node);
-   
+
     /* Sanity check */
     ASSERT(Node->Flags == Kcb->Flags);
 
@@ -1535,7 +1874,7 @@ CmDeleteKey(IN PCM_KEY_BODY KeyBody)
     {
         /* 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);
@@ -1543,7 +1882,7 @@ CmDeleteKey(IN PCM_KEY_BODY KeyBody)
         {
             /* Flush any notifications */
             CmpFlushNotifiesOnKeyBodyList(Kcb, FALSE);
-            
+
             /* Clean up information we have on the subkey */
             CmpCleanUpSubKeyInfo(Kcb->ParentKcb);
 
@@ -1553,7 +1892,7 @@ CmDeleteKey(IN PCM_KEY_BODY KeyBody)
             {
                 /* Update the maximum name length */
                 Kcb->ParentKcb->KcbMaxNameLen = (USHORT)Parent->MaxNameLen;
-                
+
                 /* Make sure we're dirty */
                 ASSERT(HvIsCellDirty(Hive, ParentCell));
 
@@ -1564,7 +1903,7 @@ CmDeleteKey(IN PCM_KEY_BODY KeyBody)
                 /* Release the cell */
                 HvReleaseCell(Hive, ParentCell);
             }
-            
+
             /* Set the KCB in delete mode and remove it */
             Kcb->Delete = TRUE;
             CmpRemoveKeyControlBlock(Kcb);
@@ -1578,7 +1917,7 @@ CmDeleteKey(IN PCM_KEY_BODY KeyBody)
         /* Fail */
         Status = STATUS_CANNOT_DELETE;
     }
-    
+
     /* Release the cell */
     HvReleaseCell(Hive, Cell);
 
@@ -1586,7 +1925,7 @@ CmDeleteKey(IN PCM_KEY_BODY KeyBody)
     CmpUnlockHiveFlusher((PCMHIVE)Hive);
 
     /* Release the KCB locks */
-Quickie2:
+Quickie:
     CmpReleaseTwoKcbLockByKey(Kcb->ConvKey, Kcb->ParentKcb->ConvKey);
 
     /* Release hive lock */
@@ -1605,11 +1944,11 @@ CmFlushKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
 
     /* Ignore flushes until we're ready */
     if (CmpNoWrite) return STATUS_SUCCESS;
-    
+
     /* Get the hives */
     Hive = Kcb->KeyHive;
     CmHive = (PCMHIVE)Hive;
-          
+
     /* Check if this is the master hive */
     if (CmHive == CmiVolatileHive)
     {
@@ -1623,7 +1962,7 @@ CmFlushKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
         ASSERT(CmHive->ViewLock);
         KeAcquireGuardedMutex(CmHive->ViewLock);
         CmHive->ViewLockOwner = KeGetCurrentThread();
-        
+
         /* Will the hive shrink? */
         if (HvHiveWillShrink(Hive))
         {
@@ -1638,16 +1977,16 @@ CmFlushKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
             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);
+        CmpUnlockHiveFlusher(CmHive);
     }
 
     /* Return the status */
@@ -1668,15 +2007,14 @@ CmLoadKey(IN POBJECT_ATTRIBUTES TargetKey,
     PCMHIVE CmHive, LoadedHive;
     NTSTATUS Status;
     CM_PARSE_CONTEXT ParseContext;
-    
+
     /* Check if we have a trust key */
     if (KeyBody)
     {
         /* Fail */
-        DPRINT1("Trusted classes not yet supported\n");
-        return STATUS_NOT_IMPLEMENTED;
+        DPRINT("Trusted classes not yet supported\n");
     }
-    
+
     /* Build a service QoS for a security context */
     ServiceQos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE);
     ServiceQos.ImpersonationLevel = SecurityImpersonation;
@@ -1692,11 +2030,8 @@ CmLoadKey(IN POBJECT_ATTRIBUTES TargetKey,
         DPRINT1("Security context failed\n");
         return Status;
     }
-    
+
     /* Open the target key */
-#if 0
-    Status = ZwOpenKey(&KeyHandle, KEY_READ, TargetKey);
-#else
     RtlZeroMemory(&ParseContext, sizeof(ParseContext));
     ParseContext.CreateOperation = FALSE;
     Status = ObOpenObjectByName(TargetKey,
@@ -1706,7 +2041,6 @@ CmLoadKey(IN POBJECT_ATTRIBUTES TargetKey,
                                 KEY_READ,
                                 &ParseContext,
                                 &KeyHandle);
-#endif
     if (!NT_SUCCESS(Status)) KeyHandle = NULL;
 
     /* Open the hive */
@@ -1727,7 +2061,7 @@ CmLoadKey(IN POBJECT_ATTRIBUTES TargetKey,
         {
             /* Lock the registry */
             CmpLockRegistryExclusive();
-            
+
             /* Check if we are already loaded */
             if (CmpIsHiveAlreadyLoaded(KeyHandle, SourceFile, &LoadedHive))
             {
@@ -1739,25 +2073,25 @@ CmLoadKey(IN POBJECT_ATTRIBUTES TargetKey,
             /* Release the registry */
             CmpUnlockRegistry();
         }
-        
+
         /* Close the key handle if we had one */
         if (KeyHandle) ZwClose(KeyHandle);
         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();
-    
+
     /* Set flag */
     if (Flags & REG_NO_LAZY_FLUSH) CmHive->Hive.HiveFlags |= HIVE_NOLAZYFLUSH;
-    
+
     /* Link the hive */
     Status = CmpLinkHiveToMaster(TargetKey->ObjectName,
                                  TargetKey->RootDirectory,
@@ -1768,7 +2102,7 @@ CmLoadKey(IN POBJECT_ATTRIBUTES TargetKey,
     {
         /* Add to HiveList key */
         CmpAddToHiveFileList(CmHive);
-        
+
         /* Sync the hive if necessary */
         if (Allocate)
         {
@@ -1777,11 +2111,11 @@ CmLoadKey(IN POBJECT_ATTRIBUTES TargetKey,
             HvSyncHive(&CmHive->Hive);
             CmpUnlockHiveFlusher(CmHive);
         }
-        
+
         /* Release the hive */
         CmHive->Hive.HiveFlags &= ~HIVE_IS_UNLOADING;
         CmHive->CreatorOwner = NULL;
-        
+
         /* Allow loads */
         ExReleasePushLock(&CmpLoadHiveLock);
     }
@@ -1791,36 +2125,195 @@ CmLoadKey(IN POBJECT_ATTRIBUTES TargetKey,
         /* FIXME: TODO */
         ASSERT(FALSE);
     }
-    
+
     /* Is this first profile load? */
-    if (!(CmpProfileLoaded) && !(CmpWasSetupBoot))
+    if (!CmpProfileLoaded && !CmpWasSetupBoot)
     {
         /* User is now logged on, set quotas */
         CmpProfileLoaded = TRUE;
         CmpSetGlobalQuotaAllowed();
     }
-    
+
     /* Unlock the registry */
     CmpUnlockRegistry();
-    
+
     /* Close handle and return */
     if (KeyHandle) ZwClose(KeyHandle);
     return Status;
 }
 
+static
+BOOLEAN
+NTAPI
+CmpUnlinkHiveFromMaster(IN PCMHIVE CmHive,
+                        IN HCELL_INDEX Cell)
+{
+    PCELL_DATA CellData;
+    HCELL_INDEX LinkCell;
+    NTSTATUS Status;
+
+    DPRINT("CmpUnlinkHiveFromMaster()\n");
+
+    /* Get the cell data */
+    CellData = HvGetCell(&CmHive->Hive, Cell);
+    if (CellData == NULL)
+        return FALSE;
+
+    /* Get the link cell and release the current cell */
+    LinkCell = CellData->u.KeyNode.Parent;
+    HvReleaseCell(&CmHive->Hive, Cell);
+
+    /* Remove the link cell from the master hive */
+    CmpLockHiveFlusherExclusive(CmiVolatileHive);
+    Status = CmpFreeKeyByCell((PHHIVE)CmiVolatileHive,
+                              LinkCell,
+                              TRUE);
+    CmpUnlockHiveFlusher(CmiVolatileHive);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("CmpFreeKeyByCell() failed (Status 0x%08lx)\n", Status);
+        return FALSE;
+    }
+
+    /* Remove the hive from the list */
+    ExAcquirePushLockExclusive(&CmpHiveListHeadLock);
+    RemoveEntryList(&CmHive->HiveList);
+    ExReleasePushLock(&CmpHiveListHeadLock);
+
+    return TRUE;
+}
+
 NTSTATUS
 NTAPI
 CmUnloadKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
             IN ULONG Flags)
 {
-    UNIMPLEMENTED;
-    return STATUS_NOT_IMPLEMENTED;
+    PHHIVE Hive;
+    PCMHIVE CmHive;
+    HCELL_INDEX Cell;
+
+    DPRINT("CmUnloadKey(%p, %lx)\n", Kcb, Flags);
+
+    /* Get the hive */
+    Hive = Kcb->KeyHive;
+    Cell = Kcb->KeyCell;
+    CmHive = (PCMHIVE)Hive;
+
+    /* Fail if the key is not a hive root key */
+    if (Cell != Hive->BaseBlock->RootCell)
+    {
+        DPRINT1("Key is not a hive root key!\n");
+        return STATUS_INVALID_PARAMETER;
+    }
+
+    /* Fail if we try to unload the master hive */
+    if (CmHive == CmiVolatileHive)
+    {
+        DPRINT1("Do not try to unload the master hive!\n");
+        return STATUS_INVALID_PARAMETER;
+    }
+
+    /* Mark this hive as being unloaded */
+    Hive->HiveFlags |= HIVE_IS_UNLOADING;
+
+    /* Search for any opened keys in this hive, and take an appropriate action */
+    if (Kcb->RefCount > 1)
+    {
+        if (Flags != REG_FORCE_UNLOAD)
+        {
+            if (CmpEnumerateOpenSubKeys(Kcb, FALSE, FALSE) != 0)
+            {
+                /* There are open subkeys but we don't force hive unloading, fail */
+                Hive->HiveFlags &= ~HIVE_IS_UNLOADING;
+                return STATUS_CANNOT_DELETE;
+            }
+        }
+        else
+        {
+            DPRINT1("CmUnloadKey: Force unloading is HALF-IMPLEMENTED, expect dangling KCBs problems!\n");
+            if (CmpEnumerateOpenSubKeys(Kcb, TRUE, TRUE) != 0)
+            {
+                /* There are open subkeys that we cannot force to unload, fail */
+                Hive->HiveFlags &= ~HIVE_IS_UNLOADING;
+                return STATUS_CANNOT_DELETE;
+            }
+        }
+    }
+
+    /* Flush the hive */
+    CmFlushKey(Kcb, TRUE);
+
+    /* Unlink the hive from the master hive */
+    if (!CmpUnlinkHiveFromMaster(CmHive, Cell))
+    {
+        DPRINT("CmpUnlinkHiveFromMaster() failed!\n");
+
+        /* Remove the unloading flag and return failure */
+        Hive->HiveFlags &= ~HIVE_IS_UNLOADING;
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    /* Flush any notifications if we force hive unloading */
+    if (Flags == REG_FORCE_UNLOAD)
+        CmpFlushNotifiesOnKeyBodyList(Kcb, TRUE); // Lock is already held
+
+    /* Clean up information we have on the subkey */
+    CmpCleanUpSubKeyInfo(Kcb->ParentKcb);
+
+    /* Set the KCB in delete mode and remove it */
+    Kcb->Delete = TRUE;
+    CmpRemoveKeyControlBlock(Kcb);
+
+    if (Flags != REG_FORCE_UNLOAD)
+    {
+        /* Release the KCB locks */
+        CmpReleaseTwoKcbLockByKey(Kcb->ConvKey, Kcb->ParentKcb->ConvKey);
+
+        /* Release the hive loading lock */
+        ExReleasePushLockExclusive(&CmpLoadHiveLock);
+    }
+
+    /* Release hive lock */
+    CmpUnlockRegistry();
+
+    /* Close file handles */
+    CmpCloseHiveFiles(CmHive);
+
+    /* Remove the hive from the hive file list */
+    CmpRemoveFromHiveFileList(CmHive);
+
+/**
+ ** NOTE:
+ ** The following code is mostly equivalent to what we "call" CmpDestroyHive()
+ **/
+    /* Destroy the security descriptor cache */
+    CmpDestroySecurityCache(CmHive);
+
+    /* Destroy the view list */
+    CmpDestroyHiveViewList(CmHive);
+
+    /* Delete the flusher lock */
+    ExDeleteResourceLite(CmHive->FlusherLock);
+    ExFreePoolWithTag(CmHive->FlusherLock, TAG_CMHIVE);
+
+    /* Delete the view lock */
+    ExFreePoolWithTag(CmHive->ViewLock, TAG_CMHIVE);
+
+    /* Free the hive storage */
+    HvFree(Hive);
+
+    /* Free the hive */
+    CmpFree(CmHive, TAG_CM);
+
+    return STATUS_SUCCESS;
 }
 
 ULONG
 NTAPI
-CmCountOpenSubKeys(IN PCM_KEY_CONTROL_BLOCK RootKcb,
-                   IN BOOLEAN RemoveEmptyCacheEntries)
+CmpEnumerateOpenSubKeys(
+    IN PCM_KEY_CONTROL_BLOCK RootKcb,
+    IN BOOLEAN RemoveEmptyCacheEntries,
+    IN BOOLEAN DereferenceOpenedEntries)
 {
     PCM_KEY_HASH Entry;
     PCM_KEY_CONTROL_BLOCK CachedKcb;
@@ -1829,12 +2322,12 @@ CmCountOpenSubKeys(IN PCM_KEY_CONTROL_BLOCK RootKcb,
     ULONG i, j;
     ULONG SubKeys = 0;
 
-    DPRINT("CmCountOpenSubKeys() called\n");
+    DPRINT("CmpEnumerateOpenSubKeys() called\n");
 
-    /* The root key is the only referenced key. There are no refereced sub keys. */
+    /* The root key is the only referenced key. There are no referenced sub keys. */
     if (RootKcb->RefCount == 1)
     {
-        DPRINT("open sub keys: 0\n");
+        DPRINT("Open sub keys: 0\n");
         return 0;
     }
 
@@ -1866,15 +2359,47 @@ CmCountOpenSubKeys(IN PCM_KEY_CONTROL_BLOCK RootKcb,
                 /* Check whether the parent is the root key */
                 if (ParentKcb == RootKcb)
                 {
-                    DPRINT("Found a sub key \n");
-                    DPRINT("RefCount = %u\n", CachedKcb->RefCount);
+                    DPRINT("Found a sub key, RefCount = %u\n", CachedKcb->RefCount);
 
                     if (CachedKcb->RefCount > 0)
                     {
+                        DPRINT1("Found a sub key pointing to '%.*s', RefCount = %u\n",
+                                CachedKcb->NameBlock->NameLength, CachedKcb->NameBlock->Name,
+                                CachedKcb->RefCount);
+
+                        /* If we dereference opened KCBs, don't touch read-only keys */
+                        if (DereferenceOpenedEntries &&
+                            !(CachedKcb->ExtFlags & CM_KCB_READ_ONLY_KEY))
+                        {
+                            /* Registry needs to be locked down */
+                            CMP_ASSERT_EXCLUSIVE_REGISTRY_LOCK();
+
+                            /* Flush any notifications */
+                            CmpFlushNotifiesOnKeyBodyList(CachedKcb, TRUE); // Lock is already held
+
+                            /* Clean up information we have on the subkey */
+                            CmpCleanUpSubKeyInfo(CachedKcb->ParentKcb);
+
+                            /* Get and cache the next cache entry */
+                            // Entry = Entry->NextHash;
+                            Entry = CachedKcb->NextHash;
+
+                            /* Set the KCB in delete mode and remove it */
+                            CachedKcb->Delete = TRUE;
+                            CmpRemoveKeyControlBlock(CachedKcb);
+
+                            /* Clear the cell */
+                            CachedKcb->KeyCell = HCELL_NIL;
+
+                            /* Restart with the next cache entry */
+                            continue;
+                        }
+                        /* Else, the key cannot be dereferenced, and we count it as in use */
+
                         /* Count the current hash entry if it is in use */
                         SubKeys++;
                     }
-                    else if ((CachedKcb->RefCount == 0) && (RemoveEmptyCacheEntries == TRUE))
+                    else if ((CachedKcb->RefCount == 0) && RemoveEmptyCacheEntries)
                     {
                         /* Remove the current key from the delayed close list */
                         CmpRemoveFromDelayedClose(CachedKcb);
@@ -1894,7 +2419,346 @@ CmCountOpenSubKeys(IN PCM_KEY_CONTROL_BLOCK RootKcb,
         }
     }
 
-    DPRINT("open sub keys: %u\n", SubKeys);
+    if (SubKeys > 0)
+        DPRINT1("Open sub keys: %u\n", SubKeys);
 
     return SubKeys;
 }
+
+static
+NTSTATUS
+CmpDeepCopyKeyInternal(IN PHHIVE SourceHive,
+                       IN HCELL_INDEX SrcKeyCell,
+                       IN PHHIVE DestinationHive,
+                       IN HCELL_INDEX Parent,
+                       IN HSTORAGE_TYPE StorageType,
+                       OUT PHCELL_INDEX DestKeyCell OPTIONAL)
+{
+    NTSTATUS Status;
+    PCM_KEY_NODE SrcNode;
+    PCM_KEY_NODE DestNode = NULL;
+    HCELL_INDEX NewKeyCell = HCELL_NIL;
+    HCELL_INDEX NewClassCell = HCELL_NIL, NewSecCell = HCELL_NIL;
+    HCELL_INDEX SubKey, NewSubKey;
+    ULONG Index, SubKeyCount;
+
+    PAGED_CODE();
+
+    DPRINT("CmpDeepCopyKeyInternal(0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X)\n",
+           SourceHive,
+           SrcKeyCell,
+           DestinationHive,
+           Parent,
+           StorageType,
+           DestKeyCell);
+
+    /* Get the source cell node */
+    SrcNode = HvGetCell(SourceHive, SrcKeyCell);
+    ASSERT(SrcNode);
+
+    /* Sanity check */
+    ASSERT(SrcNode->Signature == CM_KEY_NODE_SIGNATURE);
+
+    /* Create a simple copy of the source key */
+    NewKeyCell = CmpCopyCell(SourceHive,
+                             SrcKeyCell,
+                             DestinationHive,
+                             StorageType);
+    if (NewKeyCell == HCELL_NIL)
+    {
+        /* Not enough storage space */
+        Status = STATUS_INSUFFICIENT_RESOURCES;
+        goto Cleanup;
+    }
+
+    /* Get the destination cell node */
+    DestNode = HvGetCell(DestinationHive, NewKeyCell);
+    ASSERT(DestNode);
+
+    /* Set the parent and copy the flags */
+    DestNode->Parent = Parent;
+    DestNode->Flags  = (SrcNode->Flags & KEY_COMP_NAME); // Keep only the single permanent flag
+    if (Parent == HCELL_NIL)
+    {
+        /* This is the new root node */
+        DestNode->Flags |= KEY_HIVE_ENTRY | KEY_NO_DELETE;
+    }
+
+    /* Copy the class cell */
+    if (SrcNode->ClassLength > 0)
+    {
+        NewClassCell = CmpCopyCell(SourceHive,
+                                   SrcNode->Class,
+                                   DestinationHive,
+                                   StorageType);
+        if (NewClassCell == HCELL_NIL)
+        {
+            /* Not enough storage space */
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto Cleanup;
+        }
+
+        DestNode->Class = NewClassCell;
+        DestNode->ClassLength = SrcNode->ClassLength;
+    }
+    else
+    {
+        DestNode->Class = HCELL_NIL;
+        DestNode->ClassLength = 0;
+    }
+
+    /* Copy the security cell (FIXME: HACKish poor-man version) */
+    if (SrcNode->Security != HCELL_NIL)
+    {
+        NewSecCell = CmpCopyCell(SourceHive,
+                                 SrcNode->Security,
+                                 DestinationHive,
+                                 StorageType);
+        if (NewSecCell == HCELL_NIL)
+        {
+            /* Not enough storage space */
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto Cleanup;
+        }
+    }
+    DestNode->Security = NewSecCell;
+
+    /* Copy the value list */
+    Status = CmpCopyKeyValueList(SourceHive,
+                                 &SrcNode->ValueList,
+                                 DestinationHive,
+                                 &DestNode->ValueList,
+                                 StorageType);
+    if (!NT_SUCCESS(Status))
+        goto Cleanup;
+
+    /* Clear the invalid subkey index */
+    DestNode->SubKeyCounts[Stable] = DestNode->SubKeyCounts[Volatile] = 0;
+    DestNode->SubKeyLists[Stable] = DestNode->SubKeyLists[Volatile] = HCELL_NIL;
+
+    /* Calculate the total number of subkeys */
+    SubKeyCount = SrcNode->SubKeyCounts[Stable] + SrcNode->SubKeyCounts[Volatile];
+
+    /* Loop through all the subkeys */
+    for (Index = 0; Index < SubKeyCount; Index++)
+    {
+        /* Get the subkey */
+        SubKey = CmpFindSubKeyByNumber(SourceHive, SrcNode, Index);
+        ASSERT(SubKey != HCELL_NIL);
+
+        /* Call the function recursively for the subkey */
+        //
+        // FIXME: Danger!! Kernel stack exhaustion!!
+        //
+        Status = CmpDeepCopyKeyInternal(SourceHive,
+                                        SubKey,
+                                        DestinationHive,
+                                        NewKeyCell,
+                                        StorageType,
+                                        &NewSubKey);
+        if (!NT_SUCCESS(Status))
+            goto Cleanup;
+
+        /* Add the copy of the subkey to the new key */
+        if (!CmpAddSubKey(DestinationHive,
+                          NewKeyCell,
+                          NewSubKey))
+        {
+            /* Cleanup allocated cell */
+            HvFreeCell(DestinationHive, NewSubKey);
+
+            Status = STATUS_INSUFFICIENT_RESOURCES;
+            goto Cleanup;
+        }
+    }
+
+    /* Set success */
+    Status = STATUS_SUCCESS;
+
+Cleanup:
+
+    /* Release the cells */
+    if (DestNode) HvReleaseCell(DestinationHive, NewKeyCell);
+    if (SrcNode) HvReleaseCell(SourceHive, SrcKeyCell);
+
+    /* Cleanup allocated cells in case of failure */
+    if (!NT_SUCCESS(Status))
+    {
+        if (NewSecCell != HCELL_NIL)
+            HvFreeCell(DestinationHive, NewSecCell);
+
+        if (NewClassCell != HCELL_NIL)
+            HvFreeCell(DestinationHive, NewClassCell);
+
+        if (NewKeyCell != HCELL_NIL)
+            HvFreeCell(DestinationHive, NewKeyCell);
+
+        NewKeyCell = HCELL_NIL;
+    }
+
+    /* Set the cell index if requested and return status */
+    if (DestKeyCell) *DestKeyCell = NewKeyCell;
+    return Status;
+}
+
+NTSTATUS
+NTAPI
+CmpDeepCopyKey(IN PHHIVE SourceHive,
+               IN HCELL_INDEX SrcKeyCell,
+               IN PHHIVE DestinationHive,
+               IN HSTORAGE_TYPE StorageType,
+               OUT PHCELL_INDEX DestKeyCell OPTIONAL)
+{
+    /* Call the internal function */
+    return CmpDeepCopyKeyInternal(SourceHive,
+                                  SrcKeyCell,
+                                  DestinationHive,
+                                  HCELL_NIL,
+                                  StorageType,
+                                  DestKeyCell);
+}
+
+NTSTATUS
+NTAPI
+CmSaveKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
+          IN HANDLE FileHandle,
+          IN ULONG Flags)
+{
+    NTSTATUS Status = STATUS_SUCCESS;
+    PCMHIVE KeyHive = NULL;
+    PAGED_CODE();
+
+    DPRINT("CmSaveKey(0x%08X, 0x%08X, %lu)\n", Kcb, FileHandle, Flags);
+
+    /* Lock the registry and KCB */
+    CmpLockRegistry();
+    CmpAcquireKcbLockShared(Kcb);
+
+    if (Kcb->Delete)
+    {
+        /* The source key has been deleted, do nothing */
+        Status = STATUS_KEY_DELETED;
+        goto Cleanup;
+    }
+
+    if (Kcb->KeyHive == &CmiVolatileHive->Hive)
+    {
+        /* Keys that are directly in the master hive can't be saved */
+        Status = STATUS_ACCESS_DENIED;
+        goto Cleanup;
+    }
+
+    /* Create a new hive that will hold the key */
+    Status = CmpInitializeHive(&KeyHive,
+                               HINIT_CREATE,
+                               HIVE_VOLATILE,
+                               HFILE_TYPE_PRIMARY,
+                               NULL,
+                               NULL,
+                               NULL,
+                               NULL,
+                               NULL,
+                               0);
+    if (!NT_SUCCESS(Status)) goto Cleanup;
+
+    /* Copy the key recursively into the new hive */
+    Status = CmpDeepCopyKey(Kcb->KeyHive,
+                            Kcb->KeyCell,
+                            &KeyHive->Hive,
+                            Stable,
+                            &KeyHive->Hive.BaseBlock->RootCell);
+    if (!NT_SUCCESS(Status)) goto Cleanup;
+
+    /* Set the primary handle of the hive */
+    KeyHive->FileHandles[HFILE_TYPE_PRIMARY] = FileHandle;
+
+    /* Dump the hive into the file */
+    HvWriteHive(&KeyHive->Hive);
+
+Cleanup:
+
+    /* Free the hive */
+    if (KeyHive) CmpDestroyHive(KeyHive);
+
+    /* Release the locks */
+    CmpReleaseKcbLock(Kcb);
+    CmpUnlockRegistry();
+
+    return Status;
+}
+
+NTSTATUS
+NTAPI
+CmSaveMergedKeys(IN PCM_KEY_CONTROL_BLOCK HighKcb,
+                 IN PCM_KEY_CONTROL_BLOCK LowKcb,
+                 IN HANDLE FileHandle)
+{
+    PCMHIVE KeyHive = NULL;
+    NTSTATUS Status = STATUS_SUCCESS;
+
+    PAGED_CODE();
+
+    DPRINT("CmSaveKey(%p, %p, %p)\n", HighKcb, LowKcb, FileHandle);
+
+    /* Lock the registry and the KCBs */
+    CmpLockRegistry();
+    CmpAcquireKcbLockShared(HighKcb);
+    CmpAcquireKcbLockShared(LowKcb);
+
+    if (LowKcb->Delete || HighKcb->Delete)
+    {
+        /* The source key has been deleted, do nothing */
+        Status = STATUS_KEY_DELETED;
+        goto done;
+    }
+
+    /* Create a new hive that will hold the key */
+    Status = CmpInitializeHive(&KeyHive,
+                               HINIT_CREATE,
+                               HIVE_VOLATILE,
+                               HFILE_TYPE_PRIMARY,
+                               NULL,
+                               NULL,
+                               NULL,
+                               NULL,
+                               NULL,
+                               0);
+    if (!NT_SUCCESS(Status))
+        goto done;
+
+    /* Copy the low precedence key recursively into the new hive */
+    Status = CmpDeepCopyKey(LowKcb->KeyHive,
+                            LowKcb->KeyCell,
+                            &KeyHive->Hive,
+                            Stable,
+                            &KeyHive->Hive.BaseBlock->RootCell);
+    if (!NT_SUCCESS(Status))
+        goto done;
+
+    /* Copy the high precedence key recursively into the new hive */
+    Status = CmpDeepCopyKey(HighKcb->KeyHive,
+                            HighKcb->KeyCell,
+                            &KeyHive->Hive,
+                            Stable,
+                            &KeyHive->Hive.BaseBlock->RootCell);
+    if (!NT_SUCCESS(Status))
+        goto done;
+
+    /* Set the primary handle of the hive */
+    KeyHive->FileHandles[HFILE_TYPE_PRIMARY] = FileHandle;
+
+    /* Dump the hive into the file */
+    HvWriteHive(&KeyHive->Hive);
+
+done:
+    /* Free the hive */
+    if (KeyHive)
+        CmpDestroyHive(KeyHive);
+
+    /* Release the locks */
+    CmpReleaseKcbLock(LowKcb);
+    CmpReleaseKcbLock(HighKcb);
+    CmpUnlockRegistry();
+
+    return Status;
+}