/*++



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

