Windows2000/private/ntos/config/cmsavres.c
2020-09-30 17:12:32 +02:00

1053 lines
35 KiB
C

/*++
Copyright (c) 1992 Microsoft Corporation
Module Name:
cmsavres.c
Abstract:
This file contains code for SaveKey and RestoreKey.
Author:
Bryan M. Willman (bryanwi) 15-Jan-92
--*/
#include "cmp.h"
// defines how big the buffer we use for doing a savekey by copying the hive file should be.
#define CM_SAVEKEYBUFSIZE 0x10000
extern PCMHIVE CmpMasterHive;
extern BOOLEAN CmpProfileLoaded;
extern PUCHAR CmpStashBuffer;
extern ULONG CmpGlobalQuotaAllowed;
extern ULONG CmpGlobalQuotaWarning;
extern ULONG CmpGlobalQuotaUsed;
PCMHIVE CmpCreateTemporaryHive(IN HANDLE FileHandle);
VOID CmpDestroyTemporaryHive(PCMHIVE CmHive);
NTSTATUS CmpLoadHiveVolatile(IN PCM_KEY_CONTROL_BLOCK KeyControlBlock, IN HANDLE FileHandle);
NTSTATUS CmpRefreshHive(IN PCM_KEY_CONTROL_BLOCK KeyControlBlock);
NTSTATUS CmpSaveKeyByFileCopy(PCMHIVE Hive, HANDLE FileHandle);
ULONG CmpRefreshWorkerRoutine(PCM_KEY_CONTROL_BLOCK Current, PVOID Context1, PVOID Context2);
BOOLEAN CmpMergeKeyValues(PHHIVE SourceHive,HCELL_INDEX SourceKeyCell,PCM_KEY_NODE SourceKeyNode,PHHIVE TargetHive,HCELL_INDEX TargetKeyCell,PCM_KEY_NODE TargetKeyNode);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,CmRestoreKey)
#pragma alloc_text(PAGE,CmpLoadHiveVolatile)
#pragma alloc_text(PAGE,CmpRefreshHive)
#pragma alloc_text(PAGE,CmSaveKey)
#pragma alloc_text(PAGE,CmSaveMergedKeys)
#pragma alloc_text(PAGE,CmpCreateTemporaryHive)
#pragma alloc_text(PAGE,CmpDestroyTemporaryHive)
#pragma alloc_text(PAGE,CmpRefreshWorkerRoutine)
#pragma alloc_text(PAGE,CmpSaveKeyByFileCopy)
#endif
NTSTATUS CmRestoreKey(IN PCM_KEY_CONTROL_BLOCK KeyControlBlock, IN HANDLE FileHandle, IN ULONG Flags)
/*++
Routine Description:
This copies the data from an on-disk hive into the registry.
The file is not loaded into the registry, and the system will NOT be using the source file after the call returns.
If the flag REG_WHOLE_HIVE_VOLATILE is not set, the given key is replaced by the root of the hive file.
The root's name is changed to the name of the given key.
If the flag REG_WHOLE_HIVE_VOLATILE is set, a volatile hive is created, the hive file is copied into it, and the resulting hive is linked to the master hive.
The given key must be in the master hive. (Usually will be \Registry\User)
If the flag REG_REFRESH_HIVE is set (must be only flag) then the the Hive will be restored to its state as of the last flush.
(The hive must be marked NOLAZY_FLUSH, and the caller must have TCB privilege, and the handle must point to the root of the hive.
If the refresh fails, the hive will be corrupt, and the system will bugcheck.)
If the flag REG_FORCE_RESTORE is set, the restore operation is done even if there areopen handles underneath the key we are restoring to.
Arguments:
Hive - supplies a pointer to the hive control structure for the hive
Cell - supplies index of node at root of tree to restore into
FileHandle - handle of the file to read from.
Return Value:
NTSTATUS - Result code from call, among the following:
<TBS>
--*/
{
NTSTATUS status;
PCELL_DATA ptar;
PCELL_DATA psrc;
PCMHIVE TmpCmHive;
HCELL_INDEX newroot;
HCELL_INDEX newcell;
HCELL_INDEX parent;
HCELL_INDEX list;
ULONG count;
ULONG i;
ULONG j;
LONG size;
PHHIVE Hive;
HCELL_INDEX Cell;
HSTORAGE_TYPE Type;
ULONG NumberLeaves;
PHCELL_INDEX LeafArray;
PCM_KEY_INDEX Leaf;
PCM_KEY_FAST_INDEX FastLeaf;
PAGED_CODE();
CMLOG(CML_MAJOR, CMS_SAVRES) {
KdPrint(("CmRestoreKey:\n"));
KdPrint(("\tKCB=%08lx\n",KeyControlBlock));
KdPrint(("\tFileHandle=%08lx\n",FileHandle));
}
if (Flags & REG_REFRESH_HIVE) {
if ((Flags & ~REG_REFRESH_HIVE) != 0) {
// Refresh must be alone
return STATUS_INVALID_PARAMETER;
}
}
// If they want to do WHOLE_HIVE_VOLATILE, it's a completely different API.
if (Flags & REG_WHOLE_HIVE_VOLATILE) {
return(CmpLoadHiveVolatile(KeyControlBlock, FileHandle));
}
// If they want to do REFRESH_HIVE, that's a completely different api too.
if (Flags & REG_REFRESH_HIVE) {
CmpLockRegistryExclusive();
status = CmpRefreshHive(KeyControlBlock);
CmpUnlockRegistry();
return status;
}
Hive = KeyControlBlock->KeyHive;
Cell = KeyControlBlock->KeyCell;
// Disallow attempts to "restore" the master hive
if (Hive == &CmpMasterHive->Hive) {
return STATUS_ACCESS_DENIED;
}
CmpLockRegistryExclusive();
// Make sure this key has not been deleted
if (KeyControlBlock->Delete) {
CmpUnlockRegistry();
return(STATUS_CANNOT_DELETE);
}
DCmCheckRegistry(CONTAINING_RECORD(Hive, CMHIVE, Hive));
// Check for any open handles underneath the key we are restoring to.
if (CmpSearchForOpenSubKeys(KeyControlBlock,(Flags&REG_FORCE_RESTORE)?SearchAndDeref:SearchIfExist) != 0) {
// Cannot restore over a subtree with open handles in it, or the open handles to subkeys successfully marked as closed.
CmpUnlockRegistry();
return(STATUS_CANNOT_DELETE);
}
// Make sure this is the only handle open for this key
if (KeyControlBlock->RefCount != 1 && !(Flags&REG_FORCE_RESTORE)) {
CmpUnlockRegistry();
return(STATUS_CANNOT_DELETE);
}
ptar = HvGetCell(Hive, Cell);
// The subtree the caller wants does not exactly match a subtree.
// Make a temporary hive, load the file into it, tree copy the temporary to the active, and free the temporary.
// Create the temporary hive
status = CmpInitializeHive(&TmpCmHive,HINIT_FILE,0,HFILE_TYPE_PRIMARY,NULL,FileHandle,NULL,NULL,NULL,NULL);
if (!NT_SUCCESS(status)) {
goto ErrorExit1;
}
// Create a new target root, under which we will copy the new tree
if (ptar->u.KeyNode.Flags & KEY_HIVE_ENTRY) {
parent = HCELL_NIL; // root of hive, so parent is NIL
} else {
parent = ptar->u.KeyNode.Parent;
}
newroot = CmpCopyKeyPartial(&(TmpCmHive->Hive), TmpCmHive->Hive.BaseBlock->RootCell, Hive, parent, TRUE);
if (newroot == HCELL_NIL) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorExit2;
}
// newroot has all the correct stuff, except that it has the source root's name, when it needs to have the target root's.
// So edit its name.
psrc = HvGetCell(Hive, Cell);
ptar = HvGetCell(Hive, newroot);
size = FIELD_OFFSET(CM_KEY_NODE, Name) + psrc->u.KeyNode.NameLength;
// make sure that new root has correct amount of space to hold name from old root
newcell = HvReallocateCell(Hive, newroot, size);
if (newcell == HCELL_NIL) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorExit2;
}
newroot = newcell;
ptar = HvGetCell(Hive, newroot);
status = STATUS_SUCCESS;
RtlMoveMemory((PVOID)&(ptar->u.KeyNode.Name[0]), (PVOID)&(psrc->u.KeyNode.Name[0]), psrc->u.KeyNode.NameLength);
ptar->u.KeyNode.NameLength = psrc->u.KeyNode.NameLength;
if (psrc->u.KeyNode.Flags & KEY_COMP_NAME) {
ptar->u.KeyNode.Flags |= KEY_COMP_NAME;
} else {
ptar->u.KeyNode.Flags &= ~KEY_COMP_NAME;
}
// newroot is now ready to have subtree copied under it, do tree copy
if (CmpCopyTree(&(TmpCmHive->Hive), TmpCmHive->Hive.BaseBlock->RootCell, Hive, newroot) == FALSE)
{
status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorExit2;
}
// The new root and the tree under it now look the way we want.
// Swap the new tree in for the old one.
ptar = HvGetCell(Hive, Cell);
parent = ptar->u.KeyNode.Parent;
if (ptar->u.KeyNode.Flags & KEY_HIVE_ENTRY) {
// root is actually the root of the hive. parent doesn't refer to it via a child list, but rather with an inter hive pointer.
// also, must update base block
ptar = HvGetCell( (&(CmpMasterHive->Hive)), parent);
ptar->u.KeyNode.ChildHiveReference.KeyCell = newroot;
ptar = HvGetCell(Hive, newroot);
ptar->u.KeyNode.Parent = parent;
Hive->BaseBlock->RootCell = newroot;
} else {
// Notice that new root is *always* name of existing target, therefore, even in b-tree, old and new cell can share the same reference slot in the parent.
// So simply edit the new cell_index on the top of the old.
ptar = HvGetCell(Hive, parent);
Type = HvGetCellType(Cell);
list = ptar->u.KeyNode.SubKeyLists[Type];
count = ptar->u.KeyNode.SubKeyCounts[Type];
ptar = HvGetCell(Hive, list);
if (ptar->u.KeyIndex.Signature == CM_KEY_INDEX_ROOT) {
NumberLeaves = ptar->u.KeyIndex.Count;
LeafArray = &ptar->u.KeyIndex.List[0];
} else {
NumberLeaves = 1;
LeafArray = &list;
}
// Look in each leaf for the HCELL_INDEX we need to replace
for (i = 0; i < NumberLeaves; i++) {
Leaf = (PCM_KEY_INDEX)HvGetCell(Hive, LeafArray[i]);
if (Leaf->Signature == CM_KEY_FAST_LEAF) {
FastLeaf = (PCM_KEY_FAST_INDEX)Leaf;
for (j=0; j < FastLeaf->Count; j++) {
if (FastLeaf->List[j].Cell == Cell) {
FastLeaf->List[j].Cell = newroot;
goto FoundCell;
}
}
} else {
for (j=0; j < Leaf->Count; j++) {
if (Leaf->List[j] == Cell) {
Leaf->List[j] = newroot;
goto FoundCell;
}
}
}
}
ASSERT(FALSE); // implies we didn't find it
// we should never get here
}
FoundCell:
// Fix up the key control block to point to the new root
KeyControlBlock->KeyCell = newroot;
KeyControlBlock->KeyNode = (PCM_KEY_NODE)HvGetCell(Hive, newroot);
// Kcb has changed, update the cache information.
// Registry locked exclusively, no need for KCB lock.
ASSERT_CM_LOCK_OWNED_EXCLUSIVE();
CmpCleanUpKcbValueCache(KeyControlBlock);
if (KeyControlBlock->ExtFlags & CM_KCB_SUBKEY_HINT) {
// Now free the HintIndex allocation
ExFreePoolWithTag(KeyControlBlock->IndexHint, CM_CACHE_INDEX_TAG | PROTECTED_POOL);
}
KeyControlBlock->ValueCache.Count = KeyControlBlock->KeyNode->ValueList.Count;
KeyControlBlock->ValueCache.ValueList = (ULONG_PTR) (KeyControlBlock->KeyNode->ValueList.List);
KeyControlBlock->Flags = KeyControlBlock->KeyNode->Flags;
KeyControlBlock->Security = KeyControlBlock->KeyNode->Security;
KeyControlBlock->ExtFlags = 0;
// Delete the old subtree and it's root cell
CmpDeleteTree(Hive, Cell);
CmpFreeKeyByCell(Hive, Cell, FALSE);
// Report the notify event
CmpReportNotify(KeyControlBlock, KeyControlBlock->KeyHive, KeyControlBlock->KeyCell, REG_NOTIFY_CHANGE_NAME);
// Free the temporary hive
CmpDestroyTemporaryHive(TmpCmHive);
// We've given user chance to log on, so turn on quota
if (CmpProfileLoaded == FALSE) {
CmpProfileLoaded = TRUE;
CmpSetGlobalQuotaAllowed();
}
DCmCheckRegistry(CONTAINING_RECORD(Hive, CMHIVE, Hive));
CmpUnlockRegistry();
return status;
// Error exits
ErrorExit2:
CmpDestroyTemporaryHive(TmpCmHive);
ErrorExit1:
DCmCheckRegistry(CONTAINING_RECORD(Hive, CMHIVE, Hive));
CmpUnlockRegistry();
return status;
}
NTSTATUS CmpLoadHiveVolatile(IN PCM_KEY_CONTROL_BLOCK KeyControlBlock, IN HANDLE FileHandle)
/*++
Routine Description:
Creates a VOLATILE hive and loads it underneath the given Hive and Cell.
The data for the volatile hive is copied out of the given file.
The file is *NOT* in use by the registry when this returns.
Arguments:
Hive - Supplies the hive that the new hive is to be created under.
Currently this must be the Master Hive.
Cell - Supplies the HCELL_INDEX of the new hive's parent. (Usually will by \Registry\User)
FileHandle - Supplies a handle to the hive file that will be copied into the volatile hive.
Return Value:
NTSTATUS
--*/
{
NTSTATUS status;
PHHIVE Hive;
PCELL_DATA RootData;
PCMHIVE NewHive;
PCMHIVE TempHive;
HCELL_INDEX Cell;
HCELL_INDEX Root;
REGISTRY_COMMAND Command;
NTSTATUS Status;
UNICODE_STRING RootName;
UNICODE_STRING NewName;
USHORT NewNameLength;
PUNICODE_STRING ConstructedName;
PAGED_CODE();
CmpLockRegistryExclusive();
if (KeyControlBlock->Delete) {
CmpUnlockRegistry();
return(STATUS_KEY_DELETED);
}
Hive = KeyControlBlock->KeyHive;
Cell = KeyControlBlock->KeyCell;
// New hives can be created only under the master hive.
if (Hive != &CmpMasterHive->Hive) {
CmpUnlockRegistry();
return(STATUS_INVALID_PARAMETER);
}
// Create a temporary hive and load the file into it
status = CmpInitializeHive(&TempHive,HINIT_FILE,0,HFILE_TYPE_PRIMARY,NULL,FileHandle,NULL,NULL,NULL,NULL);
if (!NT_SUCCESS(status)) {
CmpUnlockRegistry();
return(status);
}
// Create the volatile hive.
status = CmpInitializeHive(&NewHive,HINIT_CREATE,HIVE_VOLATILE,0,NULL,NULL,NULL,NULL,NULL,NULL);
if (!NT_SUCCESS(status)) {
CmpDestroyTemporaryHive(TempHive);
CmpUnlockRegistry();
return(status);
}
// Create the target root
Root = CmpCopyKeyPartial(&TempHive->Hive,TempHive->Hive.BaseBlock->RootCell,&NewHive->Hive,HCELL_NIL,FALSE);
if (Root == HCELL_NIL) {
CmpDestroyTemporaryHive(TempHive);
CmpDestroyTemporaryHive(NewHive);
CmpUnlockRegistry();
return(STATUS_INSUFFICIENT_RESOURCES);
}
NewHive->Hive.BaseBlock->RootCell = Root;
// Copy the temporary hive into the volatile hive
if (!CmpCopyTree(&TempHive->Hive, TempHive->Hive.BaseBlock->RootCell, &NewHive->Hive, Root))
{
CmpDestroyTemporaryHive(TempHive);
CmpDestroyTemporaryHive(NewHive);
CmpUnlockRegistry();
return(STATUS_INSUFFICIENT_RESOURCES);
}
// The volatile hive now has all the right stuff in all the right places, we just need to link it into the master hive.
RootData = HvGetCell(&NewHive->Hive,Root);
ConstructedName = CmpConstructName(KeyControlBlock);
NewNameLength = ConstructedName->Length + CmpHKeyNameLen(&RootData->u.KeyNode) + sizeof(WCHAR);
NewName.Buffer = ExAllocatePool(PagedPool, NewNameLength);
if (NewName.Buffer == NULL) {
CmpDestroyTemporaryHive(TempHive);
CmpDestroyTemporaryHive(NewHive);
CmpUnlockRegistry();
ExFreePoolWithTag(ConstructedName, CM_NAME_TAG | PROTECTED_POOL);
return(STATUS_INSUFFICIENT_RESOURCES);
}
NewName.Length = NewName.MaximumLength = NewNameLength;
RtlCopyUnicodeString(&NewName, ConstructedName);
ExFreePoolWithTag(ConstructedName, CM_NAME_TAG | PROTECTED_POOL);
RtlAppendUnicodeToString(&NewName, L"\\");
if (RootData->u.KeyNode.Flags & KEY_COMP_NAME) {
CmpCopyCompressedName(NewName.Buffer + (NewName.Length / sizeof(WCHAR)), NewName.MaximumLength - NewName.Length, RootData->u.KeyNode.Name, CmpHKeyNameLen(&RootData->u.KeyNode));
NewName.Length += CmpHKeyNameLen(&RootData->u.KeyNode);
} else {
RootName.Buffer = RootData->u.KeyNode.Name;
RootName.Length = RootName.MaximumLength = RootData->u.KeyNode.NameLength;
RtlAppendUnicodeStringToString(&NewName,&RootName);
}
Status = CmpLinkHiveToMaster(&NewName,NULL,NewHive,FALSE,NULL);
if (NT_SUCCESS(Status)) {
Command.CmHive = NewHive;
Command.Command = REG_CMD_ADD_HIVE_LIST;
CmpWorker(&Command);
} else {
CmpDestroyTemporaryHive(NewHive);
}
CmpDestroyTemporaryHive(TempHive);
ExFreePool(NewName.Buffer);
if (NT_SUCCESS(Status)) {
// We've given user chance to log on, so turn on quota
if (CmpProfileLoaded == FALSE) {
CmpProfileLoaded = TRUE;
CmpSetGlobalQuotaAllowed();
}
}
CmpUnlockRegistry();
return(Status);
}
ULONG CmpRefreshWorkerRoutine(PCM_KEY_CONTROL_BLOCK Current, PVOID Context1, PVOID Context2)
/*++
Routine Description:
Helper used by CmpRefreshHive when calling CmpSearchKeyControlBlockTree.
If a match is found, the KCB is deleted and restart is returned.
Else, continue is returned.
Arguments:
Current - the kcb to examine
Context1 - the hive to match against
Context2 - nothing
Return Value:
if no match, return continue.
if match, return restart.
--*/
{
PAGED_CODE();
if (Current->KeyHive == (PHHIVE)Context1) {
// match. set deleted flag. continue search.
Current->Delete = TRUE;
Current->KeyHive = NULL;
Current->KeyCell = 0;
return(KCB_WORKER_DELETE);
}
return KCB_WORKER_CONTINUE;
}
NTSTATUS CmpRefreshHive(IN PCM_KEY_CONTROL_BLOCK KeyControlBlock)
/*++
Routine Description:
Backs out all changes to a hives since it was last flushed.
Used as a transaction abort by the security system.
Caller must have SeTcbPrivilege.
The target hive must have HIVE_NOLAZYFLUSH set.
KeyControlBlock must refer to the root of the hive (HIVE_ENTRY must be set in the key.)
Any kcbs that point into this hive (and thus any handles open against it) will be force to DELETED state. (If we do any work.)
All notifies pending against the hive will be flushed.
When we're done, only the tombstone kcbs, handles, and attached notify blocks will be left.
WARNNOTE: Once reads have begun, if the operation fails, the hive will be corrupt, so we will bugcheck.
Arguments:
KeyControlBlock - provides a reference to the root of the hive we wish to refresh.
Return Value:
NTSTATUS
--*/
{
PHHIVE Hive;
HCELL_INDEX Cell;
PCELL_DATA pcell;
REGISTRY_COMMAND CommandArea;
PLIST_ENTRY ptr;
PCM_NOTIFY_BLOCK node;
PAGED_CODE();
// Check to see if the caller has the privilege to make this call.
if (!SeSinglePrivilegeCheck(SeTcbPrivilege, KeGetPreviousMode())) {
return STATUS_PRIVILEGE_NOT_HELD;
}
if (KeyControlBlock->Delete) {
return(STATUS_KEY_DELETED);
}
CmpLockRegistryExclusive();
Hive = KeyControlBlock->KeyHive;
Cell = KeyControlBlock->KeyCell;
// check to see if hive is of proper type
if ( ! (Hive->HiveFlags & HIVE_NOLAZYFLUSH)) {
CmpUnlockRegistry();
return STATUS_INVALID_PARAMETER;
}
// punt if any volatile storage has been allocated
if (Hive->Storage[Volatile].Length != 0) {
CmpUnlockRegistry();
return STATUS_UNSUCCESSFUL;
}
// check to see if call was applied to the root of the hive
pcell = HvGetCell(Hive, Cell);
if ( ! (pcell->u.KeyNode.Flags & KEY_HIVE_ENTRY)) {
CmpUnlockRegistry();
return STATUS_INVALID_PARAMETER;
}
// Flush all NotifyBlocks attached to this hive
while (TRUE) {
// flush below will edit list, so restart at beginning each time
ptr = &(((PCMHIVE)Hive)->NotifyList);
if (ptr->Flink == NULL) {
break;
}
ptr = ptr->Flink;
node = CONTAINING_RECORD(ptr, CM_NOTIFY_BLOCK, HiveList);
ASSERT((node->KeyBody)->NotifyBlock == node);
CmpFlushNotify(node->KeyBody);
}
// Force all kcbs that refer to this hive to the deleted state.
CmpSearchKeyControlBlockTree(CmpRefreshWorkerRoutine, (PVOID)Hive, NULL);
// We cannot do the relevent I/O from this context, so send a command to the worker code.
CommandArea.Command = REG_CMD_REFRESH_HIVE;
CommandArea.Hive = Hive;
CmpWorker(&CommandArea);
CmpUnlockRegistry();
// we're back (rather than bugchecked) so it worked
return STATUS_SUCCESS;
}
NTSTATUS CmSaveKey(IN PCM_KEY_CONTROL_BLOCK KeyControlBlock, IN HANDLE FileHandle)
/*++
Arguments:
KeyControlBlock - pointer to the KCB that describes the key
FileHandle - handle of the file to dump to.
Return Value:
NTSTATUS - Result code from call, among the following:
<TBS>
--*/
{
NTSTATUS status;
PCELL_DATA proot;
USHORT flags;
PCMHIVE TmpCmHive;
PCMHIVE CmHive;
HCELL_INDEX newroot;
PHHIVE Hive;
HCELL_INDEX Cell;
ULONG OldQuotaAllowed;
ULONG OldQuotaWarning;
#if DBG
ULONG OldQuotaUsed;
#endif
PAGED_CODE();
CMLOG(CML_MAJOR, CMS_SAVRES) {
KdPrint(("CmSaveKey:\n"));
KdPrint(("\tKCB=%08lx",KeyControlBlock));
KdPrint(("\tFileHandle=%08lx\n",FileHandle));
}
// Disallow attempts to "save" the master hive
Hive = KeyControlBlock->KeyHive;
Cell = KeyControlBlock->KeyCell;
if (Hive == &CmpMasterHive->Hive) {
return STATUS_ACCESS_DENIED;
}
CmpLockRegistryExclusive();
if (KeyControlBlock->Delete) {
CmpUnlockRegistry();
return STATUS_KEY_DELETED;
}
DCmCheckRegistry(CONTAINING_RECORD(Hive, CMHIVE, Hive));
if ( (Hive->HiveFlags & HIVE_NOLAZYFLUSH) && (Hive->DirtyCount != 0))
{
// It's a NOLAZY hive, and there's some dirty data, so writing out a snapshot of what's in memory will not give the caller consistent user data.
// Therefore, copy the on disk image instead of the memory image
// Note that this will generate weird results if the key being saved is not the root of the hive, since the resulting file will always be a copy of the entire hive, not just the subtree they asked for.
status = CmpSaveKeyByFileCopy((PCMHIVE)Hive, FileHandle);
CmpUnlockRegistry();
return status;
}
proot = HvGetCell(Hive, Cell);
flags = proot->u.KeyNode.Flags;
// Always try to copy the hive and write it out.
// This has the effect of compressing out unused free storage.
// If there isn't space, and the savekey is of the root of the hive, then just write it out directly.
// (i.e. don't fail on a whole hive restore just because we're out of memory.)
CMLOG(CML_FLOW, CMS_SAVRES) KdPrint(("\tSave of partial hive\n"));
// The subtree the caller wants does not exactly match a subtree.
// Make a temporary hive, tree copy the source to temp, write out the temporary, free the temporary.
// temporarily disable registry quota as we will be giving this memory back immediately!
OldQuotaAllowed = CmpGlobalQuotaAllowed;
OldQuotaWarning = CmpGlobalQuotaWarning;
CmpGlobalQuotaAllowed = CM_WRAP_LIMIT;
CmpGlobalQuotaWarning = CM_WRAP_LIMIT;
#if DBG
OldQuotaUsed = CmpGlobalQuotaUsed;
#endif
// Create the temporary hive
TmpCmHive = CmpCreateTemporaryHive(FileHandle);
if (TmpCmHive == NULL) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorInsufficientResources;
}
// Create a root cell, mark it as such
newroot = CmpCopyKeyPartial(
Hive,
Cell,
&(TmpCmHive->Hive),
HCELL_NIL, // will force KEY_HIVE_ENTRY set
TRUE);
if (newroot == HCELL_NIL) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorInsufficientResources;
}
TmpCmHive->Hive.BaseBlock->RootCell = newroot;
// Do a tree copy
if (CmpCopyTree(Hive, Cell, &(TmpCmHive->Hive), newroot) == FALSE) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorInsufficientResources;
}
// Write the file
TmpCmHive->FileHandles[HFILE_TYPE_EXTERNAL] = FileHandle;
status = HvWriteHive(&(TmpCmHive->Hive));
TmpCmHive->FileHandles[HFILE_TYPE_EXTERNAL] = NULL;
// Error exits
ErrorInsufficientResources:
// Free the temporary hive
if (TmpCmHive != NULL) {
CmpDestroyTemporaryHive(TmpCmHive);
}
#if DBG
// Sanity check: when this assert fires, we have leaks in the copy routine.
ASSERT( OldQuotaUsed == CmpGlobalQuotaUsed );
#endif
// Set global quota back to what it was.
CmpGlobalQuotaAllowed = OldQuotaAllowed;
CmpGlobalQuotaWarning = OldQuotaWarning;
DCmCheckRegistry(CONTAINING_RECORD(Hive, CMHIVE, Hive));
CmpUnlockRegistry();
return status;
}
NTSTATUS CmSaveMergedKeys(IN PCM_KEY_CONTROL_BLOCK HighPrecedenceKcb, IN PCM_KEY_CONTROL_BLOCK LowPrecedenceKcb, IN HANDLE FileHandle)
/*++
Arguments:
HighPrecedenceKcb - pointer to the KCB that describes the High precedence key (the one that wins in a duplicate key case)
LowPrecedenceKcb - pointer to the KCB that describes the Low precedence key (the one that gets overwritten in a duplicate key case)
FileHandle - handle of the file to dump to.
Return Value:
NTSTATUS - Result code from call, among the following:
<TBS>
--*/
{
NTSTATUS status;
PCELL_DATA proot;
USHORT flags;
PCMHIVE TmpCmHive;
HCELL_INDEX newroot;
PHHIVE HighHive;
PHHIVE LowHive;
HCELL_INDEX HighCell;
HCELL_INDEX LowCell;
ULONG OldQuotaAllowed;
ULONG OldQuotaWarning;
PCM_KEY_NODE HighNode,LowNode;
#if DBG
ULONG OldQuotaUsed;
#endif
PAGED_CODE();
CMLOG(CML_MAJOR, CMS_SAVRES) {
KdPrint(("CmSaveMergedKeys:\n"));
KdPrint(("\tHighKCB=%08lx",HighPrecedenceKcb));
KdPrint(("\tLowKCB=%08lx",LowPrecedenceKcb));
KdPrint(("\tFileHandle=%08lx\n",FileHandle));
}
// Disallow attempts to "merge" keys located in the same hive
// A brutal way to avoid recursivity
HighHive = HighPrecedenceKcb->KeyHive;
HighCell = HighPrecedenceKcb->KeyCell;
LowHive = LowPrecedenceKcb->KeyHive;
LowCell = LowPrecedenceKcb->KeyCell;
if (LowHive == HighHive ) {
return STATUS_INVALID_PARAMETER;
}
CmpLockRegistryExclusive();
if (HighPrecedenceKcb->Delete || LowPrecedenceKcb->Delete) {
// Unlock the registry and fail if one of the keys are marked as deleted
CmpUnlockRegistry();
return STATUS_KEY_DELETED;
}
DCmCheckRegistry(CONTAINING_RECORD(HighHive, CMHIVE, Hive));
DCmCheckRegistry(CONTAINING_RECORD(LowHive, CMHIVE, Hive));
if( ((HighHive->HiveFlags & HIVE_NOLAZYFLUSH) && (HighHive->DirtyCount != 0)) || ((LowHive->HiveFlags & HIVE_NOLAZYFLUSH) && (LowHive->DirtyCount != 0)) ) {
// Reject the call when one of the hives is a NOLAZY hive and there's some dirty data.
// Another alternative will be to save only one of the trees (if a valid one exists) or an entire hive (see CmSaveKey)
status = STATUS_INVALID_PARAMETER;
CmpUnlockRegistry();
return status;
}
proot = HvGetCell(LowHive, LowCell);
flags = proot->u.KeyNode.Flags;
CMLOG(CML_FLOW, CMS_SAVRES) KdPrint(("\tCopy of partial HighHive\n"));
// Make a temporary hive, tree copy the key subtree from HighHive hive to temp, tree-merge with the key subtree from LowHive hive, write out the temporary, free the temporary.
// Always write the HighHive subtree first, so its afterwise only add new keys/values
// temporarily disable registry quota as we will be giving this memory back immediately!
OldQuotaAllowed = CmpGlobalQuotaAllowed;
OldQuotaWarning = CmpGlobalQuotaWarning;
CmpGlobalQuotaAllowed = CM_WRAP_LIMIT;
CmpGlobalQuotaWarning = CM_WRAP_LIMIT;
#if DBG
OldQuotaUsed = CmpGlobalQuotaUsed;
#endif
// Create the temporary hive
TmpCmHive = CmpCreateTemporaryHive(FileHandle);
if (TmpCmHive == NULL) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorInsufficientResources;
}
// Create a root cell, mark it as such
newroot = CmpCopyKeyPartial(
HighHive,
HighCell,
&(TmpCmHive->Hive),
HCELL_NIL, // will force KEY_HIVE_ENTRY set
TRUE);
if (newroot == HCELL_NIL) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorInsufficientResources;
}
TmpCmHive->Hive.BaseBlock->RootCell = newroot;
// Do a tree copy. Copy the HighCell tree from HighHive first.
if (CmpCopyTree(HighHive, HighCell, &(TmpCmHive->Hive), newroot) == FALSE) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorInsufficientResources;
}
// Merge the values in the root node of the merged subtrees
LowNode = (PCM_KEY_NODE)HvGetCell(LowHive, LowCell);
HighNode = (PCM_KEY_NODE)HvGetCell(&(TmpCmHive->Hive),newroot);
if (CmpMergeKeyValues(LowHive, LowCell, LowNode, &(TmpCmHive->Hive), newroot, HighNode) == FALSE ){
status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorInsufficientResources;
}
CMLOG(CML_FLOW, CMS_SAVRES) KdPrint(("\tMerge partial LowHive over the HighHive\n"));
// Merge the two trees. A Merge operation is a sync that obeys the following aditional rules:
// 1. keys the exist in the taget tree and does not exist in the source tree remain as they are (don't get deleted)
// 2. keys the doesn't exist both in the target tree are added "as they are" from the source tree (always the target tree has a higher precedence)
if (CmpMergeTrees(LowHive, LowCell, &(TmpCmHive->Hive), newroot) == FALSE) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorInsufficientResources;
}
// Write the file
TmpCmHive->FileHandles[HFILE_TYPE_EXTERNAL] = FileHandle;
status = HvWriteHive(&(TmpCmHive->Hive));
TmpCmHive->FileHandles[HFILE_TYPE_EXTERNAL] = NULL;
// Error exits
ErrorInsufficientResources:
// Free the temporary hive
if (TmpCmHive != NULL) {
CmpDestroyTemporaryHive(TmpCmHive);
}
#if DBG
// Sanity check: when this assert fires, we have leaks in the merge routine.
ASSERT( OldQuotaUsed == CmpGlobalQuotaUsed );
#endif
// Set global quota back to what it was.
CmpGlobalQuotaAllowed = OldQuotaAllowed;
CmpGlobalQuotaWarning = OldQuotaWarning;
DCmCheckRegistry(CONTAINING_RECORD(HighHive, CMHIVE, Hive));
DCmCheckRegistry(CONTAINING_RECORD(LowHive, CMHIVE, Hive));
CmpUnlockRegistry();
return status;
}
NTSTATUS CmpSaveKeyByFileCopy(PCMHIVE CmHive, HANDLE FileHandle)
/*++
Routine Description:
Do special case of SaveKey by copying the hive file
Arguments:
CmHive - supplies a pointer to an HHive
FileHandle - open handle to target file
Return Value:
NTSTATUS - Result code from call, among the following:
--*/
{
PHBASE_BLOCK BaseBlock;
NTSTATUS status;
ULONG Offset;
ULONG Length;
ULONG Position;
REGISTRY_COMMAND CommandArea;
PUCHAR CopyBuffer;
ULONG BufferLength;
ULONG BytesToCopy;
CMP_OFFSET_ARRAY offsetElement;
PAGED_CODE();
// Attempt to allocate large buffer for copying stuff around. If
// we can't get one, just use the stash buffer.
BufferLength = CM_SAVEKEYBUFSIZE;
try {
CopyBuffer = ExAllocatePoolWithQuota(PagedPoolCacheAligned, BufferLength);
} except(EXCEPTION_EXECUTE_HANDLER) {
CopyBuffer = NULL;
}
CmpLockRegistryExclusive();
if (CopyBuffer == NULL) {
CopyBuffer = CmpStashBuffer;
BufferLength = HBLOCK_SIZE;
}
// Read the base block, step the sequence number, and write it out
status = STATUS_REGISTRY_IO_FAILED;
CmHive->FileHandles[HFILE_TYPE_EXTERNAL] = FileHandle;
Offset = 0;
CommandArea.Command = REG_CMD_HIVE_READ;
CommandArea.CmHive = CmHive;
CommandArea.FileType = HFILE_TYPE_PRIMARY;
CommandArea.Offset = &Offset;
CommandArea.Buffer = CopyBuffer;
CommandArea.FileSize = HBLOCK_SIZE;
CmpWorker(&CommandArea);
if (! NT_SUCCESS(CommandArea.Status)) {
goto ErrorExit;
}
BaseBlock = (PHBASE_BLOCK)CopyBuffer;
Length = BaseBlock->Length;
BaseBlock->Sequence1++;
Offset = 0;
offsetElement.FileOffset = Offset;
offsetElement.DataBuffer = CopyBuffer;
offsetElement.DataLength = HBLOCK_SIZE;
if ( ! CmpFileWrite((PHHIVE)CmHive, HFILE_TYPE_EXTERNAL, &offsetElement, 1, &Offset))
{
goto ErrorExit;
}
// Flush the external, so header will show corrupt until we're done
if (CmpFileFlush((PHHIVE)CmHive, HFILE_TYPE_EXTERNAL)) {
status = STATUS_SUCCESS;
}
// For span of data, read from master and write to external
for (Position = 0; Position < Length; Position += BytesToCopy) {
Offset = Position + HBLOCK_SIZE;
BytesToCopy = Length-Position;
if (BytesToCopy > BufferLength) {
BytesToCopy = BufferLength;
}
CommandArea.Command = REG_CMD_HIVE_READ;
CommandArea.CmHive = CmHive;
CommandArea.FileType = HFILE_TYPE_PRIMARY;
CommandArea.Offset = &Offset;
CommandArea.Buffer = CopyBuffer;
CommandArea.FileSize = BytesToCopy;
CmpWorker(&CommandArea);
if (! NT_SUCCESS(CommandArea.Status)) {
goto ErrorExit;
}
Offset = Position + HBLOCK_SIZE;
offsetElement.FileOffset = Offset;
offsetElement.DataBuffer = CopyBuffer;
offsetElement.DataLength = BytesToCopy;
if ( ! CmpFileWrite((PHHIVE)CmHive, HFILE_TYPE_EXTERNAL, &offsetElement, 1, &Offset))
{
goto ErrorExit;
}
}
// Flush the external, so data is there before we update the header
if (CmpFileFlush((PHHIVE)CmHive, HFILE_TYPE_EXTERNAL)) {
status = STATUS_SUCCESS;
}
// Reread the base block, sync the seq #, rewrite it.
// (Brute force, but means no memory alloc - always works)
Offset = 0;
CommandArea.Command = REG_CMD_HIVE_READ;
CommandArea.CmHive = CmHive;
CommandArea.FileType = HFILE_TYPE_PRIMARY;
CommandArea.Offset = &Offset;
CommandArea.Buffer = CopyBuffer;
CommandArea.FileSize = HBLOCK_SIZE;
CmpWorker(&CommandArea);
if (! NT_SUCCESS(CommandArea.Status)) {
goto ErrorExit;
}
BaseBlock->Sequence1++; // it got trampled when we reread it
BaseBlock->Sequence2++;
Offset = 0;
offsetElement.FileOffset = Offset;
offsetElement.DataBuffer = CopyBuffer;
offsetElement.DataLength = HBLOCK_SIZE;
if ( ! CmpFileWrite((PHHIVE)CmHive, HFILE_TYPE_EXTERNAL, &offsetElement, 1, &Offset))
{
goto ErrorExit;
}
// Flush the external, and we are done
if (CmpFileFlush((PHHIVE)CmHive, HFILE_TYPE_EXTERNAL)) {
status = STATUS_SUCCESS;
}
ErrorExit:
if (CopyBuffer != CmpStashBuffer) {
ExFreePool(CopyBuffer);
}
CmHive->FileHandles[HFILE_TYPE_EXTERNAL] = NULL;
CmpUnlockRegistry();
return status;
}
PCMHIVE CmpCreateTemporaryHive(IN HANDLE FileHandle)
/*++
Routine Description:
Allocates and inits a temporary hive.
Arguments:
FileHandle - Supplies the handle of the file to back the hive.
Return Value:
Pointer to CmHive.
If NULL the operation failed.
--*/
{
PCMHIVE TempHive;
NTSTATUS Status;
PAGED_CODE();
// NOTE: Hive will get put on CmpHiveListHead list.
// Make sure CmpDestroyTemporaryHive gets called to remove it.
Status = CmpInitializeHive(&TempHive,HINIT_CREATE,HIVE_VOLATILE,0,NULL,NULL,NULL,NULL,NULL,NULL);
if (NT_SUCCESS(Status)) {
return(TempHive);
} else {
return(NULL);
}
}
VOID CmpDestroyTemporaryHive(PCMHIVE CmHive)
/*++
Routine Description:
Frees all the pieces of a hive.
Arguments:
CmHive - CM level hive structure to free
--*/
{
PAGED_CODE();
CMLOG(CML_MINOR, CMS_SAVRES) {
KdPrint(("CmpDestroyTemporaryHive:\n"));
KdPrint(("\tCmHive=%08lx\n", CmHive));
}
if (CmHive == NULL) {
return;
}
// NOTE: Hive is on CmpHiveListHead list.
// Remove it.
RemoveEntryList(&CmHive->HiveList);
HvFreeHive(&(CmHive->Hive));
ASSERT( CmHive->HiveLock );
ExFreePool(CmHive->HiveLock);
CmpFree(CmHive, sizeof(CMHIVE));
}