mirror of
https://github.com/reactos/reactos.git
synced 2024-11-18 13:01:40 +00:00
2764 lines
82 KiB
C
2764 lines
82 KiB
C
/*
|
|
* PROJECT: ReactOS Kernel
|
|
* LICENSE: GPL - See COPYING in the top level directory
|
|
* FILE: ntoskrnl/config/cmapi.c
|
|
* 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 ******************************************************************/
|
|
|
|
#include "ntoskrnl.h"
|
|
#define NDEBUG
|
|
#include "debug.h"
|
|
|
|
/* 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)
|
|
{
|
|
PLIST_ENTRY NextEntry;
|
|
PCMHIVE Hive;
|
|
NTSTATUS Status;
|
|
BOOLEAN Result = TRUE;
|
|
|
|
/* Make sure that the registry isn't read-only now */
|
|
if (CmpNoWrite) return TRUE;
|
|
|
|
/* Otherwise, acquire the hive list lock and disable force flush */
|
|
CmpForceForceFlush = FALSE;
|
|
ExAcquirePushLockShared(&CmpHiveListHeadLock);
|
|
|
|
/* Loop the hive list */
|
|
NextEntry = CmpHiveListHead.Flink;
|
|
while (NextEntry != &CmpHiveListHead)
|
|
{
|
|
/* Get the hive */
|
|
Hive = CONTAINING_RECORD(NextEntry, CMHIVE, HiveList);
|
|
if (!(Hive->Hive.HiveFlags & HIVE_NOLAZYFLUSH))
|
|
{
|
|
/* Acquire the flusher lock */
|
|
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;
|
|
}
|
|
else
|
|
{
|
|
/* We won't flush if the hive might shrink */
|
|
Result = FALSE;
|
|
CmpForceForceFlush = TRUE;
|
|
}
|
|
|
|
/* Release the flusher lock */
|
|
CmpUnlockHiveFlusher(Hive);
|
|
}
|
|
|
|
/* Try the next entry */
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
/* Release lock and return */
|
|
ExReleasePushLock(&CmpHiveListHeadLock);
|
|
return Result;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CmpSetValueKeyNew(IN PHHIVE Hive,
|
|
IN PCM_KEY_NODE Parent,
|
|
IN PUNICODE_STRING ValueName,
|
|
IN ULONG Index,
|
|
IN ULONG Type,
|
|
IN PVOID Data,
|
|
IN ULONG DataSize,
|
|
IN ULONG StorageType,
|
|
IN ULONG SmallData)
|
|
{
|
|
PCELL_DATA CellData;
|
|
HCELL_INDEX ValueCell;
|
|
NTSTATUS Status;
|
|
|
|
/* Check if we already have a value list */
|
|
if (Parent->ValueList.Count)
|
|
{
|
|
/* Then make sure it's valid and dirty it */
|
|
ASSERT(Parent->ValueList.List != HCELL_NIL);
|
|
if (!HvMarkCellDirty(Hive, Parent->ValueList.List, FALSE))
|
|
{
|
|
/* Fail if we're out of space for log changes */
|
|
return STATUS_NO_LOG_SPACE;
|
|
}
|
|
}
|
|
|
|
/* Allocate a value cell */
|
|
ValueCell = HvAllocateCell(Hive,
|
|
FIELD_OFFSET(CM_KEY_VALUE, Name) +
|
|
CmpNameSize(Hive, ValueName),
|
|
StorageType,
|
|
HCELL_NIL);
|
|
if (ValueCell == HCELL_NIL) return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
/* Get the actual data for it */
|
|
CellData = HvGetCell(Hive, ValueCell);
|
|
if (!CellData) ASSERT(FALSE);
|
|
|
|
/* Now we can release it, make sure it's also dirty */
|
|
HvReleaseCell(Hive, ValueCell);
|
|
ASSERT(HvIsCellDirty(Hive, ValueCell));
|
|
|
|
/* Set it up and copy the name */
|
|
CellData->u.KeyValue.Signature = CM_KEY_VALUE_SIGNATURE;
|
|
_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)
|
|
{
|
|
/* Build a data cell for it */
|
|
Status = CmpSetValueDataNew(Hive,
|
|
Data,
|
|
DataSize,
|
|
StorageType,
|
|
ValueCell,
|
|
&CellData->u.KeyValue.Data);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* We failed, free the cell */
|
|
HvFreeCell(Hive, ValueCell);
|
|
return Status;
|
|
}
|
|
|
|
/* Otherwise, set the data length, and make sure the data is dirty */
|
|
CellData->u.KeyValue.DataLength = DataSize;
|
|
ASSERT(HvIsCellDirty(Hive, CellData->u.KeyValue.Data));
|
|
}
|
|
else
|
|
{
|
|
/* This is a small key, set the data directly inside */
|
|
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,
|
|
ValueCell,
|
|
Index,
|
|
StorageType,
|
|
&Parent->ValueList);
|
|
|
|
/* If we failed, free the entire cell, including the data */
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Overwrite the status with a known one */
|
|
CmpFreeValue(Hive, ValueCell);
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
/* Return Status */
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CmpSetValueKeyExisting(IN PHHIVE Hive,
|
|
IN HCELL_INDEX OldChild,
|
|
IN PCM_KEY_VALUE Value,
|
|
IN ULONG Type,
|
|
IN PVOID Data,
|
|
IN ULONG DataSize,
|
|
IN ULONG StorageType,
|
|
IN ULONG TempData)
|
|
{
|
|
HCELL_INDEX DataCell, NewCell;
|
|
PCELL_DATA CellData;
|
|
ULONG Length;
|
|
BOOLEAN WasSmall, IsSmall;
|
|
|
|
/* Registry writes must be blocked */
|
|
CMP_ASSERT_FLUSH_LOCK(Hive);
|
|
|
|
/* Mark the old child cell dirty */
|
|
if (!HvMarkCellDirty(Hive, OldChild, FALSE)) return STATUS_NO_LOG_SPACE;
|
|
|
|
/* See if this is a small or normal key */
|
|
WasSmall = CmpIsKeyValueSmall(&Length, Value->DataLength);
|
|
|
|
/* See if our new data can fit in a small key */
|
|
IsSmall = (DataSize <= CM_KEY_VALUE_SMALL) ? TRUE: FALSE;
|
|
|
|
/* Big keys are unsupported */
|
|
ASSERT_VALUE_BIG(Hive, Length);
|
|
ASSERT_VALUE_BIG(Hive, DataSize);
|
|
|
|
/* Mark the old value dirty */
|
|
if (!CmpMarkValueDataDirty(Hive, Value)) return STATUS_NO_LOG_SPACE;
|
|
|
|
/* Check if we have a small key */
|
|
if (IsSmall)
|
|
{
|
|
/* Check if we had a normal key with some data in it */
|
|
if (!(WasSmall) && (Length > 0))
|
|
{
|
|
/* Free the previous data */
|
|
CmpFreeValueData(Hive, Value->Data, Length);
|
|
}
|
|
|
|
/* Write our data directly */
|
|
Value->DataLength = DataSize + CM_KEY_VALUE_SPECIAL_SIZE;
|
|
Value->Data = TempData;
|
|
Value->Type = Type;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
/* Immediately release the cell */
|
|
HvReleaseCell(Hive, DataCell);
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
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);
|
|
|
|
/* 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;
|
|
|
|
/* Return success */
|
|
ASSERT(HvIsCellDirty(Hive, NewCell));
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
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)
|
|
{
|
|
NTSTATUS Status;
|
|
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)
|
|
{
|
|
/* 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;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
/* 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
|
|
{
|
|
/* 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)
|
|
{
|
|
/* 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->KeyFullInformation.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);
|
|
Kcb->KcbLastWriteTime = Parent->LastWriteTime;
|
|
|
|
/* Check if the cell is cached */
|
|
if ((Found) && (CMP_IS_CELL_CACHED(Kcb->ValueCache.ValueList)))
|
|
{
|
|
/* Shouldn't happen */
|
|
ASSERT(FALSE);
|
|
}
|
|
else
|
|
{
|
|
/* 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 = 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;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CmDeleteValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
|
|
IN UNICODE_STRING ValueName)
|
|
{
|
|
NTSTATUS Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
PHHIVE Hive;
|
|
PCM_KEY_NODE Parent;
|
|
HCELL_INDEX ChildCell, Cell;
|
|
PCHILD_LIST ChildList;
|
|
PCM_KEY_VALUE Value = NULL;
|
|
ULONG ChildIndex;
|
|
BOOLEAN Result;
|
|
|
|
/* Acquire hive lock */
|
|
CmpLockRegistry();
|
|
|
|
/* Lock KCB exclusively */
|
|
CmpAcquireKcbLockExclusive(Kcb);
|
|
|
|
/* Don't touch deleted keys */
|
|
if (Kcb->Delete)
|
|
{
|
|
/* Undo everything */
|
|
CmpReleaseKcbLock(Kcb);
|
|
CmpUnlockRegistry();
|
|
return STATUS_KEY_DELETED;
|
|
}
|
|
|
|
/* 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);
|
|
ASSERT(Parent);
|
|
|
|
/* Get the value list and check if it has any entries */
|
|
ChildList = &Parent->ValueList;
|
|
if (ChildList->Count)
|
|
{
|
|
/* Try to find this value */
|
|
Result = CmpFindNameInList(Hive,
|
|
ChildList,
|
|
&ValueName,
|
|
&ChildIndex,
|
|
&ChildCell);
|
|
if (!Result)
|
|
{
|
|
/* Fail */
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Quickie;
|
|
}
|
|
|
|
/* Value not found, return error */
|
|
if (ChildCell == HCELL_NIL) goto Quickie;
|
|
|
|
/* We found the value, mark all relevant cells dirty */
|
|
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);
|
|
ASSERT(Value);
|
|
|
|
/* Mark it and all related data as dirty */
|
|
if (!CmpMarkValueDataDirty(Hive, Value))
|
|
{
|
|
/* Not enough log space, fail */
|
|
Status = STATUS_NO_LOG_SPACE;
|
|
goto Quickie;
|
|
}
|
|
|
|
/* Sanity checks */
|
|
ASSERT(HvIsCellDirty(Hive, Parent->ValueList.List));
|
|
ASSERT(HvIsCellDirty(Hive, ChildCell));
|
|
|
|
/* Remove the value from the child list */
|
|
Status = CmpRemoveValueFromList(Hive, ChildIndex, ChildList);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Set known error */
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Quickie;
|
|
}
|
|
|
|
/* Remove the value and its data itself */
|
|
if (!CmpFreeValue(Hive, ChildCell))
|
|
{
|
|
/* Failed to free the value, fail */
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Quickie;
|
|
}
|
|
|
|
/* Set the last write time */
|
|
KeQuerySystemTime(&Parent->LastWriteTime);
|
|
Kcb->KcbLastWriteTime = Parent->LastWriteTime;
|
|
|
|
/* Sanity check */
|
|
ASSERT(Parent->MaxValueNameLen == Kcb->KcbMaxValueNameLen);
|
|
ASSERT(Parent->MaxValueDataLen == Kcb->KcbMaxValueDataLen);
|
|
ASSERT(HvIsCellDirty(Hive, Cell));
|
|
|
|
/* Check if the value list is empty now */
|
|
if (!Parent->ValueList.Count)
|
|
{
|
|
/* Then clear key node data */
|
|
Parent->MaxValueNameLen = 0;
|
|
Parent->MaxValueDataLen = 0;
|
|
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);
|
|
|
|
/* Change default Status to success */
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
Quickie:
|
|
/* Release the parent cell, if any */
|
|
if (Parent) HvReleaseCell(Hive, Cell);
|
|
|
|
/* Check if we had a value */
|
|
if (Value)
|
|
{
|
|
/* Release the child cell */
|
|
ASSERT(ChildCell != HCELL_NIL);
|
|
HvReleaseCell(Hive, ChildCell);
|
|
}
|
|
|
|
/* Release locks */
|
|
CmpUnlockHiveFlusher((PCMHIVE)Hive);
|
|
CmpReleaseKcbLock(Kcb);
|
|
CmpUnlockRegistry();
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CmQueryValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
|
|
IN UNICODE_STRING ValueName,
|
|
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);
|
|
|
|
/* 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);
|
|
_SEH2_YIELD(goto DoAgain);
|
|
}
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
Status = _SEH2_GetExceptionCode();
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
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)
|
|
{
|
|
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_CACHED_VALUE *CachedValue;
|
|
PCM_KEY_VALUE ValueData = NULL;
|
|
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;
|
|
}
|
|
|
|
/* Get the hive and parent */
|
|
Hive = Kcb->KeyHive;
|
|
Parent = (PCM_KEY_NODE)HvGetCell(Hive, Kcb->KeyCell);
|
|
ASSERT(Parent);
|
|
|
|
/* FIXME: Lack of cache? */
|
|
if (Kcb->ValueCache.Count != Parent->ValueList.Count)
|
|
{
|
|
DPRINT1("HACK: Overriding value cache count\n");
|
|
Kcb->ValueCache.Count = Parent->ValueList.Count;
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* 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);
|
|
|
|
/* 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)
|
|
{
|
|
/* Cleanup state */
|
|
ASSERT(CellToRelease2 == HCELL_NIL);
|
|
if (CellToRelease)
|
|
{
|
|
HvReleaseCell(Hive, CellToRelease);
|
|
CellToRelease = HCELL_NIL;
|
|
}
|
|
HvReleaseCell(Hive, Kcb->KeyCell);
|
|
|
|
/* Try with exclusive KCB lock */
|
|
CmpConvertKcbSharedToExclusive(Kcb);
|
|
goto DoAgain;
|
|
}
|
|
else if (Result != SearchSuccess)
|
|
{
|
|
/* Sanity check */
|
|
ASSERT(ValueData == NULL);
|
|
|
|
/* 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);
|
|
|
|
/* Try with exclusive KCB lock */
|
|
CmpConvertKcbSharedToExclusive(Kcb);
|
|
_SEH2_YIELD(goto DoAgain);
|
|
}
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Get exception code */
|
|
Status = _SEH2_GetExceptionCode();
|
|
}
|
|
_SEH2_END;
|
|
|
|
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;
|
|
}
|
|
|
|
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,
|
|
_Out_opt_ PVOID KeyInformation,
|
|
_In_ ULONG Length,
|
|
_Out_ PULONG ResultLength)
|
|
{
|
|
NTSTATUS Status;
|
|
PHHIVE Hive;
|
|
PCM_KEY_NODE Parent;
|
|
HV_TRACK_CELL_REF CellReferences = {0};
|
|
|
|
/* Acquire hive lock */
|
|
CmpLockRegistry();
|
|
|
|
/* Lock KCB shared */
|
|
CmpAcquireKcbLockShared(Kcb);
|
|
|
|
/* Don't touch deleted keys */
|
|
if (Kcb->Delete)
|
|
{
|
|
/* Fail */
|
|
Status = STATUS_KEY_DELETED;
|
|
goto Quickie;
|
|
}
|
|
|
|
/* Data can be user-mode, use SEH */
|
|
_SEH2_TRY
|
|
{
|
|
/* Check what class we got */
|
|
switch (KeyInformationClass)
|
|
{
|
|
/* 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))
|
|
{
|
|
/* Not enough memory to track references */
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
else
|
|
{
|
|
/* Call the internal API */
|
|
Status = CmpQueryKeyData(Hive,
|
|
Parent,
|
|
KeyInformationClass,
|
|
KeyInformation,
|
|
Length,
|
|
ResultLength);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case KeyCachedInformation:
|
|
{
|
|
/* Call the internal API */
|
|
Status = CmpQueryKeyDataFromCache(Kcb,
|
|
KeyInformation,
|
|
Length,
|
|
ResultLength);
|
|
break;
|
|
}
|
|
|
|
case KeyFlagsInformation:
|
|
{
|
|
/* Call the internal API */
|
|
Status = CmpQueryFlagsInformation(Kcb,
|
|
KeyInformation,
|
|
Length,
|
|
ResultLength);
|
|
break;
|
|
}
|
|
|
|
case KeyNameInformation:
|
|
{
|
|
/* Call the internal API */
|
|
Status = CmpQueryNameInformation(Kcb,
|
|
KeyInformation,
|
|
Length,
|
|
ResultLength);
|
|
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 */
|
|
HvReleaseFreeCellRefArray(&CellReferences);
|
|
|
|
/* Release locks */
|
|
CmpReleaseKcbLock(Kcb);
|
|
CmpUnlockRegistry();
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CmEnumerateKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
|
|
IN ULONG Index,
|
|
IN KEY_INFORMATION_CLASS KeyInformationClass,
|
|
IN PVOID KeyInformation,
|
|
IN ULONG Length,
|
|
IN PULONG ResultLength)
|
|
{
|
|
NTSTATUS Status;
|
|
PHHIVE Hive;
|
|
PCM_KEY_NODE Parent, Child;
|
|
HCELL_INDEX ChildCell;
|
|
HV_TRACK_CELL_REF CellReferences = {0};
|
|
|
|
/* Acquire hive lock */
|
|
CmpLockRegistry();
|
|
|
|
/* Lock the KCB shared */
|
|
CmpAcquireKcbLockShared(Kcb);
|
|
|
|
/* Don't touch deleted keys */
|
|
if (Kcb->Delete)
|
|
{
|
|
/* Undo everything */
|
|
Status = STATUS_KEY_DELETED;
|
|
goto Quickie;
|
|
}
|
|
|
|
/* Get the hive and parent */
|
|
Hive = Kcb->KeyHive;
|
|
Parent = (PCM_KEY_NODE)HvGetCell(Hive, Kcb->KeyCell);
|
|
ASSERT(Parent);
|
|
|
|
/* Get the child cell */
|
|
ChildCell = CmpFindSubKeyByNumber(Hive, Parent, Index);
|
|
|
|
/* Release the parent cell */
|
|
HvReleaseCell(Hive, Kcb->KeyCell);
|
|
|
|
/* Check if we found the child */
|
|
if (ChildCell == HCELL_NIL)
|
|
{
|
|
/* We didn't, fail */
|
|
Status = STATUS_NO_MORE_ENTRIES;
|
|
goto Quickie;
|
|
}
|
|
|
|
/* Now get the actual child node */
|
|
Child = (PCM_KEY_NODE)HvGetCell(Hive, ChildCell);
|
|
ASSERT(Child);
|
|
|
|
/* Track references */
|
|
if (!HvTrackCellRef(&CellReferences, Hive, ChildCell))
|
|
{
|
|
/* Can't allocate memory for tracking */
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Quickie;
|
|
}
|
|
|
|
/* 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();
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CmDeleteKey(IN PCM_KEY_BODY KeyBody)
|
|
{
|
|
NTSTATUS Status;
|
|
PHHIVE Hive;
|
|
PCM_KEY_NODE Node, Parent;
|
|
HCELL_INDEX Cell, ParentCell;
|
|
PCM_KEY_CONTROL_BLOCK Kcb;
|
|
|
|
/* Acquire hive lock */
|
|
CmpLockRegistry();
|
|
|
|
/* Get the kcb */
|
|
Kcb = KeyBody->KeyControlBlock;
|
|
|
|
/* Don't allow deleting the root */
|
|
if (!Kcb->ParentKcb)
|
|
{
|
|
/* Fail */
|
|
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 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);
|
|
|
|
/* Check if we don't have any children */
|
|
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);
|
|
|
|
/* Get the parent node */
|
|
Parent = (PCM_KEY_NODE)HvGetCell(Hive, ParentCell);
|
|
if (Parent)
|
|
{
|
|
/* Update the maximum name length */
|
|
Kcb->ParentKcb->KcbMaxNameLen = (USHORT)Parent->MaxNameLen;
|
|
|
|
/* Make sure we're dirty */
|
|
ASSERT(HvIsCellDirty(Hive, ParentCell));
|
|
|
|
/* Update the write time */
|
|
KeQuerySystemTime(&Parent->LastWriteTime);
|
|
Kcb->ParentKcb->KcbLastWriteTime = Parent->LastWriteTime;
|
|
|
|
/* Release the cell */
|
|
HvReleaseCell(Hive, ParentCell);
|
|
}
|
|
|
|
/* Set the KCB in delete mode and remove it */
|
|
Kcb->Delete = TRUE;
|
|
CmpRemoveKeyControlBlock(Kcb);
|
|
|
|
/* Clear the cell */
|
|
Kcb->KeyCell = HCELL_NIL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Fail */
|
|
Status = STATUS_CANNOT_DELETE;
|
|
}
|
|
|
|
/* Release the cell */
|
|
HvReleaseCell(Hive, Cell);
|
|
|
|
/* Release flush lock */
|
|
CmpUnlockHiveFlusher((PCMHIVE)Hive);
|
|
|
|
/* Release the KCB locks */
|
|
Quickie:
|
|
CmpReleaseTwoKcbLockByKey(Kcb->ConvKey, Kcb->ParentKcb->ConvKey);
|
|
|
|
/* Release hive lock */
|
|
CmpUnlockRegistry();
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CmFlushKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
|
|
IN BOOLEAN ExclusiveLock)
|
|
{
|
|
PCMHIVE CmHive;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PHHIVE Hive;
|
|
|
|
/* 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)
|
|
{
|
|
/* Flush all the hives instead */
|
|
CmpDoFlushAll(FALSE);
|
|
}
|
|
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(CmHive);
|
|
}
|
|
|
|
/* Return the status */
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CmLoadKey(IN POBJECT_ATTRIBUTES TargetKey,
|
|
IN POBJECT_ATTRIBUTES SourceFile,
|
|
IN ULONG Flags,
|
|
IN PCM_KEY_BODY KeyBody)
|
|
{
|
|
SECURITY_QUALITY_OF_SERVICE ServiceQos;
|
|
SECURITY_CLIENT_CONTEXT ClientSecurityContext;
|
|
HANDLE KeyHandle;
|
|
BOOLEAN Allocate = TRUE;
|
|
PCMHIVE CmHive, LoadedHive;
|
|
NTSTATUS Status;
|
|
CM_PARSE_CONTEXT ParseContext;
|
|
|
|
/* Check if we have a trust key */
|
|
if (KeyBody)
|
|
{
|
|
/* Fail */
|
|
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;
|
|
ServiceQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
|
|
ServiceQos.EffectiveOnly = TRUE;
|
|
Status = SeCreateClientSecurity(PsGetCurrentThread(),
|
|
&ServiceQos,
|
|
FALSE,
|
|
&ClientSecurityContext);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Fail */
|
|
DPRINT1("Security context failed\n");
|
|
return Status;
|
|
}
|
|
|
|
/* Open the target key */
|
|
RtlZeroMemory(&ParseContext, sizeof(ParseContext));
|
|
ParseContext.CreateOperation = FALSE;
|
|
Status = ObOpenObjectByName(TargetKey,
|
|
CmpKeyObjectType,
|
|
KernelMode,
|
|
NULL,
|
|
KEY_READ,
|
|
&ParseContext,
|
|
&KeyHandle);
|
|
if (!NT_SUCCESS(Status)) KeyHandle = NULL;
|
|
|
|
/* Open the hive */
|
|
Status = CmpCmdHiveOpen(SourceFile,
|
|
&ClientSecurityContext,
|
|
&Allocate,
|
|
&CmHive,
|
|
0);
|
|
|
|
/* Get rid of the security context */
|
|
SeDeleteClientSecurity(&ClientSecurityContext);
|
|
|
|
/* See if we failed */
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* See if the target already existed */
|
|
if (KeyHandle)
|
|
{
|
|
/* Lock the registry */
|
|
CmpLockRegistryExclusive();
|
|
|
|
/* 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);
|
|
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,
|
|
CmHive,
|
|
Allocate,
|
|
TargetKey->SecurityDescriptor);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Add to HiveList key */
|
|
CmpAddToHiveFileList(CmHive);
|
|
|
|
/* Sync the hive if necessary */
|
|
if (Allocate)
|
|
{
|
|
/* 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
|
|
{
|
|
DPRINT1("CmpLinkHiveToMaster failed, Status %lx\n", Status);
|
|
/* 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 */
|
|
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)
|
|
{
|
|
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
|
|
CmpEnumerateOpenSubKeys(
|
|
IN PCM_KEY_CONTROL_BLOCK RootKcb,
|
|
IN BOOLEAN RemoveEmptyCacheEntries,
|
|
IN BOOLEAN DereferenceOpenedEntries)
|
|
{
|
|
PCM_KEY_HASH Entry;
|
|
PCM_KEY_CONTROL_BLOCK CachedKcb;
|
|
PCM_KEY_CONTROL_BLOCK ParentKcb;
|
|
ULONG ParentKeyCount;
|
|
ULONG i, j;
|
|
ULONG SubKeys = 0;
|
|
|
|
DPRINT("CmpEnumerateOpenSubKeys() called\n");
|
|
|
|
/* The root key is the only referenced key. There are no referenced 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, RefCount = %u\n", CachedKcb->RefCount);
|
|
|
|
if (CachedKcb->RefCount > 0)
|
|
{
|
|
DPRINT("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)
|
|
{
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|