2876 lines
73 KiB
C
2876 lines
73 KiB
C
/*
|
|
|
|
|
|
|
|
Copyright (c) 1991 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
RegeCls.c
|
|
|
|
Abstract:
|
|
|
|
This module contains helper functions for enumerating
|
|
class registrations via the win32 RegEnumKeyEx api
|
|
|
|
Author:
|
|
|
|
Adam Edwards (adamed) 06-May-1998
|
|
|
|
Key Functions:
|
|
|
|
EnumTableGetNextEnum
|
|
EnumTableRemoveKey
|
|
InitializeClassesEnumTable
|
|
ClassKeyCountSubKeys
|
|
|
|
Notes:
|
|
|
|
Starting with NT5, the HKEY_CLASSES_ROOT key is per-user
|
|
instead of per-machine -- previously, HKCR was an alias for
|
|
HKLM\Software\Classes. Please see regclass.c for more information
|
|
on this functionality.
|
|
|
|
This feature complicates registry key enumeration because certain keys,
|
|
such as CLSID, can have some subkeys that come from HKLM\Software\Classes, and
|
|
other subkeys that come from HKCU\Software\Classes. Since the feature is
|
|
implemented in user mode, the kernel mode apis know nothing of this. When it's
|
|
time to enumerate keys, the kernel doesn't know that it should enumerate keys from
|
|
two different parent keys.
|
|
|
|
The key problem is that keys with the same name can exist in the user and machine portions.
|
|
When this happens, we choose the user portion is belonging to HKCR -- the other
|
|
one does not exist -- it is "overridden" by the user version. This means that
|
|
we cannot simply enumerate from both places and return the results -- we would
|
|
get duplicates in this case. Thus, we have to do work in user mode to make
|
|
sure duplicates are not returned.
|
|
|
|
This module provides the user mode implementation for enumerating class
|
|
registration keys in HKEY_CLASSES_ROOT.
|
|
|
|
The general method is to maintain state between each call to RegEnumKeyEx. The
|
|
state is kept in a global table indexed by registry key handle and thread id. The
|
|
state allows the api to remember where it is in the enumeration. The rest of the code
|
|
handles finding the next key, which is accomplished by retrieving keys from both user
|
|
and machine locations. Since the kernel returns keys from either of these locations in
|
|
sorted order, we can compare the key names and return whichever one is less or greater,
|
|
depending on if we're enumerating upward or downward. We keep track of where
|
|
we are for both user and machine locations, so we know which key to enumerate
|
|
next and when to stop.
|
|
|
|
|
|
IMPORTANT ASSUMPTIONS:
|
|
|
|
|
|
BUGBUG: This code assumes that the caller has both query permission and enumerate subkey
|
|
permission in the registry key's acl -- some calls may fail with access denied if the
|
|
acl denies access to the caller.
|
|
|
|
--*/
|
|
|
|
|
|
#ifdef LOCAL
|
|
|
|
#include <rpc.h>
|
|
#include "regrpc.h"
|
|
#include "localreg.h"
|
|
#include "regclass.h"
|
|
#include "regecls.h"
|
|
#include <malloc.h>
|
|
|
|
|
|
NTSTATUS QueryKeyInfo(
|
|
HKEY hKey,
|
|
PKEY_FULL_INFORMATION* ppKeyFullInfo,
|
|
ULONG BufferLength,
|
|
BOOL fClass,
|
|
USHORT MaxClassLength);
|
|
|
|
|
|
// Global table of registry key enumeration state. This is initialized
|
|
// at dll initialize time.
|
|
|
|
EnumTable gClassesEnumTable;
|
|
|
|
|
|
// Global indicating need for calling thread detach routines
|
|
|
|
BOOL gbDllHasThreadState = FALSE;
|
|
|
|
// BUGBUG: This recordset merging is all in user mode -- should be moved to the kernel for perf and
|
|
// other reasons.
|
|
|
|
BOOL InitializeClassesEnumTable()
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Initializes the global classes enumeration table when
|
|
advapi32.dll is initialized.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
Returns TRUE for success, FALSE for failure
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
|
|
// Init the classes enumeration table
|
|
|
|
Status = EnumTableInit(&gClassesEnumTable);
|
|
|
|
return NT_SUCCESS(Status);
|
|
}
|
|
|
|
BOOL CleanupClassesEnumTable(BOOL fThisThreadOnly)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Uninitializes the global classes enumeration table when
|
|
advapi32.dll is unloaded -- this frees all
|
|
heap associated with the enumeration table, including
|
|
that for keys which have not been closed. Other resources
|
|
required for the table are also freed.
|
|
|
|
Arguments:
|
|
|
|
dwCriteria - if this is ENUM_TABLE_REMOVEKEY_CRITERIA_THISTHREAD,
|
|
then only the table entries concerning this thread are cleaned up.
|
|
If it is ENUM_TABLE_REMOVEKEY_CRITERIA_ANYTHREAD, the table entries
|
|
for all threads in the process are cleaned up.
|
|
|
|
Return Value:
|
|
|
|
TRUE for success, FALSE otherwise.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
DWORD dwCriteria;
|
|
|
|
dwCriteria = fThisThreadOnly ? ENUM_TABLE_REMOVEKEY_CRITERIA_THISTHREAD :
|
|
ENUM_TABLE_REMOVEKEY_CRITERIA_ANYTHREAD;
|
|
|
|
|
|
// Clear our enumeration table
|
|
|
|
Status = EnumTableClear(&gClassesEnumTable, dwCriteria);
|
|
|
|
return NT_SUCCESS(Status);
|
|
}
|
|
|
|
NTSTATUS EnumTableInit(EnumTable* pEnumTable)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Initializes an enumeration state table
|
|
|
|
Arguments:
|
|
|
|
pEnumTable - table to initialize
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
EnumState* rgNewState;
|
|
|
|
#if defined(_REGCLASS_ENUMTABLE_INSTRUMENTED_)
|
|
DbgPrint("WINREG: Instrumented enum table data for process id 0x%x\n", NtCurrentTeb()->ClientId.UniqueProcess);
|
|
DbgPrint("WINREG: EnumTableInit subtree state size %d\n", sizeof(rgNewState->UserState));
|
|
DbgPrint("WINREG: EnumTableInit state size %d\n", sizeof(*rgNewState));
|
|
DbgPrint("WINREG: EnumTableInit initial table size %d\n", sizeof(*pEnumTable));
|
|
#endif // _REGCLASS_ENUMTABLE_INSTRUMENTED_
|
|
|
|
|
|
// Initialize the thread list
|
|
|
|
StateObjectListInit(
|
|
&(pEnumTable->ThreadEnumList),
|
|
0);
|
|
|
|
|
|
// We have not initialized the critical section
|
|
// for this table yet -- remember this.
|
|
|
|
pEnumTable->bCriticalSectionInitialized = FALSE;
|
|
|
|
|
|
// Initialize the critical section that will be used to
|
|
// synchronize access to this table
|
|
|
|
Status = RtlInitializeCriticalSection(
|
|
&(pEnumTable->CriticalSection));
|
|
|
|
|
|
// Remember that we have initialized this critical section
|
|
// so we can remember to delete it.
|
|
|
|
pEnumTable->bCriticalSectionInitialized = NT_SUCCESS(Status);
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS EnumTableClear(EnumTable* pEnumTable, DWORD dwCriteria)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Clears all state in an enumeration table --
|
|
frees all state (memory, resources) memory associated
|
|
with the enumeration table.
|
|
|
|
Arguments:
|
|
|
|
pEnumTable - table to clear
|
|
|
|
dwCriteria - if this is ENUM_TABLE_REMOVEKEY_CRITERIA_THISTHREAD,
|
|
enumeration states are removed for this thread only.
|
|
If it is ENUM_TABLE_REMOVEKEY_CRITERIA_ANYTHREAD, enumeration
|
|
states are removed for all threads in the process.
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
BOOL fThisThreadOnly;
|
|
DWORD dwThreadId;
|
|
|
|
#if defined(_REGCLASS_ENUMTABLE_INSTRUMENTED_)
|
|
DWORD cOrphanedStates = 0;
|
|
#endif // _REGCLASS_ENUMTABLE_INSTRUMENTED_
|
|
|
|
|
|
ASSERT((ENUM_TABLE_REMOVEKEY_CRITERIA_THISTHREAD == dwCriteria) ||
|
|
(ENUM_TABLE_REMOVEKEY_CRITERIA_ANYTHREAD == dwCriteria));
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
|
|
// we assume that if we are called with ENUM_TABLE_REMOVEKEY_CRITERIA_ANYTHREAD
|
|
// that we are being called at process detach to remove all keys from the
|
|
// table and free the table itself -- this means that we are the only
|
|
// thread executing this code.
|
|
|
|
|
|
|
|
// Protect ourselves while modifying the table
|
|
|
|
if (dwCriteria != ENUM_TABLE_REMOVEKEY_CRITERIA_ANYTHREAD) {
|
|
|
|
Status = RtlEnterCriticalSection(&(pEnumTable->CriticalSection));
|
|
|
|
ASSERT( NT_SUCCESS( Status ) );
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
#if DBG
|
|
DbgPrint( "WINREG: RtlEnterCriticalSection() in EnumTableRemoveKey() failed. Status = %lx \n", Status );
|
|
#endif
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
fThisThreadOnly = (ENUM_TABLE_REMOVEKEY_CRITERIA_THISTHREAD == dwCriteria);
|
|
|
|
|
|
// Find our thread id if the caller wants to remove
|
|
// state for just this thread
|
|
|
|
if (fThisThreadOnly) {
|
|
|
|
KeyStateList* pStateList;
|
|
|
|
dwThreadId = GetCurrentThreadId();
|
|
|
|
pStateList = (KeyStateList*) StateObjectListRemove(
|
|
&(pEnumTable->ThreadEnumList),
|
|
(PVOID) dwThreadId);
|
|
|
|
|
|
// Announce that this dll no longer stores state for any
|
|
// threads -- used to avoid calls to dll thread
|
|
// detach routines when there's no state to clean up.
|
|
|
|
if (StateObjectListIsEmpty(&(pEnumTable->ThreadEnumList))) {
|
|
gbDllHasThreadState = FALSE;
|
|
}
|
|
|
|
if (pStateList) {
|
|
KeyStateListDestroy((StateObject*) pStateList);
|
|
}
|
|
|
|
} else {
|
|
|
|
|
|
// If we're clearing all threads, just destroy this list
|
|
|
|
StateObjectListClear(&(pEnumTable->ThreadEnumList),
|
|
KeyStateListDestroy);
|
|
|
|
gbDllHasThreadState = FALSE;
|
|
|
|
}
|
|
|
|
|
|
// It's safe to unlock the table
|
|
|
|
if (dwCriteria != ENUM_TABLE_REMOVEKEY_CRITERIA_ANYTHREAD) {
|
|
|
|
Status = RtlLeaveCriticalSection(&(pEnumTable->CriticalSection));
|
|
|
|
ASSERT( NT_SUCCESS( Status ) );
|
|
#if DBG
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
DbgPrint( "WINREG: RtlLeaveCriticalSection() in EnumTableClear() failed. Status = %lx \n", Status );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (pEnumTable->bCriticalSectionInitialized && !fThisThreadOnly) {
|
|
|
|
Status = RtlDeleteCriticalSection(&(pEnumTable->CriticalSection));
|
|
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
#if DBG
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
DbgPrint( "WINREG: RtlDeleteCriticalSection() in EnumTableClear() failed. Status = %lx \n", Status );
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
#if defined(_REGCLASS_ENUMTABLE_INSTRUMENTED_)
|
|
if (!fThisThreadOnly) {
|
|
DbgPrint("WINREG: EnumTableClear() deleted %d unfreed states.\n", cOrphanedStates);
|
|
DbgPrint("WINREG: If the number of unfreed states is > 1, either the\n"
|
|
"WINREG: process terminated a thread with TerminateThread, the process\n"
|
|
"WINREG: didn't close all registry handles before exiting,\n"
|
|
"WINREG: or there's a winreg bug in the classes enumeration code\n");
|
|
}
|
|
#endif // _REGCLASS_ENUMTABLE_INSTRUMENTED_
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS EnumTableFindKeyState(
|
|
EnumTable* pEnumTable,
|
|
HKEY hKey,
|
|
EnumState** ppEnumState)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Searches for the state for a registry key in
|
|
an enumeration table
|
|
|
|
Arguments:
|
|
|
|
pEnumTable - table in which to search
|
|
|
|
hKey - key for whose state we're searching
|
|
|
|
ppEnumState - out param for result of search
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
KeyStateList* pStateList;
|
|
|
|
pStateList = (KeyStateList*) StateObjectListFind(
|
|
&(pEnumTable->ThreadEnumList),
|
|
(PVOID) GetCurrentThreadId());
|
|
|
|
if (!pStateList) {
|
|
return STATUS_OBJECT_NAME_NOT_FOUND;
|
|
} else {
|
|
|
|
*ppEnumState = (EnumState*) StateObjectListFind(
|
|
(StateObjectList*) pStateList,
|
|
hKey);
|
|
|
|
if (!*ppEnumState) {
|
|
return STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
NTSTATUS EnumTableAddKey(
|
|
EnumTable* pEnumTable,
|
|
HKEY hKey,
|
|
DWORD dwFirstSubKey,
|
|
EnumState** ppEnumState,
|
|
EnumState** ppRootState)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Adds an enumeration state to
|
|
an enumeration table for a given key.
|
|
|
|
Arguments:
|
|
|
|
pEnumTable - table in which to add state
|
|
|
|
hKey - key for whom we want to add state
|
|
|
|
dwFirstSubKey - index of first subkey requested by caller
|
|
for enumeration
|
|
|
|
ppEnumState - out param for result of search or add
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
EnumState* pEnumState;
|
|
KeyStateList* pStateList;
|
|
NTSTATUS Status;
|
|
|
|
pEnumState = NULL;
|
|
|
|
|
|
// Announce that this dll has thread state so it will
|
|
// be properly cleaned up by dll thread detach routines
|
|
|
|
gbDllHasThreadState = TRUE;
|
|
|
|
pStateList = (KeyStateList*) StateObjectListFind(
|
|
(StateObjectList*) &(pEnumTable->ThreadEnumList),
|
|
(PVOID) GetCurrentThreadId());
|
|
|
|
if (!pStateList) {
|
|
|
|
pStateList = RegClassHeapAlloc(sizeof(*pStateList));
|
|
|
|
if (!pStateList) {
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
KeyStateListInit(pStateList);
|
|
|
|
StateObjectListAdd(
|
|
&(pEnumTable->ThreadEnumList),
|
|
(StateObject*) pStateList);
|
|
}
|
|
|
|
pEnumState = RegClassHeapAlloc(sizeof(*pEnumState));
|
|
|
|
if (!pEnumState) {
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
RtlZeroMemory(pEnumState, sizeof(*pEnumState));
|
|
|
|
{
|
|
SKeySemantics keyinfo;
|
|
UNICODE_STRING EmptyString = {0, 0, 0};
|
|
BYTE rgNameBuf[REG_MAX_CLASSKEY_LEN + REG_CHAR_SIZE + sizeof(KEY_NAME_INFORMATION)];
|
|
|
|
|
|
// Set buffer to store info about this key
|
|
|
|
RtlZeroMemory(&keyinfo, sizeof(keyinfo));
|
|
|
|
keyinfo._pFullPath = (PKEY_NAME_INFORMATION) rgNameBuf;
|
|
keyinfo._cbFullPath = sizeof(rgNameBuf);
|
|
|
|
|
|
// get information about this key
|
|
|
|
Status = BaseRegGetKeySemantics(hKey, &EmptyString, &keyinfo);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto error_exit;
|
|
}
|
|
|
|
|
|
// initialize the empty spot
|
|
|
|
Status = EnumStateInit(
|
|
pEnumState,
|
|
hKey,
|
|
dwFirstSubKey,
|
|
dwFirstSubKey ? ENUM_DIRECTION_BACKWARD : ENUM_DIRECTION_FORWARD,
|
|
&keyinfo);
|
|
|
|
BaseRegReleaseKeySemantics(&keyinfo);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto error_exit;
|
|
}
|
|
|
|
if (IsRootKey(&keyinfo)) {
|
|
|
|
NTSTATUS RootStatus;
|
|
|
|
|
|
// If this fails, it is not fatal -- it just means
|
|
// we may miss out on an optimization. This can
|
|
// fail due to out of memory, so it is possible
|
|
// that it may fail and we would still want to continue
|
|
|
|
RootStatus = EnumTableGetRootState(pEnumTable, ppRootState);
|
|
|
|
#if DBG
|
|
if (!NT_SUCCESS(RootStatus)) {
|
|
DbgPrint( "WINREG: EnumTableAddKey failed to get classes root state. Status = %lx \n", RootStatus );
|
|
}
|
|
#endif // DBG
|
|
|
|
|
|
if (NT_SUCCESS(RootStatus)) {
|
|
|
|
RootStatus = EnumStateCopy(
|
|
pEnumState,
|
|
*ppRootState);
|
|
|
|
#if DBG
|
|
if (!NT_SUCCESS(RootStatus)) {
|
|
DbgPrint( "WINREG: EnumTableAddKey failed to copy key state. Status = %lx \n", RootStatus );
|
|
}
|
|
#endif // DBG
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// set the out parameter for the caller
|
|
|
|
*ppEnumState = pEnumState;
|
|
|
|
StateObjectListAdd(
|
|
(StateObjectList*) pStateList,
|
|
(StateObject*) pEnumState);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
error_exit:
|
|
|
|
if (!NT_SUCCESS(Status) && pEnumState) {
|
|
RegClassHeapFree(pEnumState);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS EnumTableRemoveKey(
|
|
EnumTable* pEnumTable,
|
|
HKEY hKey,
|
|
DWORD dwCriteria)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
remove an enumeration state from
|
|
an enumeration table for a given key.
|
|
|
|
Arguments:
|
|
|
|
pEnumTable - table in which to remove state
|
|
|
|
hKey - key whose state we wish to remove
|
|
|
|
dwCriteria - if this is ENUM_TABLE_REMOVEKEY_CRITERIA_THISTHREAD,
|
|
the enumeration state for hkey is removed for this thread only.
|
|
If it is ENUM_TABLE_REMOVEKEY_CRITERIA_ANYTHREAD, the enumeration
|
|
state for hkey is removed for all threads in the
|
|
process.
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
KeyStateList* pStateList;
|
|
EnumState* pEnumState;
|
|
BOOL fThisThreadOnly;
|
|
NTSTATUS Status;
|
|
|
|
|
|
// Protect ourselves while modifying the table
|
|
|
|
Status = RtlEnterCriticalSection(&(pEnumTable->CriticalSection));
|
|
|
|
ASSERT( NT_SUCCESS( Status ) );
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
#if DBG
|
|
DbgPrint( "WINREG: RtlEnterCriticalSection() in EnumTableRemoveKey() failed. Status = %lx \n", Status );
|
|
#endif
|
|
return Status;
|
|
}
|
|
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
fThisThreadOnly = (ENUM_TABLE_REMOVEKEY_CRITERIA_THISTHREAD == dwCriteria);
|
|
|
|
{
|
|
KeyStateList* pNext;
|
|
|
|
pNext = NULL;
|
|
|
|
for (pStateList = (KeyStateList*) (pEnumTable->ThreadEnumList.pHead);
|
|
pStateList != NULL;
|
|
pStateList = NULL)
|
|
{
|
|
EnumState* pEnumState;
|
|
|
|
if (fThisThreadOnly) {
|
|
|
|
pStateList = (KeyStateList*) StateObjectListFind(
|
|
(StateObjectList*) &(pEnumTable->ThreadEnumList),
|
|
(PVOID) GetCurrentThreadId());
|
|
|
|
if (!pStateList) {
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
pNext = (KeyStateList*) (pStateList->Object.Links.Flink);
|
|
}
|
|
|
|
pEnumState = (EnumState*) StateObjectListRemove(
|
|
(StateObjectList*) pStateList,
|
|
hKey);
|
|
|
|
if (pEnumState) {
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
EnumStateDestroy((StateObject*) pEnumState);
|
|
|
|
|
|
// Note the state list might be empty for a given thread,
|
|
// but we will not destroy this list in order to avoid
|
|
// excessive heap calls
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// It's safe to unlock the table
|
|
|
|
Status = RtlLeaveCriticalSection(&(pEnumTable->CriticalSection));
|
|
|
|
ASSERT( NT_SUCCESS( Status ) );
|
|
#if DBG
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
DbgPrint( "WINREG: RtlLeaveCriticalSection() in EnumTableRemoveKey() failed. Status = %lx \n", Status );
|
|
}
|
|
#endif
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS EnumTableGetNextEnum(
|
|
EnumTable* pEnumTable,
|
|
HKEY hKey,
|
|
DWORD dwSubkey,
|
|
KEY_INFORMATION_CLASS KeyInformationClass,
|
|
PVOID pKeyInfo,
|
|
DWORD cbKeyInfo,
|
|
LPDWORD pcbKeyInfo)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Gets the next enumerated subkey for a
|
|
particular subkey
|
|
|
|
Arguments:
|
|
|
|
pEnumTable - table that holds state of
|
|
registry key enumerations
|
|
|
|
hKey - key for whom we want to add state
|
|
|
|
dwSubKey - index of subkey requested by caller
|
|
for enumeration
|
|
|
|
KeyInformationClass - the type of key information data
|
|
requested by caller
|
|
|
|
pKeyInfo - out param -- buffer for key information data for caller
|
|
|
|
cbKeyInfo - size of pKeyInfo buffer
|
|
|
|
pcbKeyInfo - out param -- size of key information returned to caller
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
EnumState* pEnumState;
|
|
EnumState* pRootState;
|
|
NTSTATUS Status;
|
|
BOOL fFreeState;
|
|
|
|
|
|
// Protect ourselves while we enumerate
|
|
|
|
Status = RtlEnterCriticalSection(&(pEnumTable->CriticalSection));
|
|
|
|
|
|
// Very big -- unlikely to happen unless there's a runaway enumeration
|
|
// due to a bug in this module.
|
|
|
|
// ASSERT(dwSubkey < 16383);
|
|
|
|
ASSERT( NT_SUCCESS( Status ) );
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
#if DBG
|
|
DbgPrint( "WINREG: RtlEnterCriticalSection() in EnumTableGetNextENUm() failed. Status = %lx \n", Status );
|
|
#endif
|
|
return Status;
|
|
}
|
|
|
|
|
|
// Find the enumeration state for the requested key. Note that even if this
|
|
// function fails to find an existing state, which case it returns a failure code
|
|
// it can still return an empty pEnumState for that hKey so it can be added later
|
|
|
|
Status = EnumTableGetKeyState(pEnumTable, hKey, dwSubkey, &pEnumState, &pRootState, pcbKeyInfo);
|
|
|
|
if (!NT_SUCCESS(Status) || !pEnumState) {
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
// We have a state for this key, now we can use it to enumerate the next key
|
|
|
|
Status = EnumStateGetNextEnum(pEnumState, dwSubkey, KeyInformationClass, pKeyInfo, cbKeyInfo, pcbKeyInfo, &fFreeState);
|
|
|
|
|
|
// Below is an optimization for apps that enumerate HKEY_CLASSES_ROOT but close the handle and reopen it each
|
|
// time before they call the registry enumeration api. This is a very foolish way to use the api (that's two extra
|
|
// kernel calls for the open and close per enumeration), but existing applications do this and
|
|
// without the optimization, their enumeration times can go from 3 seconds to 1 or more minutes. With this optimization,
|
|
// the time gets back down to a few seconds. This happened because we lost state after the close -- when the new
|
|
// key was opened, we had to call the kernel to enumerate all the keys up to the requested index since we had no
|
|
// previous state to go by -- this ends up making the entire enumeration an O(n^2) operation instead of O(n) as it
|
|
// had been when callers didn't close the key during the enumeration. Here, n is a kernel trap to enumerate a key.
|
|
|
|
|
|
|
|
// Above, we retrieved an enumeration state for the root of classes -- this state reflects the enumeration state
|
|
// of the last handle that was used to enumerate the root on this thread. This way, when a new handle is opened
|
|
// to enumerate the root, we start with this state which will most likely be right at the index before the requested
|
|
// index. Instead of making i calls to NtEnumerateKey where i is the index of enumeration requested by the caller,
|
|
// we make 1 or at most 2 calls.
|
|
|
|
|
|
|
|
// Here, we update the root state to match the recently enumerated state. Note that this only happens
|
|
// if the key being enumerated refers to HKEY_CLASSES_ROOT since pRootState is only non-NULL in this
|
|
// case.
|
|
|
|
if (pRootState) {
|
|
EnumTableUpdateRootState(pEnumTable, pRootState, pEnumState, fFreeState);
|
|
}
|
|
|
|
if (fFreeState) {
|
|
|
|
NTSTATUS RemoveStatus;
|
|
|
|
|
|
// For whatever reason, we've been told to free the enumeration state for this key.
|
|
// This could be due to an error, or it could be a normal situation such as reaching
|
|
// the end of an enumeration.
|
|
|
|
|
|
RemoveStatus = EnumTableRemoveKey(pEnumTable, hKey, ENUM_TABLE_REMOVEKEY_CRITERIA_THISTHREAD);
|
|
|
|
ASSERT(NT_SUCCESS(RemoveStatus));
|
|
}
|
|
|
|
cleanup:
|
|
|
|
|
|
// It's safe to unlock the table now.
|
|
|
|
{
|
|
NTSTATUS CriticalSectionStatus;
|
|
|
|
CriticalSectionStatus = RtlLeaveCriticalSection(&(pEnumTable->CriticalSection));
|
|
|
|
ASSERT( NT_SUCCESS( CriticalSectionStatus ) );
|
|
#if DBG
|
|
if ( !NT_SUCCESS( CriticalSectionStatus ) ) {
|
|
DbgPrint( "WINREG: RtlLeaveCriticalSection() in EnumTableGetNextEnum() failed. Status = %lx \n",
|
|
CriticalSectionStatus );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS EnumTableGetKeyState(
|
|
EnumTable* pEnumTable,
|
|
HKEY hKey,
|
|
DWORD dwSubkey,
|
|
EnumState** ppEnumState,
|
|
EnumState** ppRootState,
|
|
LPDWORD pcbKeyInfo)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Finds a key state for hKey -- creates a new state for hkey if
|
|
there is no existing state
|
|
|
|
Arguments:
|
|
|
|
pEnumTable - enumeration table in which to find key's state
|
|
hKey - handle to registry key for which to find state
|
|
dwSubkey - subkey that we're trying to enumerate -- needed in
|
|
case we need to create a new state
|
|
ppEnumState - pointer to where we should return address of
|
|
the retrieved state,
|
|
ppRootState - if the retrieved state is the root of the classes
|
|
tree, this address will point to a known state for the root
|
|
that's good across all hkey's enumerated on this thread
|
|
pcbKeyInfo - stores size of key information on return
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS for success, other error code on error
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
if (ppRootState) {
|
|
*ppRootState = NULL;
|
|
}
|
|
|
|
|
|
// Find the enumeration state for the requested key. Note that even if this
|
|
// function fails to find an existing state, in which case it returns a failure code
|
|
// it can still return an empty pEnumState for that hKey so it can be added later
|
|
|
|
Status = EnumTableFindKeyState(pEnumTable, hKey, ppEnumState);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
if (STATUS_OBJECT_NAME_NOT_FOUND == Status) {
|
|
|
|
|
|
// This means the key didn't exist, already, so we'll add it
|
|
|
|
Status = EnumTableAddKey(pEnumTable, hKey, dwSubkey, ppEnumState, ppRootState);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
|
|
// The above function can succeed but return a NULL pEnumState -- this
|
|
// happens if it turns out this key is not a "special key" -- i.e. this key's
|
|
// parents exist in only one hive, not two, so we don't need to do anything here
|
|
// and regular enumeration will suffice.
|
|
|
|
if (!(*ppEnumState)) {
|
|
|
|
// We set this value to let our caller know that this isn't a class key
|
|
|
|
*pcbKeyInfo = 0;
|
|
}
|
|
}
|
|
} else {
|
|
|
|
if ((*ppEnumState)->fClassesRoot) {
|
|
Status = EnumTableGetRootState(pEnumTable, ppRootState);
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS EnumTableGetRootState(
|
|
EnumTable* pEnumTable,
|
|
EnumState** ppRootState)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
pEnumTable - enumeration table in which to find the root
|
|
state
|
|
ppRootState - points to address of root state on return
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
DWORD cbKeyInfo;
|
|
KeyStateList* pStateList;
|
|
|
|
|
|
// We assume the caller has made sure that a state list
|
|
// for this thread exists -- this should never, ever fail
|
|
|
|
pStateList = (KeyStateList*) StateObjectListFind(
|
|
&(pEnumTable->ThreadEnumList),
|
|
(PVOID) GetCurrentThreadId());
|
|
|
|
ASSERT(pStateList);
|
|
|
|
*ppRootState = &(pStateList->RootState);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
void EnumTableUpdateRootState(
|
|
EnumTable* pEnumTable,
|
|
EnumState* pRootState,
|
|
EnumState* pEnumState,
|
|
BOOL fResetState)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Updates the state of the classes root for this thread -- this
|
|
allows us to optimize for apps that close handles when enumerating
|
|
hkcr -- we use this classes root state when no existing state is
|
|
found for an hkey that refers to hkcr, and we update this state
|
|
after enumerating an hkcr key on this thread so that it will
|
|
be up to date.
|
|
|
|
Arguments:
|
|
|
|
pEnumTable - enumeration table in which the classes root state resides
|
|
|
|
pRootState - classes root state that should be updated
|
|
|
|
ppEnumState - state that contains the data with which pRootState should
|
|
be updated
|
|
|
|
fResetState - if TRUE, this flag means we should not update the root state
|
|
with pEnumState's data, just reset it. If FALSE, we update the root
|
|
with pEnumState's data.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
|
|
// See if we need to merely reset the root or actually
|
|
// update it with another state
|
|
|
|
if (!fResetState) {
|
|
|
|
|
|
// Don't reset -- copy over the state from pEnumState to the
|
|
// root state -- the root's state will be the same as pEnumState's
|
|
// after this copy
|
|
|
|
Status = EnumStateCopy(pRootState, pEnumState);
|
|
|
|
} else {
|
|
|
|
|
|
// Just clear out the state -- caller didn't request that we
|
|
// use pEnumState.
|
|
|
|
Status = EnumStateInit(
|
|
pRootState,
|
|
0,
|
|
0,
|
|
ENUM_DIRECTION_FORWARD,
|
|
NULL);
|
|
}
|
|
|
|
|
|
// If there's a failure, it must be out-of-memory, so we should get rid
|
|
// of this state since we can't make it accurately reflect the true
|
|
// enumeration state
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
#if DBG
|
|
DbgPrint( "WINREG: failure in UpdateRootState. Status = %lx \n", Status );
|
|
#endif
|
|
|
|
ASSERT(STATUS_NO_MEMORY == Status);
|
|
|
|
EnumStateClear(pRootState);
|
|
}
|
|
}
|
|
|
|
|
|
VOID KeyStateListInit(KeyStateList* pStateList)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Initializes a state list
|
|
|
|
Arguments:
|
|
|
|
pObject -- pointer to KeyStateList object to destroy
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
|
|
// First initialize the base object
|
|
|
|
StateObjectListInit((StateObjectList*) pStateList,
|
|
(PVOID) GetCurrentThreadId());
|
|
|
|
|
|
// Now do KeyStateList specific init
|
|
|
|
(void) EnumStateInit(
|
|
&(pStateList->RootState),
|
|
NULL,
|
|
0,
|
|
ENUM_DIRECTION_FORWARD,
|
|
NULL);
|
|
}
|
|
|
|
VOID KeyStateListDestroy(StateObject* pObject)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Destroys an KeyStateList, freeing its resources such
|
|
as memory or kernel object handles
|
|
|
|
Arguments:
|
|
|
|
pObject -- pointer to KeyStateList object to destroy
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
KeyStateList* pThisList;
|
|
|
|
pThisList = (KeyStateList*) pObject;
|
|
|
|
|
|
// Destroy all states in this list
|
|
|
|
StateObjectListClear(
|
|
(StateObjectList*) pObject,
|
|
EnumStateDestroy);
|
|
|
|
|
|
// Free resources associated with the root state
|
|
|
|
EnumStateClear(&(pThisList->RootState));
|
|
|
|
|
|
// Free the data structure for this object
|
|
|
|
RegClassHeapFree(pThisList);
|
|
}
|
|
|
|
|
|
NTSTATUS EnumStateInit(
|
|
EnumState* pEnumState,
|
|
HKEY hKey,
|
|
DWORD dwFirstSubKey,
|
|
DWORD dwDirection,
|
|
SKeySemantics* pKeySemantics)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Initializes enumeration state
|
|
|
|
Arguments:
|
|
|
|
pEnumState - enumeration state to initialize
|
|
hKey - registry key to which this state refers
|
|
dwFirstSubKey - index of the first subkey which this state will enumerate
|
|
dwDirection - direction through which we should enumerate -- either
|
|
ENUM_DIRECTION_FORWARD or ENUM_DIRECTION_BACKWARD
|
|
pKeySemantics - structure containing information about hKey
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG cMachineKeys;
|
|
ULONG cUserKeys;
|
|
HKEY hkOther;
|
|
|
|
ASSERT((ENUM_DIRECTION_FORWARD == dwDirection) || (ENUM_DIRECTION_BACKWARD == dwDirection) ||
|
|
(ENUM_DIRECTION_IGNORE == dwDirection));
|
|
|
|
ASSERT((ENUM_DIRECTION_IGNORE == dwDirection) ? hKey == NULL : TRUE);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
hkOther = NULL;
|
|
|
|
|
|
// If no hkey is specified, this is an init of a blank enum
|
|
// state, so clear everything
|
|
|
|
if (!hKey) {
|
|
memset(pEnumState, 0, sizeof(*pEnumState));
|
|
}
|
|
|
|
|
|
// Clear each subtree
|
|
|
|
EnumSubtreeStateClear(&(pEnumState->UserState));
|
|
EnumSubtreeStateClear(&(pEnumState->MachineState));
|
|
|
|
|
|
// Reset each subtree
|
|
|
|
pEnumState->UserState.Finished = FALSE;
|
|
pEnumState->MachineState.Finished = FALSE;
|
|
|
|
pEnumState->UserState.iSubKey = 0;
|
|
pEnumState->MachineState.iSubKey = 0;
|
|
|
|
cUserKeys = 0;
|
|
cMachineKeys = 0;
|
|
|
|
if (pKeySemantics) {
|
|
StateObjectInit((StateObject*) &(pEnumState->Object), hKey);
|
|
}
|
|
|
|
if (hKey) {
|
|
|
|
if (pKeySemantics) {
|
|
pEnumState->fClassesRoot = IsRootKey(pKeySemantics);
|
|
}
|
|
|
|
|
|
// open the other key if we have enough info to do so --
|
|
|
|
if (pKeySemantics) {
|
|
|
|
|
|
// Remember, only one of the handles returned below
|
|
// is new -- the other is simply hKey
|
|
|
|
Status = BaseRegGetUserAndMachineClass(
|
|
pKeySemantics,
|
|
hKey,
|
|
MAXIMUM_ALLOWED,
|
|
&(pEnumState->hkMachineKey),
|
|
&(pEnumState->hkUserKey));
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
|
|
// for backwards enumerations
|
|
|
|
if (ENUM_DIRECTION_BACKWARD == dwDirection) {
|
|
|
|
ULONG cMachineKeys;
|
|
ULONG cUserKeys;
|
|
HKEY hkUser;
|
|
HKEY hkMachine;
|
|
|
|
cMachineKeys = 0;
|
|
cUserKeys = 0;
|
|
|
|
hkMachine = pEnumState->hkMachineKey;
|
|
hkUser = pEnumState->hkUserKey;
|
|
|
|
|
|
// In order to query for subkey counts, we should
|
|
// to get a new handle since the caller supplied handle
|
|
// may not have enough permissions
|
|
|
|
{
|
|
HKEY hkSource;
|
|
HANDLE hCurrentProcess;
|
|
|
|
hCurrentProcess = NtCurrentProcess();
|
|
|
|
hkSource = (hkMachine == hKey) ? hkMachine : hkUser;
|
|
|
|
Status = NtDuplicateObject(
|
|
hCurrentProcess,
|
|
hkSource,
|
|
hCurrentProcess,
|
|
&hkOther,
|
|
KEY_QUERY_VALUE,
|
|
FALSE,
|
|
0);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto error_exit;
|
|
}
|
|
|
|
if (hkSource == hkUser) {
|
|
hkUser = hkOther;
|
|
} else {
|
|
hkMachine = hkOther;
|
|
}
|
|
}
|
|
|
|
|
|
// find new start -- query for index of last subkey in
|
|
// each hive
|
|
|
|
if (hkMachine) {
|
|
|
|
Status = GetSubKeyCount(hkMachine, &cMachineKeys);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto error_exit;
|
|
}
|
|
}
|
|
|
|
if (hkUser) {
|
|
|
|
Status = GetSubKeyCount(hkUser, &cUserKeys);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto error_exit;
|
|
}
|
|
}
|
|
|
|
|
|
// If either subtree has no subkeys, we're done enumerating that
|
|
// subtree
|
|
|
|
if (!cUserKeys) {
|
|
pEnumState->UserState.Finished = TRUE;
|
|
} else {
|
|
pEnumState->UserState.iSubKey = cUserKeys - 1;
|
|
}
|
|
|
|
if (!cMachineKeys) {
|
|
pEnumState->MachineState.Finished = TRUE;
|
|
} else {
|
|
pEnumState->MachineState.iSubKey = cMachineKeys - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Set members of this structure
|
|
|
|
|
|
pEnumState->dwThreadId = GetCurrentThreadId();
|
|
pEnumState->Direction = dwDirection;
|
|
pEnumState->dwLastRequest = dwFirstSubKey;
|
|
pEnumState->LastLocation = ENUM_LOCATION_NONE;
|
|
|
|
pEnumState->hKey = hKey;
|
|
|
|
error_exit:
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
EnumSubtreeStateClear(&(pEnumState->MachineState));
|
|
EnumSubtreeStateClear(&(pEnumState->UserState));
|
|
}
|
|
|
|
if (hkOther) {
|
|
NtClose(hkOther);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS EnumStateGetNextEnum(
|
|
EnumState* pEnumState,
|
|
DWORD dwSubKey,
|
|
KEY_INFORMATION_CLASS KeyInformationClass,
|
|
PVOID pKeyInfo,
|
|
DWORD cbKeyInfo,
|
|
LPDWORD pcbKeyInfo,
|
|
BOOL* pfFreeState)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Gets the next key in an enumeration based on the current state.
|
|
|
|
Arguments:
|
|
|
|
pEnumState - enumeration state on which to base our search
|
|
for the next key
|
|
dwSubKey - index of key to enumerate
|
|
KeyInformationClass - enum for what sort of information to retrieve in the
|
|
enumeration -- Basic Information or Node Information
|
|
|
|
pKeyInfo - location to store retrieved data for caller
|
|
cbKeyInfo - size of caller's info buffer
|
|
pcbKeyInfo - size of data this function writes to buffer on return.
|
|
pfFreeState - out param -- if set to TRUE, caller should free pEnumState.
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
This function essentially enumerates from the previous index requested
|
|
by the caller of RegEnumKeyEx to the new one. In most cases, this just
|
|
means one trip to the kernel -- i.e. if a caller goes from index 2 to 3,
|
|
or from 3 to 2, this is one trip to the kernel. However, if the caller goes
|
|
from 2 to 5, we'll have to do several enumerations on the way from 2 to 5.
|
|
Also, if the caller switches direction (i.e. starts off 0,1,2,3 and then
|
|
requests 1), a large penalty may be incurred. When switching from ascending
|
|
to descending, we have to enumerate all keys to the end and then before we
|
|
can then enumerate down to the caller's requested index. Switching from
|
|
descending to ascending is less expensive -- we know that the beginning
|
|
is at 0 for both user and machine keys, so we can simply set our indices to
|
|
0 without enumerating anything. However, we must then enumerate to the
|
|
caller's requested index. Note that for all descending enumerations, we
|
|
must enumerate all the way to the end first before returning anything to the
|
|
caller.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
LONG lIncrement;
|
|
DWORD dwStart;
|
|
DWORD dwLimit;
|
|
EnumSubtreeState* pTreeState;
|
|
|
|
|
|
// If anything bad happens, this state should be freed
|
|
|
|
*pfFreeState = TRUE;
|
|
|
|
|
|
// Find out the limits (start, finish, increment) for
|
|
// our enumeration. The increment is either 1 or -1,
|
|
// depending on whether this is an ascending or descending
|
|
// enumeration. EnumStateSetLimits will take into account
|
|
// any changes in direction and set dwStart and dwLimit
|
|
// accordingly.
|
|
|
|
Status = EnumStateSetLimits(
|
|
pEnumState,
|
|
dwSubKey,
|
|
&dwStart,
|
|
&dwLimit,
|
|
&lIncrement);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
|
|
// Get the next enum to give back to the caller
|
|
|
|
Status = EnumStateChooseNext(
|
|
pEnumState,
|
|
dwSubKey,
|
|
dwStart,
|
|
dwLimit,
|
|
lIncrement,
|
|
&pTreeState);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
|
|
// We have retrieved information, so we should
|
|
// not free this state
|
|
|
|
if (!(pEnumState->UserState.Finished && pEnumState->MachineState.Finished)) {
|
|
*pfFreeState = FALSE;
|
|
}
|
|
|
|
|
|
// Remember the last key we enumerated
|
|
|
|
pEnumState->dwLastRequest = dwSubKey;
|
|
|
|
|
|
// Copy the retrieved information to the user's
|
|
// buffer.
|
|
|
|
Status = EnumSubtreeStateCopyKeyInfo(
|
|
pTreeState,
|
|
KeyInformationClass,
|
|
pKeyInfo,
|
|
cbKeyInfo,
|
|
pcbKeyInfo);
|
|
|
|
|
|
// The copy could fail if the user's buffer isn't big enough --
|
|
// if it succeeds, clear the name information for the subkey from
|
|
// which we retrieved the data so that the next time we're called
|
|
// we'll get the next subkey for that subtree.
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
EnumSubtreeStateClear(pTreeState);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS EnumStateSetLimits(
|
|
EnumState* pEnumState,
|
|
DWORD dwSubKey,
|
|
LPDWORD pdwStart,
|
|
LPDWORD pdwLimit,
|
|
PLONG plIncrement)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Gets the limits (start, finish, increment) for enumerating a given
|
|
subkey index
|
|
|
|
Arguments:
|
|
|
|
pEnumState - enumeration state on which to base our limits
|
|
|
|
dwSubKey - index of key which caller wants enumerated
|
|
|
|
pdwStart - out param -- result is the place at which to start
|
|
enumerating in order to find dwSubKey
|
|
|
|
pdwLimit - out param -- result is the place at which to stop
|
|
enumerating when looking for dwSubKey
|
|
|
|
plIncrement - out param -- increment to use for enumeration. It will
|
|
be set to 1 if the enumeration is upward (0,1,2...) or
|
|
-1 if it is downard (3,2,1,...).
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
LONG lNewIncrement;
|
|
NTSTATUS Status;
|
|
BOOL fSameKey;
|
|
|
|
|
|
// set our increment to the direction which our state remembers
|
|
|
|
*plIncrement = pEnumState->Direction == ENUM_DIRECTION_FORWARD ? 1 : -1;
|
|
|
|
fSameKey = FALSE;
|
|
|
|
|
|
// Figure out what the new direction should be
|
|
// This is done by comparing the current request
|
|
// with the last request.
|
|
|
|
if (dwSubKey > pEnumState->dwLastRequest) {
|
|
lNewIncrement = 1;
|
|
} else if (dwSubKey < pEnumState->dwLastRequest) {
|
|
lNewIncrement = -1;
|
|
} else {
|
|
|
|
// We are enumerating a key that may already
|
|
// have been enumerated
|
|
|
|
fSameKey = TRUE;
|
|
lNewIncrement = *plIncrement;
|
|
}
|
|
|
|
|
|
// See if we've changed direction
|
|
|
|
if (lNewIncrement != *plIncrement) {
|
|
|
|
|
|
// If so, we should throw away all existing state and start from scratch
|
|
|
|
Status = EnumStateInit(
|
|
pEnumState,
|
|
pEnumState->hKey,
|
|
(-1 == lNewIncrement) ? dwSubKey : 0,
|
|
(-1 == lNewIncrement) ? ENUM_DIRECTION_BACKWARD : ENUM_DIRECTION_FORWARD,
|
|
NULL);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
|
|
// By default, we start enumerating where we left off
|
|
|
|
*pdwStart = pEnumState->dwLastRequest;
|
|
|
|
|
|
// for state for which we have previously enumerated a key
|
|
|
|
if (ENUM_LOCATION_NONE != pEnumState->LastLocation) {
|
|
|
|
|
|
// We're going in the same direction as on the
|
|
// previous call. We should start
|
|
// one past our previous position. Note that we
|
|
// only start there if this is a different key --
|
|
// if we've already enumerated it we start at the
|
|
// same spot.
|
|
|
|
if (!fSameKey) {
|
|
*pdwStart += *plIncrement;
|
|
} else {
|
|
|
|
|
|
// If we're being asked for the same index
|
|
// multiple times they're probably deleting
|
|
// keys -- we should reset ourselves to
|
|
// the beginning so their enum will hit
|
|
// all the keys
|
|
|
|
|
|
|
|
// We're starting at zero, so set ourselves
|
|
// to start at the beginning
|
|
|
|
Status = EnumStateInit(
|
|
pEnumState,
|
|
pEnumState->hKey,
|
|
0,
|
|
ENUM_DIRECTION_FORWARD,
|
|
NULL);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
*plIncrement = 1;
|
|
pEnumState->Direction = ENUM_DIRECTION_FORWARD;
|
|
*pdwStart = 0;
|
|
}
|
|
|
|
} else {
|
|
|
|
|
|
// No previous calls were made for this state
|
|
|
|
if (ENUM_DIRECTION_BACKWARD == pEnumState->Direction) {
|
|
|
|
|
|
// For backwards enumerations, we want to get an
|
|
// accurate count of total subkeys and start there
|
|
|
|
Status = ClassKeyCountSubKeys(
|
|
pEnumState->hKey,
|
|
pEnumState->hkUserKey,
|
|
pEnumState->hkMachineKey,
|
|
0,
|
|
pdwStart);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
|
|
// Make sure we don't go past the end
|
|
|
|
if (dwSubKey >= *pdwStart) {
|
|
return STATUS_NO_MORE_ENTRIES;
|
|
}
|
|
|
|
|
|
// This is a zero-based index, so to
|
|
// put our start at the very end we must
|
|
// be one less than the number of keys
|
|
|
|
(*pdwStart)--;
|
|
|
|
*plIncrement = -1;
|
|
|
|
} else {
|
|
*plIncrement = 1;
|
|
}
|
|
}
|
|
|
|
|
|
// Set limit to be one past requested subkey
|
|
|
|
*pdwLimit = dwSubKey + *plIncrement;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
NTSTATUS EnumStateChooseNext(
|
|
EnumState* pEnumState,
|
|
DWORD dwSubKey,
|
|
DWORD dwStart,
|
|
DWORD dwLimit,
|
|
LONG lIncrement,
|
|
EnumSubtreeState** ppTreeState)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Iterates through registry keys to get the key requested by the caller
|
|
|
|
Arguments:
|
|
|
|
pEnumState - enumeration state on which to base our search
|
|
|
|
dwSubKey - index of key which caller wants enumerated
|
|
|
|
dwStart - The place at which to start
|
|
enumerating in order to find dwSubKey
|
|
|
|
dwLimit - The place at which to stop
|
|
enumerating when looking for dwSubKey
|
|
|
|
lIncrement - Increment to use for enumeration. It will
|
|
be set to 1 if the enumeration is upward (0,1,2...) or
|
|
-1 if it is downard (3,2,1,...).
|
|
|
|
ppTreeState - out param -- pointer to address of subtree state in which this regkey
|
|
was found -- each EnumState has two EnumSubtreeState's -- one for user
|
|
and one for machine.
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
DWORD iCurrent;
|
|
NTSTATUS Status;
|
|
BOOL fClearLast;
|
|
|
|
Status = STATUS_NO_MORE_ENTRIES;
|
|
|
|
fClearLast = FALSE;
|
|
|
|
|
|
// We will now iterate from dwStart to dwLimit so that we can find the key
|
|
// requested by the caller
|
|
|
|
for (iCurrent = dwStart; iCurrent != dwLimit; iCurrent += lIncrement) {
|
|
|
|
BOOL fFoundKey;
|
|
BOOL fIgnoreFailure;
|
|
|
|
fFoundKey = FALSE;
|
|
|
|
fIgnoreFailure = FALSE;
|
|
|
|
Status = STATUS_NO_MORE_ENTRIES;
|
|
|
|
|
|
// Clear last subtree
|
|
|
|
if (fClearLast) {
|
|
EnumSubtreeStateClear(*ppTreeState);
|
|
}
|
|
|
|
|
|
// if key names aren't present, alloc space and get names
|
|
|
|
if (pEnumState->hkUserKey) {
|
|
if (pEnumState->UserState.pKeyInfo) {
|
|
fFoundKey = TRUE;
|
|
} else if (!(pEnumState->UserState.Finished)) {
|
|
|
|
// get user key info
|
|
Status = EnumClassKey(
|
|
pEnumState->hkUserKey,
|
|
&(pEnumState->UserState));
|
|
|
|
fFoundKey = NT_SUCCESS(Status);
|
|
|
|
|
|
// If there are no more subkeys for this subtree,
|
|
// mark it as finished
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
if (STATUS_NO_MORE_ENTRIES != Status) {
|
|
return Status;
|
|
}
|
|
|
|
if (lIncrement > 0) {
|
|
pEnumState->UserState.Finished = TRUE;
|
|
} else {
|
|
|
|
pEnumState->UserState.iSubKey += lIncrement;
|
|
fIgnoreFailure = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pEnumState->hkMachineKey) {
|
|
|
|
if (pEnumState->MachineState.pKeyInfo) {
|
|
fFoundKey = TRUE;
|
|
} else if (!(pEnumState->MachineState.Finished)) {
|
|
|
|
// get machine key info
|
|
Status = EnumClassKey(
|
|
pEnumState->hkMachineKey,
|
|
&(pEnumState->MachineState));
|
|
|
|
|
|
// If there are no more subkeys for this subtree,
|
|
// mark it as finished
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
fFoundKey = TRUE;
|
|
} else if (STATUS_NO_MORE_ENTRIES == Status) {
|
|
|
|
if (lIncrement > 0) {
|
|
pEnumState->MachineState.Finished = TRUE;
|
|
} else {
|
|
pEnumState->MachineState.iSubKey += lIncrement;
|
|
fIgnoreFailure = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// If we found no keys in either user or machine locations, there are
|
|
// no more keys.
|
|
|
|
if (!fFoundKey) {
|
|
|
|
|
|
// For descending enumerations, we ignore STATUS_NO_MORE_ENTRIES
|
|
// and keep going until we find one.
|
|
|
|
if (fIgnoreFailure) {
|
|
continue;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
// If we already hit the bottom, skip to the end
|
|
|
|
if ((pEnumState->UserState.iSubKey == 0) &&
|
|
(pEnumState->MachineState.iSubKey == 0) &&
|
|
(lIncrement < 0)) {
|
|
|
|
iCurrent = dwLimit - lIncrement;
|
|
}
|
|
|
|
|
|
// Now we need to choose between keys in the machine hive and user hives --
|
|
// this call will choose which key to use.
|
|
|
|
Status = EnumStateCompareSubtrees(pEnumState, lIncrement, ppTreeState);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
pEnumState->dwLastRequest = dwSubKey;
|
|
|
|
return Status;
|
|
}
|
|
|
|
fClearLast = TRUE;
|
|
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS EnumStateCompareSubtrees(
|
|
EnumState* pEnumState,
|
|
LONG lIncrement,
|
|
EnumSubtreeState** ppSubtree)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Compares the user and machine subtrees of an enumeration state
|
|
to see which of the two current keys in each hive should be
|
|
returned as the next key in an enumeration
|
|
|
|
Arguments:
|
|
|
|
pEnumState - enumeration state on which to base our search
|
|
|
|
lIncrement - Increment to use for enumeration. It will
|
|
be set to 1 if the enumeration is upward (0,1,2...) or
|
|
-1 if it is downard (3,2,1,...).
|
|
|
|
ppSubtree - out param -- pointer to address of subtree state where
|
|
key was found -- the name of the key can be extracted from it.
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
|
|
// If both subtrees have a current subkey name, we'll need to compare
|
|
// the names
|
|
|
|
if (pEnumState->MachineState.pKeyInfo && pEnumState->UserState.pKeyInfo) {
|
|
|
|
UNICODE_STRING MachineKeyName;
|
|
UNICODE_STRING UserKeyName;
|
|
LONG lCompareResult;
|
|
|
|
MachineKeyName.Buffer = pEnumState->MachineState.pKeyInfo->Name;
|
|
MachineKeyName.Length = (USHORT) pEnumState->MachineState.pKeyInfo->NameLength;
|
|
|
|
UserKeyName.Buffer = pEnumState->UserState.pKeyInfo->Name;
|
|
UserKeyName.Length = (USHORT) pEnumState->UserState.pKeyInfo->NameLength;
|
|
|
|
|
|
// Do the comparison
|
|
|
|
lCompareResult =
|
|
RtlCompareUnicodeString(&UserKeyName, &MachineKeyName, TRUE) * lIncrement;
|
|
|
|
|
|
// User wins comparison
|
|
|
|
if (lCompareResult < 0) {
|
|
// choose user
|
|
*ppSubtree = &(pEnumState->UserState);
|
|
pEnumState->LastLocation = ENUM_LOCATION_USER;
|
|
|
|
} else if (lCompareResult > 0) {
|
|
|
|
|
|
// Machine wins choose machine
|
|
|
|
*ppSubtree = &(pEnumState->MachineState);
|
|
pEnumState->LastLocation = ENUM_LOCATION_MACHINE;
|
|
|
|
} else {
|
|
|
|
|
|
// Comparison returned equality -- the keys have the same
|
|
// name. This means the same key name exists in both machine and
|
|
// user, so we need to make a choice about which one we will enumerate.
|
|
// Policy for per-user class registration enumeration is to choose user, just
|
|
// as we do for other api's such as RegOpenKeyEx and RegCreateKeyEx.
|
|
|
|
if (!((pEnumState->MachineState.iSubKey == 0) && (lIncrement < 0))) {
|
|
pEnumState->MachineState.iSubKey += lIncrement;
|
|
} else {
|
|
pEnumState->MachineState.Finished = TRUE;
|
|
}
|
|
|
|
|
|
// Clear the machine state and move it to the next index -- we don't
|
|
// have to clear the user state yet because the state of whichever subtree
|
|
// was selected is cleared down below
|
|
|
|
EnumSubtreeStateClear(&(pEnumState->MachineState));
|
|
pEnumState->LastLocation = ENUM_LOCATION_USER;
|
|
*ppSubtree = &(pEnumState->UserState);
|
|
}
|
|
|
|
} else if (!(pEnumState->UserState.pKeyInfo) && !(pEnumState->MachineState.pKeyInfo)) {
|
|
|
|
// Neither subtree state has a subkey, so there are no subkeys
|
|
|
|
return STATUS_NO_MORE_ENTRIES;
|
|
|
|
} else if (pEnumState->MachineState.pKeyInfo) {
|
|
|
|
|
|
// Only machine has a subkey
|
|
|
|
*ppSubtree = &(pEnumState->MachineState);
|
|
pEnumState->LastLocation = ENUM_LOCATION_MACHINE;
|
|
|
|
} else {
|
|
|
|
|
|
// only user has a subkey
|
|
|
|
*ppSubtree = &(pEnumState->UserState);
|
|
pEnumState->LastLocation = ENUM_LOCATION_USER;
|
|
}
|
|
|
|
|
|
// change the state of the subtree which we selected
|
|
|
|
if (!(((*ppSubtree)->iSubKey == 0) && (lIncrement < 0))) {
|
|
(*ppSubtree)->iSubKey += lIncrement;
|
|
} else {
|
|
(*ppSubtree)->Finished = TRUE;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
void EnumStateDestroy(StateObject* pObject)
|
|
{
|
|
EnumStateClear((EnumState*) pObject);
|
|
|
|
RegClassHeapFree(pObject);
|
|
}
|
|
|
|
VOID EnumStateClear(EnumState* pEnumState)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Clears the enumeration state
|
|
|
|
Arguments:
|
|
|
|
pEnumState - enumeration state to clear
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
|
|
// Close an existing reference to a second key
|
|
|
|
if (pEnumState->hkMachineKey && (pEnumState->hKey != pEnumState->hkMachineKey)) {
|
|
|
|
NtClose(pEnumState->hkMachineKey);
|
|
|
|
} else if (pEnumState->hkUserKey && (pEnumState->hKey != pEnumState->hkUserKey)) {
|
|
|
|
NtClose(pEnumState->hkUserKey);
|
|
}
|
|
|
|
|
|
// Free any heap memory held by our subtrees
|
|
|
|
EnumSubtreeStateClear(&(pEnumState->UserState));
|
|
EnumSubtreeStateClear(&(pEnumState->MachineState));
|
|
|
|
|
|
// reset everything in this state
|
|
|
|
memset(pEnumState, 0, sizeof(*pEnumState));
|
|
}
|
|
|
|
|
|
BOOL EnumStateIsEmpty(EnumState* pEnumState)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Returns whether or not an enumeration state is empty.
|
|
An enumeration state is empty if it is not associated
|
|
with any particular registry key handle
|
|
|
|
Arguments:
|
|
|
|
pEnumState - enumeration state to clear
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
return pEnumState->hKey == NULL;
|
|
}
|
|
|
|
NTSTATUS EnumStateCopy(
|
|
EnumState* pDestState,
|
|
EnumState* pEnumState)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Copies an enumeration state for one hkey
|
|
to the state for another hkey -- note that it the
|
|
does not change the hkey referred to by the destination
|
|
state, it just makes pDestState->hKey's state the
|
|
same as pEnumState's
|
|
|
|
Arguments:
|
|
|
|
pDestState - enumeration state which is destination
|
|
of the copy
|
|
pEnumState - source enumeration for the copy
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS for success, other error code on error
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
PKEY_NODE_INFORMATION pKeyInfoUser;
|
|
PKEY_NODE_INFORMATION pKeyInfoMachine;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
|
|
// Copy simple data
|
|
|
|
pDestState->Direction = pEnumState->Direction;
|
|
pDestState->LastLocation = pEnumState->LastLocation;
|
|
|
|
pDestState->dwLastRequest = pEnumState->dwLastRequest;
|
|
pDestState->dwThreadId = pEnumState->dwThreadId;
|
|
|
|
|
|
// Free existing data before we overwrite it -- note that the pKeyInfo can point to a fixed buffer inside the state or
|
|
// a heap allocated buffer, so we must see which one it points to before we decide to free it
|
|
|
|
if (pDestState->UserState.pKeyInfo &&
|
|
(pDestState->UserState.pKeyInfo != (PKEY_NODE_INFORMATION) pDestState->UserState.KeyInfoBuffer)) {
|
|
RegClassHeapFree(pDestState->UserState.pKeyInfo);
|
|
pDestState->UserState.pKeyInfo = NULL;
|
|
}
|
|
|
|
if (pDestState->MachineState.pKeyInfo &&
|
|
(pDestState->MachineState.pKeyInfo != (PKEY_NODE_INFORMATION) pDestState->MachineState.KeyInfoBuffer)) {
|
|
RegClassHeapFree(pDestState->MachineState.pKeyInfo);
|
|
pDestState->MachineState.pKeyInfo = NULL;
|
|
}
|
|
|
|
|
|
// easy way to copy states -- we'll have to fix up below though since pKeyInfo can be
|
|
// self-referential.
|
|
|
|
memcpy(&(pDestState->UserState), &(pEnumState->UserState), sizeof(pEnumState->UserState));
|
|
memcpy(&(pDestState->MachineState), &(pEnumState->MachineState), sizeof(pEnumState->MachineState));
|
|
|
|
pKeyInfoUser = NULL;
|
|
pKeyInfoMachine = NULL;
|
|
|
|
|
|
// Copy new data -- as above, keep in mind that pKeyInfo can be self-referential, so check
|
|
// for that before deciding whether to allocate heap or use the internal fixed buffer of the
|
|
// structure.
|
|
|
|
if (pEnumState->UserState.pKeyInfo &&
|
|
((pEnumState->UserState.pKeyInfo != (PKEY_NODE_INFORMATION) pEnumState->UserState.KeyInfoBuffer))) {
|
|
|
|
pKeyInfoUser = (PKEY_NODE_INFORMATION)
|
|
RegClassHeapAlloc(pEnumState->UserState.cbKeyInfo);
|
|
|
|
if (!pKeyInfoUser) {
|
|
Status = STATUS_NO_MEMORY;
|
|
}
|
|
|
|
pDestState->UserState.pKeyInfo = pKeyInfoUser;
|
|
|
|
RtlCopyMemory(pDestState->UserState.pKeyInfo,
|
|
pEnumState->UserState.pKeyInfo,
|
|
pEnumState->UserState.cbKeyInfo);
|
|
} else {
|
|
if (pDestState->UserState.pKeyInfo) {
|
|
pDestState->UserState.pKeyInfo = (PKEY_NODE_INFORMATION) pDestState->UserState.KeyInfoBuffer;
|
|
}
|
|
}
|
|
|
|
if (pEnumState->MachineState.pKeyInfo &&
|
|
((pEnumState->MachineState.pKeyInfo != (PKEY_NODE_INFORMATION) pEnumState->MachineState.KeyInfoBuffer))) {
|
|
|
|
pKeyInfoMachine = (PKEY_NODE_INFORMATION)
|
|
RegClassHeapAlloc(pEnumState->MachineState.cbKeyInfo);
|
|
|
|
if (!pKeyInfoMachine) {
|
|
Status = STATUS_NO_MEMORY;
|
|
}
|
|
|
|
pDestState->MachineState.pKeyInfo = pKeyInfoMachine;
|
|
|
|
RtlCopyMemory(pDestState->MachineState.pKeyInfo,
|
|
pEnumState->MachineState.pKeyInfo,
|
|
pEnumState->MachineState.cbKeyInfo);
|
|
} else {
|
|
if (pDestState->MachineState.pKeyInfo) {
|
|
pDestState->MachineState.pKeyInfo = (PKEY_NODE_INFORMATION) pDestState->MachineState.KeyInfoBuffer;
|
|
}
|
|
}
|
|
|
|
|
|
// On error, make sure we clean up.
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
if (pKeyInfoUser) {
|
|
RegClassHeapFree(pKeyInfoUser);
|
|
}
|
|
|
|
if (pKeyInfoMachine) {
|
|
RegClassHeapFree(pKeyInfoMachine);
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
void EnumSubtreeStateClear(EnumSubtreeState* pTreeState)
|
|
/*
|
|
Routine Description:
|
|
|
|
This function frees the key data associated with this
|
|
subtree state
|
|
|
|
Arguments:
|
|
|
|
pTreeState -- tree state to clear
|
|
|
|
Return Value: None.
|
|
|
|
Note:
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
// see if we're using pre-alloced buffer -- if not, free it
|
|
|
|
if (pTreeState->pKeyInfo && (((LPBYTE) pTreeState->pKeyInfo) != pTreeState->KeyInfoBuffer)) {
|
|
|
|
RegClassHeapFree(pTreeState->pKeyInfo);
|
|
}
|
|
|
|
pTreeState->pKeyInfo = NULL;
|
|
}
|
|
|
|
NTSTATUS EnumSubtreeStateCopyKeyInfo(
|
|
EnumSubtreeState* pTreeState,
|
|
KEY_INFORMATION_CLASS KeyInformationClass,
|
|
PVOID pDestKeyInfo,
|
|
ULONG cbDestKeyInfo,
|
|
PULONG pcbResult)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Copies information about a key into a buffer supplied by the caller
|
|
|
|
Arguments:
|
|
|
|
pTreeState - subtree tate from which to copy
|
|
|
|
KeyInformationClass - the type of buffer supplied by the caller -- either
|
|
a KEY_NODE_INFORMATION or KEY_BASIC_INFORMATION structure
|
|
|
|
pDestKeyInfo - caller's buffer for key information
|
|
|
|
cbDestKeyInfo - size of caller's buffer
|
|
|
|
pcbResult - out param -- amount of data to be written to caller's buffer
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
ULONG cbNeeded;
|
|
|
|
ASSERT((KeyInformationClass == KeyNodeInformation) ||
|
|
(KeyInformationClass == KeyBasicInformation));
|
|
|
|
|
|
// Find out how big the caller's buffer needs to be. This
|
|
// depends on whether the caller specified full or node information
|
|
// as well as the size of the variable size members of those
|
|
// structures
|
|
|
|
|
|
if (KeyNodeInformation == KeyInformationClass) {
|
|
|
|
PKEY_NODE_INFORMATION pNodeInformation;
|
|
|
|
|
|
// Copy fixed length pieces first -- caller expects them to
|
|
// be set even when the variable length members are not large enough
|
|
|
|
|
|
|
|
// Set ourselves to point to caller's buffer
|
|
|
|
pNodeInformation = (PKEY_NODE_INFORMATION) pDestKeyInfo;
|
|
|
|
|
|
// Copy all fixed-length pieces of structure
|
|
|
|
pNodeInformation->LastWriteTime = pTreeState->pKeyInfo->LastWriteTime;
|
|
pNodeInformation->TitleIndex = pTreeState->pKeyInfo->TitleIndex;
|
|
pNodeInformation->ClassOffset = pTreeState->pKeyInfo->ClassOffset;
|
|
pNodeInformation->ClassLength = pTreeState->pKeyInfo->ClassLength;
|
|
pNodeInformation->NameLength = pTreeState->pKeyInfo->NameLength;
|
|
|
|
|
|
// Take care of the size of the node information structure
|
|
|
|
cbNeeded = sizeof(KEY_NODE_INFORMATION);
|
|
|
|
if (cbDestKeyInfo < cbNeeded) {
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
|
|
// Add in the size of the variable length members
|
|
|
|
cbNeeded += pTreeState->pKeyInfo->NameLength;
|
|
cbNeeded += pTreeState->pKeyInfo->ClassLength;
|
|
cbNeeded -= sizeof(WCHAR); // the structure's Name member is already set to 1,
|
|
// so that one has already been accounted for in
|
|
// the size of the structure
|
|
|
|
} else {
|
|
|
|
PKEY_BASIC_INFORMATION pBasicInformation;
|
|
|
|
|
|
// Copy fixed length pieces first -- caller expects them to
|
|
// be set even when the variable length members are not large enough
|
|
|
|
|
|
|
|
// Set ourselves to point to caller's buffer
|
|
|
|
pBasicInformation = (PKEY_BASIC_INFORMATION) pDestKeyInfo;
|
|
|
|
|
|
// Copy all fixed-length pieces of structure
|
|
|
|
pBasicInformation->LastWriteTime = pTreeState->pKeyInfo->LastWriteTime;
|
|
pBasicInformation->TitleIndex = pTreeState->pKeyInfo->TitleIndex;
|
|
pBasicInformation->NameLength = pTreeState->pKeyInfo->NameLength;
|
|
|
|
|
|
cbNeeded = sizeof(KEY_BASIC_INFORMATION);
|
|
|
|
|
|
// Take care of the size of the basic information structure
|
|
|
|
if (cbDestKeyInfo < cbNeeded) {
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
|
|
// Add in the size of the variable length members
|
|
|
|
cbNeeded += pTreeState->pKeyInfo->NameLength;
|
|
cbNeeded -= sizeof(WCHAR); // the structure's Name member is already set to 1,
|
|
// so that one has already been accounted for in
|
|
// the size of the structure
|
|
}
|
|
|
|
|
|
// Store the amount needed for the caller
|
|
|
|
*pcbResult = cbNeeded;
|
|
|
|
|
|
// See if the caller supplied enough buffer -- leave if not
|
|
|
|
if (cbDestKeyInfo < cbNeeded) {
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
|
|
// We copy variable-length information differently depending
|
|
// on which type of structure was passsed in
|
|
|
|
if (KeyNodeInformation == KeyInformationClass) {
|
|
|
|
PBYTE pDestClass;
|
|
PBYTE pSrcClass;
|
|
PKEY_NODE_INFORMATION pNodeInformation;
|
|
|
|
pNodeInformation = (PKEY_NODE_INFORMATION) pDestKeyInfo;
|
|
|
|
|
|
// Copy variable length pieces such as name and class
|
|
|
|
RtlCopyMemory(pNodeInformation->Name,
|
|
pTreeState->pKeyInfo->Name,
|
|
pTreeState->pKeyInfo->NameLength);
|
|
|
|
|
|
// Only copy the class if it exists
|
|
|
|
if (pTreeState->pKeyInfo->ClassOffset >= 0) {
|
|
pDestClass = ((PBYTE) pNodeInformation) + pTreeState->pKeyInfo->ClassOffset;
|
|
pSrcClass = ((PBYTE) pTreeState->pKeyInfo) + pTreeState->pKeyInfo->ClassOffset;
|
|
RtlCopyMemory(pDestClass, pSrcClass, pTreeState->pKeyInfo->ClassLength);
|
|
}
|
|
|
|
} else {
|
|
|
|
PKEY_BASIC_INFORMATION pBasicInformation;
|
|
|
|
|
|
// Set ourselves to point to caller's buffer
|
|
|
|
pBasicInformation = (PKEY_BASIC_INFORMATION) pDestKeyInfo;
|
|
|
|
|
|
// Copy variable length pieces -- only name is variable length
|
|
|
|
RtlCopyMemory(pBasicInformation->Name,
|
|
pTreeState->pKeyInfo->Name,
|
|
pTreeState->pKeyInfo->NameLength);
|
|
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS EnumClassKey(
|
|
HKEY hKey,
|
|
EnumSubtreeState* pTreeState)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Enumerates a subkey for a subtree state -- calls the kernel
|
|
|
|
Arguments:
|
|
|
|
hKey - key we want the kernel to enumerate
|
|
pTreeState - subtree state -- either a user or machine subtree
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
PKEY_NODE_INFORMATION pCurrentKeyInfo;
|
|
NTSTATUS Status;
|
|
|
|
ASSERT(!(pTreeState->pKeyInfo));
|
|
|
|
|
|
// First try to use the buffer built in to the subtree state
|
|
|
|
pCurrentKeyInfo = (PKEY_NODE_INFORMATION) pTreeState->KeyInfoBuffer;
|
|
|
|
|
|
// Query for the necessary information about the supplied key.
|
|
|
|
|
|
Status = NtEnumerateKey( hKey,
|
|
pTreeState->iSubKey,
|
|
KeyNodeInformation,
|
|
pCurrentKeyInfo,
|
|
sizeof(pTreeState->KeyInfoBuffer),
|
|
&(pTreeState->cbKeyInfo));
|
|
|
|
ASSERT( Status != STATUS_BUFFER_TOO_SMALL );
|
|
|
|
|
|
// If the subtree state's buffer isn't big enough, we'll have
|
|
// to ask the heap to give us one.
|
|
|
|
if (STATUS_BUFFER_OVERFLOW == Status) {
|
|
|
|
pCurrentKeyInfo = RegClassHeapAlloc(pTreeState->cbKeyInfo);
|
|
|
|
// If the memory allocation fails, return a Registry Status.
|
|
|
|
if( ! pCurrentKeyInfo ) {
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
|
|
// Query for the necessary information about the supplied key.
|
|
|
|
|
|
Status = NtEnumerateKey( hKey,
|
|
pTreeState->iSubKey,
|
|
KeyNodeInformation,
|
|
pCurrentKeyInfo,
|
|
pTreeState->cbKeyInfo,
|
|
&(pTreeState->cbKeyInfo));
|
|
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
|
|
// set the subtree state's reference to point
|
|
// to the location of the data
|
|
|
|
pTreeState->pKeyInfo = pCurrentKeyInfo;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
NTSTATUS GetSubKeyCount(
|
|
HKEY hkClassKey,
|
|
LPDWORD pdwUserSubKeys)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Counts the number of subkeys under a key
|
|
|
|
Arguments:
|
|
|
|
hkClassKey - key whose subkeys we wish to count
|
|
pdwUserSubKeys - out param for number of subkeys
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
PKEY_FULL_INFORMATION KeyFullInfo;
|
|
ULONG BufferLength;
|
|
BYTE PrivateKeyFullInfo[ sizeof( KEY_FULL_INFORMATION ) +
|
|
ENUM_DEFAULT_CLASS_SIZE ];
|
|
|
|
|
|
// Initialize out params
|
|
|
|
*pdwUserSubKeys = 0;
|
|
|
|
|
|
// BUGBUG: don't need to pass in everything here
|
|
|
|
|
|
|
|
// Set up to query kernel for subkey information
|
|
|
|
KeyFullInfo = (PKEY_FULL_INFORMATION) PrivateKeyFullInfo;
|
|
BufferLength = sizeof(PrivateKeyFullInfo);
|
|
|
|
Status = QueryKeyInfo(
|
|
hkClassKey,
|
|
&KeyFullInfo,
|
|
BufferLength,
|
|
FALSE,
|
|
(USHORT) (BufferLength - sizeof(PKEY_FULL_INFORMATION))
|
|
);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
// set the out param with the subkey data from the kernel call
|
|
|
|
*pdwUserSubKeys = KeyFullInfo->SubKeys;
|
|
|
|
|
|
// The QueryKeyInfo function will allocate memory if the buffer
|
|
// we pass in is too small, so we should free it after we've
|
|
// retrieved the data.
|
|
|
|
if( KeyFullInfo != ( PKEY_FULL_INFORMATION )PrivateKeyFullInfo ) {
|
|
RegClassHeapFree(KeyFullInfo );
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS ClassKeyCountSubKeys(
|
|
HKEY hKey,
|
|
HKEY hkUser,
|
|
HKEY hkMachine,
|
|
DWORD cMax,
|
|
LPDWORD pcSubKeys)
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
Counts the total number of subkeys of a special key -- i.e.
|
|
the sum of the subkeys in the user and machine portions
|
|
of that special key minus duplicates.
|
|
|
|
Arguments:
|
|
|
|
hkUser - user part of special key
|
|
|
|
hkMachine - machine part of special key
|
|
|
|
cMax - Maximum number of keys to count -- if
|
|
zero, this is ignored
|
|
|
|
pcSubKeys - out param -- count of subkeys
|
|
|
|
Return Value:
|
|
|
|
Returns NT_SUCCESS (0) for success; error-code for failure.
|
|
|
|
Notes:
|
|
|
|
This is INCREDIBLY expensive if either hkUser or hkMachine
|
|
has more than a few subkeys. It essentially merges two
|
|
sorted lists by enumerating in both the user and machine
|
|
locations, and viewing them as a merged list by doing
|
|
comparisons betweens items in each list --
|
|
separate user and machine pointers are advanced according
|
|
to the results of the comparison. This means that if there are
|
|
N keys under hkUser and M keys under hkMachine, this function
|
|
will make N+M calls to the kernel to enumerate the keys.
|
|
|
|
This is currently the only way to do this -- before, an approximation
|
|
was used in which the sum of the number of subkeys in the
|
|
user and machine versions was returned. This method didn't take
|
|
duplicates into account, and so it overestimated the number of keys.
|
|
This was not thought to be a problem since there is no guarantee
|
|
to callers that the number they receive is completely up to date,
|
|
but it turns out that there are applications that make that assumption
|
|
(such as regedt32) that do not function properly unless the
|
|
exact number is returned.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
BOOL fCheckUser;
|
|
BOOL fCheckMachine;
|
|
EnumSubtreeState UserTree;
|
|
EnumSubtreeState MachineTree;
|
|
DWORD cMachineKeys;
|
|
DWORD cUserKeys;
|
|
OBJECT_ATTRIBUTES Obja;
|
|
HKEY hkUserCount;
|
|
HKEY hkMachineCount;
|
|
HKEY hkNewKey;
|
|
|
|
UNICODE_STRING EmptyString = {0, 0, 0};
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
hkNewKey = NULL;
|
|
|
|
cMachineKeys = 0;
|
|
cUserKeys = 0;
|
|
|
|
|
|
// Initialize ourselves to check in both the user
|
|
// and machine hives for subkeys
|
|
|
|
fCheckUser = (hkUser != NULL);
|
|
fCheckMachine = (hkMachine != NULL);
|
|
|
|
memset(&UserTree, 0, sizeof(UserTree));
|
|
memset(&MachineTree, 0, sizeof(MachineTree));
|
|
|
|
|
|
// We can't be sure that the user key was opened
|
|
// with the right permissions so we'll open
|
|
// a version that has the correct permissions
|
|
|
|
if (fCheckUser && (hkUser == hKey)) {
|
|
InitializeObjectAttributes(&Obja, &EmptyString, OBJ_CASE_INSENSITIVE, hkUser, NULL);
|
|
Status = NtOpenKey(&hkNewKey, KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE, &Obja);
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
hkUserCount = hkNewKey;
|
|
} else {
|
|
hkUserCount = hkUser;
|
|
}
|
|
|
|
if (fCheckMachine && (hkMachine == hKey)) {
|
|
ASSERT(!hkUserCount);
|
|
|
|
InitializeObjectAttributes(&Obja, &EmptyString, OBJ_CASE_INSENSITIVE, hkMachine, NULL);
|
|
Status = NtOpenKey(&hkNewKey, KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE, &Obja);
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
hkMachineCount = hkNewKey;
|
|
} else {
|
|
hkMachineCount = hkMachine;
|
|
}
|
|
|
|
|
|
// Now check to see how many keys are in the user subtree
|
|
|
|
if (fCheckUser) {
|
|
Status = GetSubKeyCount(hkUserCount, &cUserKeys);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
// We only need to enumerate the user portion if it has subkeys
|
|
|
|
fCheckUser = (cUserKeys != 0);
|
|
}
|
|
|
|
|
|
// Now check to see how many keys are in the user subtree
|
|
|
|
if (fCheckMachine) {
|
|
Status = GetSubKeyCount(hkMachineCount, &cMachineKeys);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
// We only need to enumerate the machine portion if it has subkeys
|
|
|
|
fCheckMachine = (cMachineKeys != 0);
|
|
}
|
|
|
|
if (!fCheckUser) {
|
|
*pcSubKeys = cMachineKeys;
|
|
Status = STATUS_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!fCheckMachine) {
|
|
*pcSubKeys = cUserKeys;
|
|
Status = STATUS_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
ASSERT(fCheckMachine && fCheckUser);
|
|
|
|
*pcSubKeys = 0;
|
|
|
|
|
|
// Keep enumerating subkeys until one of the locations
|
|
// runs out of keys
|
|
|
|
for (;;) {
|
|
|
|
NTSTATUS EnumStatus;
|
|
|
|
|
|
// If we can still check in the user hive and we
|
|
// are missing user key info, query the kernel for it
|
|
|
|
if (!(UserTree.pKeyInfo)) {
|
|
EnumStatus = EnumClassKey(
|
|
hkUserCount,
|
|
&UserTree);
|
|
|
|
|
|
// If there are no more user subkeys, set our
|
|
// flag so that we no longer look in the user portion
|
|
// for subkeys
|
|
|
|
if (!NT_SUCCESS(EnumStatus)) {
|
|
if (STATUS_NO_MORE_ENTRIES == EnumStatus) {
|
|
|
|
*pcSubKeys += cMachineKeys;
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
} else {
|
|
Status = EnumStatus;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// if we can still check in the machine hive and
|
|
// we are missing machine info, query for it
|
|
|
|
if (!(MachineTree.pKeyInfo)) {
|
|
|
|
EnumStatus = EnumClassKey(
|
|
hkMachineCount,
|
|
&MachineTree);
|
|
|
|
|
|
// Turn off checking in machine if there are
|
|
// no more machine keys
|
|
|
|
if (!NT_SUCCESS(EnumStatus)) {
|
|
if (STATUS_NO_MORE_ENTRIES == EnumStatus) {
|
|
|
|
*pcSubKeys += cUserKeys;
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
} else {
|
|
Status = EnumStatus;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// If we have keys in both user and machine, we need to compare
|
|
// the key names to see when to advance our subtree pointers
|
|
|
|
{
|
|
|
|
LONG lCompare;
|
|
|
|
UNICODE_STRING MachineKeyName;
|
|
UNICODE_STRING UserKeyName;
|
|
|
|
MachineKeyName.Buffer = MachineTree.pKeyInfo->Name;
|
|
MachineKeyName.Length = (USHORT) MachineTree.pKeyInfo->NameLength;
|
|
|
|
UserKeyName.Buffer = UserTree.pKeyInfo->Name;
|
|
UserKeyName.Length = (USHORT) UserTree.pKeyInfo->NameLength;
|
|
|
|
|
|
// Do the comparison of user and machine keys
|
|
|
|
lCompare =
|
|
RtlCompareUnicodeString(&UserKeyName, &MachineKeyName, TRUE);
|
|
|
|
|
|
// User is smaller, so move our user pointer up and clear it
|
|
// so we'll query for user data next time
|
|
|
|
if (lCompare <= 0) {
|
|
EnumSubtreeStateClear(&UserTree);
|
|
UserTree.iSubKey++;
|
|
cUserKeys--;
|
|
}
|
|
|
|
|
|
// Machine is smaller, so move our user pointer up and clear it
|
|
// so we'll query for machine data next time
|
|
|
|
if (lCompare >= 0) {
|
|
EnumSubtreeStateClear(&MachineTree);
|
|
MachineTree.iSubKey++;
|
|
cMachineKeys--;
|
|
}
|
|
|
|
|
|
// Increase the total number of subkeys
|
|
|
|
(*pcSubKeys)++;
|
|
|
|
}
|
|
|
|
|
|
// Only enumerate up to max -- the caller
|
|
// doesn't need to go all the way to the end
|
|
|
|
if (cMax && (*pcSubKeys > cMax)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// Free any buffer held by these subtree states
|
|
|
|
EnumSubtreeStateClear(&UserTree);
|
|
EnumSubtreeStateClear(&MachineTree);
|
|
|
|
cleanup:
|
|
|
|
if (hkNewKey) {
|
|
NtClose(hkNewKey);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
#endif // LOCAL
|
|
|