2893 lines
76 KiB
C
2893 lines
76 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:
|
||
**************************
|
||
|
||
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,
|
||
KEY_INFORMATION_CLASS KeyInformationClass,
|
||
PVOID *ppKeyInfo,
|
||
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;
|
||
|
||
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:
|
||
This recordset merging is all in user mode --
|
||
should be moved to the kernel for perf and other reasons ??
|
||
|
||
--*/
|
||
{
|
||
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),
|
||
ULongToPtr((const unsigned long)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),
|
||
ULongToPtr((const unsigned long)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),
|
||
ULongToPtr((const unsigned long)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);
|
||
keyinfo._fAllocedNameBuf = FALSE;
|
||
|
||
//
|
||
// 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),
|
||
ULongToPtr((const unsigned long)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 bad 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),
|
||
ULongToPtr((const unsigned long)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,
|
||
ULongToPtr((const unsigned long)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 (((LONG)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_CACHED_INFORMATION KeyCachedInfo;
|
||
ULONG BufferLength;
|
||
BYTE PrivateKeyCachedInfo[ sizeof( KEY_CACHED_INFORMATION ) ];
|
||
|
||
//
|
||
// Initialize out params
|
||
//
|
||
*pdwUserSubKeys = 0;
|
||
|
||
//
|
||
// Set up to query kernel for subkey information
|
||
//
|
||
KeyCachedInfo = (PKEY_CACHED_INFORMATION) PrivateKeyCachedInfo;
|
||
BufferLength = sizeof(PrivateKeyCachedInfo);
|
||
|
||
Status = QueryKeyInfo(
|
||
hkClassKey,
|
||
KeyCachedInformation,
|
||
&KeyCachedInfo,
|
||
BufferLength,
|
||
FALSE,
|
||
0
|
||
);
|
||
|
||
if (NT_SUCCESS(Status)) {
|
||
//
|
||
// set the out param with the subkey data from the kernel call
|
||
//
|
||
*pdwUserSubKeys = KeyCachedInfo->SubKeys;
|
||
|
||
ASSERT( KeyCachedInfo == ( PKEY_CACHED_INFORMATION )PrivateKeyCachedInfo );
|
||
}
|
||
|
||
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)) {
|
||
|
||
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
|
||
|