/*++ 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 #include "regrpc.h" #include "localreg.h" #include "regclass.h" #include "regecls.h" #include 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