/*++


Copyright (c) 1991  Microsoft Corporation

Module Name:

    RegClass.c

Abstract:

    This module contains routines to manipulate class registration
    registry keys for the win32 registry apis.  These routines are called
    from several of the functions for manipulating registry, including
    the functions that open, enumerate, create, and delete keys.

Author:

    Adam P. Edwards     (adamed)  14-Nov-1997

Key Functions:

    OpenCombinedClassesRoot
    BaseRegGetKeySemantics
    BaseRegOpenClassKey
    BaseRegOpenClassKeyFromLocation
    BaseRegGetUserAndMachineClass

Notes:

****************************************************
    PLEASE READ THIS IF YOU ARE NEW TO THIS CODE!!!!
****************************************************

    Starting with NT5, the HKEY_CLASSES_ROOT key is per-user
    instead of per-machine -- previously, HKCR was an alias for
    HKLM\Software\Classes.

    The per-user HKCR combines machine classes stored it the
    traditional HKLM\Software\Classes location with classes
    stored in HKCU\Software\Classes.

    Certain keys, such as CLSID, will have subkeys that come
    from both the machine and user locations.  When there is a conflict
    in key names, the user oriented key overrides the other one --
    only the user key is seen in that case.

    Here are the key ideas for this implementation:

    1. The changes for this module only affect keys under
       HKEY_CLASSES_ROOT. Only the Local registry
       implementation supports HKCR so all the changes are
       local only, they do not exist in the
       remote rpc registry server.

    2. We parse each key under HKCR as

         <prefix>\<intermediate>\<special>[\<classreg>]\[<lastelement>]

       where <prefix> is one of the forms

         \Registry\Machine\Software\Classes
         \Registry\User\<sid>\Software\Classes
         \Registry\User\<sid>_Classes

       <intermediate> can be a subpath of arbitrary length

       <special> is a certain list of keys, shown below in the
         gSpecialSubtrees table, e.g. IID, CLSID.

       <classreg> is any subkey of <special>.  <lastelement> is
         the remainder of the path.

    3. In order to quickly distinguish keys in HKCR from keys not in HKCR, we
       use tag bits on each registry handle that we return from an open or create
       if the key is under HKCR. When the HKCR predefined handle is opened,
       we set a tag on its handle index -- any children open or created with a
       parent key whose tag is set like this will inherit the tag. There are other
       tags, such as those for local and remote regkeys, already in use prior
       to the implementation of per user class registration in NT5. Please see
       the header file for more information on how to interpret the tags.

    4. The special keys have the following properties which differentiate
       them from standard registry keys:

       a. The children of a special key come from both HKLM\Software\Classes
          and HKCU\Software\Classes.  Thus, since CLSID is a special key,
          if HKLM\Software\Classes\CLSID\Key1 exists and
          HKCU\Software\Classes\CLSID\Key2 exists, one would find the keys
          Key1 and Key2 under HKCR\CLSID.
       b. If the same key exists in both the user and machine locations, only
          the user version of the key is seen under HKCR.

     5. To create the illusion described above, the code for several api's
        had to be modified:

        a. RegOpenKeyEx -- for HKCR subkeys, this api was modified to look
           for the key to open first in the user part of the registry,
           then the machine part if the user version did not exist. All
           keys opened with HKCR as an ancestor get a bit set in the handle
           index.

        b. RegCreateKeyEx -- modified in a fashion similar to RegOpenKeyEx.
        c. RegDeleteKey  -- modified to find key to delete in fashion similar
           to RegOpenKeyEx.

        d. RegEnumKeyEx -- extensive changes for HKCR. Previously this api was
           simply a wrapper to the kernel version. This is insufficient now
           because the kernel knows nothing of our hkcr scheme.  See regecls.*
           for details.

        e. RegQueryInfoKey -- changes related to RegEnumKeyEx changes -- see
           regecls.*, regqkey.c.

    It should be noted that HKCU\Software\Classes is not the true
    location of the user-only class data.  If it were, all the class
    data would be in ntuser.dat, which roams with the user.  Since
    class data can get very large, installation of a few apps
    would cause HKCU (ntuser.dat) to grow from a manageable size
    to many megabytes.  Since user-specific class data comes from
    the directory, it does not need to roam and therefore it was
    separated from HKCU (ntuser.dat) and stored in another hive
    mounted under HKEY_USERS.

    It is still desirable to allow access to this hive through
    HKCU\Software\Classes, so we use some trickery (symlinks) to
    make it seem as if the user class data exists there.

**************************
    IMPORTANT ASSUMPTIONS:
**************************

    This code assumes that all special keys exist in both
    HKEY_LOCAL_MACHINE\Software\Classes and HKEY_CURRENT_USER\Software\Classes.
    The code may break if this is not true.

--*/


#ifdef LOCAL

#include <rpc.h>
#include <string.h>
#include <wchar.h>
#include "regrpc.h"
#include "localreg.h"
#include "regclass.h"
#include "regecls.h"
#include <malloc.h>


NTSTATUS QueryKeyInfo(
    HKEY hKey,
    PKEY_FULL_INFORMATION* ppKeyFullInfo,
    ULONG BufferLength,
    BOOL fClass,
    USHORT MaxClassLength);

extern HKEY HKEY_ClassesRoot;

BOOL            gbCombinedClasses = TRUE;

PKEY_VALUE_PARTIAL_INFORMATION gpNameSpaceKeyInfo = NULL;

#if defined(_REGCLASS_MALLOC_INSTRUMENTED_)

RTL_CRITICAL_SECTION gRegClassHeapCritSect;
DWORD                gcbAllocated = 0;
DWORD                gcAllocs = 0;
DWORD                gcbMaxAllocated = 0;
DWORD                gcMaxAllocs = 0;
PVOID                gpvAllocs;

#endif // defined(_REGCLASS_MALLOC_INSTRUMENTED_)

UNICODE_STRING gMachineClassesName = {
    REG_MACHINE_CLASSES_HIVE_NAMELEN,
    REG_MACHINE_CLASSES_HIVE_NAMELEN,
    REG_MACHINE_CLASSES_HIVE_NAME};


error_status_t
OpenCombinedClassesRoot(
     IN REGSAM samDesired,
    OUT HANDLE * phKey
    )
/*++

Routine Description:

    Attempts to open the the HKEY_CLASSES_ROOT predefined handle.

Arguments:

    ServerName - Not used.
    samDesired - This access mask describes the desired security access
                 for the key.
    phKey - Returns a handle to the key \REGISTRY\MACHINE\SOFTWARE\CLASSES.

Return Value:

    Returns ERROR_SUCCESS (0) for success; error-code for failure.

--*/

{
    OBJECT_ATTRIBUTES       Obja;
    NTSTATUS                Status;
    UNICODE_STRING          UsersHive;
    UNICODE_STRING          UsersMergedHive;

    // first try for a per-user HKCR
    RtlFormatCurrentUserKeyPath( &UsersHive );

    UsersMergedHive.MaximumLength = UsersHive.MaximumLength +
        REG_USER_HIVE_CLASSES_SUFFIXLEN + REG_CHAR_SIZE;

    //
    // alloca does not return NULL on failure, it throws an exception,
    // so return value is not checked.
    //
    UsersMergedHive.Buffer = alloca(UsersMergedHive.MaximumLength);

    RtlCopyUnicodeString(&UsersMergedHive, &UsersHive );

    // add the _Merged_Classes suffix
    Status = RtlAppendUnicodeToString( &UsersMergedHive, REG_USER_HIVE_CLASSES_SUFFIX);

    ASSERT(NT_SUCCESS(Status));

    //
    // Initialize the OBJECT_ATTRIBUTES structure so that it creates
    // (opens) the key "\HKU\<sid>_Merged_Classes" with a Security
    // Descriptor that allows everyone complete access.
    //

    InitializeObjectAttributes(
        &Obja,
        &UsersMergedHive,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL
        );

    Status = NtOpenKey(
                phKey,
                samDesired, // MAXIMUM_ALLOWED,
                &Obja
                );

    RtlFreeUnicodeString( &UsersHive );

    //
    // This key is the ancestor of all keys in HKCR, so
    // we must mark its handle so that its origin in HKCR
    // is propagated to all children opened with this handle
    //
    if (NT_SUCCESS(Status)) {

        *phKey = REG_CLASS_SET_SPECIAL_KEY(*phKey);

    }

    return Status;
}



NTSTATUS BaseRegGetKeySemantics(
    HKEY            hkParent,
    PUNICODE_STRING pSubKey,
    SKeySemantics*  pKeySemantics)
/*++

Routine Description:

    This function parses a key in HKEY_CLASSES_ROOT.  It is used to determine if a given key
    is a class registration unit key, as well as other syntactic / semantic information about
    the key.  It sets the value of pfIsClsRegKey to TRUE if it is, FALSE if not.

    The key in question is defined by the (hkParent, pSubKey) pair.

    Definitions for terms such as Prefix, Special Key, and class registration can
    be found at the top of this module.

Arguments:

    hkParent      - parent portion of key
    pSubKey       - child portion of key
    pKeySemantics - pointer to struct containing key semantic information -- the
                    following members of this structure are affected:

                    _fUser: TRUE if this key is rooted in HKEY_USERS, FALSE if not
                    _fMachine: TRUE if this key is rooted in HKLM, FALSE if not
                    _fCombinedClasses: TRUE if this key is rooted in HKEY_USERS\\<Sid>_Classes
                    _fClassRegistration: TRUE if this key is a class registration unit
                    _fClassRegParent: TRUE if this key is the parent of a class registration unit
                    _ichKeyStart: index to start of a class reg after the prefix -- this is after
                                 the pathsep which follows the prefix
                    _cbPrefixLen: Length (in bytes) of prefix from start of full path
                    _cbSpecialKey: Length (in bytes) of the name of the special key -- this is
                                 not from the start of the full path, just that key name only. It
                                 includes an initial pathsep.
                    _cbClassRegKey: length of class reg key name (not from start of full path).
                                 Includes an initial pathsep.
                    _cbFullPath: size of buffer structure pointed to by _pFullPath.  On return,
                                 this member is set to the number of bytes written to _pFullPath
                                 by the function, or the required number of bytes if the buffer
                                 passed in was too small
                    _pFullPath: KEY_NAME_INFORMATION structure containing the full pathname
                                of the registry key defined by (hkParent, pSubKey). This pathname
                                is null terminated.

Returns:

    NT_SUCCESS If the function completed successfully.  If the buffer pointed to by
    pKeySemantics->_pFullPath is not large enough to hold the name of the key, the
    function returns STATUS_BUFFER_TOO_SMALL and the required size in bytes is
    written to pKeySemantics->_cbFullPath.  The caller may then reallocate the buffer
    and call this function again.  All other errors return the appropriate NTSTATUS
    failure code.

Notes:

    After calling this function and getting a successful return status, the pKeySemantics
    structure should be freed by calling BaseRegReleaseKeySemantics

--*/
{
    NTSTATUS                 Status;
    UNICODE_STRING           NameInfo;
    PKEY_NAME_INFORMATION    pNameInfo;

    USHORT                   ichClassesKeyNameEnd;
    USHORT                   ichSpecialKeyNameEnd;
    USHORT                   cbName;
    ULONG                    cbObjInfo;
    WCHAR*                   szClassRegKeyEnd;

    //
    // Save in params
    //
    cbObjInfo = pKeySemantics->_cbFullPath  - REG_CHAR_SIZE; // subtract one for trailing \0
    pNameInfo = pKeySemantics->_pFullPath;

    //
    // reset out params
    //
    memset(&(pKeySemantics->_pFullPath), 0, sizeof(*(pKeySemantics->_pFullPath->Name)));
    memset(pKeySemantics, 0, sizeof(*pKeySemantics));

    //
    // restore in params
    //
    pKeySemantics->_pFullPath = pNameInfo;
    pKeySemantics->_cbFullPath = cbObjInfo;

    //
    // Get full name of key -- first, we need to find the path
    // for the registry key hkParent
    //
    if (!hkParent) {

        //
        // If no key name was specified, the full path is simply the subkey name
        //
        pKeySemantics->_cbFullPath = REG_CHAR_SIZE;
        (pKeySemantics->_pFullPath->Name)[0] = L'\0';
        pKeySemantics->_pFullPath->NameLength = 0;
        pKeySemantics->_cbFullPath = sizeof(*(pKeySemantics->_pFullPath));

    } else {

        Status = NtQueryKey(
            hkParent,
            KeyNameInformation,
            pKeySemantics->_pFullPath,
            cbObjInfo,
            &pKeySemantics->_cbFullPath);

        if (STATUS_KEY_DELETED == Status) {
            Status = STATUS_SUCCESS;
        }

        //
        // Kernel set the _cbFullPath member to the necessary size -- tack
        // on the length of the subkey too
        //

        //
        // Kernel set the _cbFullPath member to the necessary size -- tack
        // on the length of the subkey too
        //

        pKeySemantics->_cbFullPath += pSubKey->Length + REG_CHAR_SIZE * 2;

        //
        // The retrieval of the object's name information may have succeeded,
        // but we still need to append the subkey, so verify that enough
        // space is left
        //
        if (NT_SUCCESS(Status) && (cbObjInfo < pKeySemantics->_cbFullPath)) {
            //
            // we have successfully retrieved the info from the kernel,
            // but adding the subkey, we overflow ==> allocate a buffer
            // big enough and copy the info from _pFullPath
            //
            pNameInfo = (PKEY_NAME_INFORMATION) RegClassHeapAlloc(
                pKeySemantics->_cbFullPath);

            if (!pNameInfo) {
                return STATUS_NO_MEMORY;
            }

            RtlCopyMemory(pNameInfo->Name,
                          pKeySemantics->_pFullPath->Name,
                          pKeySemantics->_pFullPath->NameLength);
            pNameInfo->NameLength = pKeySemantics->_pFullPath->NameLength;

        }

        if (!NT_SUCCESS(Status)) {

            //
            // Retry by allocating a new buffer if the kernel thought the
            // supplied buffer was too small.  Add extra padding
            // because we may need to add a null terminator and pathsep later
            //
            if (STATUS_BUFFER_OVERFLOW == Status) {

                //
                // The _cbFullPath member was to the required length in the
                // call to NtQueryKey above and includes extra padding
                // for appending more characters
                //
                pNameInfo = (PKEY_NAME_INFORMATION) RegClassHeapAlloc(
                    pKeySemantics->_cbFullPath);

                if (!pNameInfo) {
                    return STATUS_NO_MEMORY;
                }

                cbObjInfo = pKeySemantics->_cbFullPath;

                //
                // Retry -- we should have a large enough buffer now
                //
                Status = NtQueryKey(
                    hkParent,
                    KeyNameInformation,
                    pNameInfo,
                    cbObjInfo,
                    &pKeySemantics->_cbFullPath);

                if (STATUS_KEY_DELETED == Status) {
                    Status = STATUS_SUCCESS;
                }
            }

            if (!NT_SUCCESS(Status)) {

                //
                // We allocated heap for the second query, but since it failed,
                // we need to free the allocated memory.
                //
                if (pNameInfo != pKeySemantics->_pFullPath) {
                    RegClassHeapFree(pNameInfo);
                }

                return Status;
            }
        }
    }

    //
    // If this isn't set, we know a non-registry key handle was passed in since
    // all registry handles have a path associated with them, whereas other types
    // of handles may not
    //
    if (!(pNameInfo->Name)) {
        return STATUS_INVALID_HANDLE;
    }

    //
    // We will always return success after this point, so it's
    // ok to set the full path member of the structure now. Make
    // sure we set the flag indicating that we had to allocate
    // memory to store the name if that was indeed the case
    //
    if (pNameInfo != pKeySemantics->_pFullPath) {
        pKeySemantics->_fAllocedNameBuf = TRUE;
    }

    pKeySemantics->_pFullPath = pNameInfo;

    //
    // Now that we know the name of the parent key, we can concatenate it
    // with the pSubKey parameter
    //

    //
    // First we need to add a trailing pathsep and NULL terminate it
    //
    pNameInfo->Name[pNameInfo->NameLength / 2] = L'\\';
    pNameInfo->Name[pNameInfo->NameLength / 2 + 1] = L'\0';

    //
    // Get a unicode string so we can perform string operations
    //
    RtlInitUnicodeString(&NameInfo, pNameInfo->Name);

    //
    // Adjust the length to reflect the unicode string -- right
    // now it inlcudes the length of the Length member of the
    // KEY_NAME_INFORMATION structure -- we just want the length
    // of the string
    //
    pNameInfo->NameLength = NameInfo.Length;

    //
    // Now add space to the string for the subkey and slash
    //
    NameInfo.MaximumLength += pSubKey->Length + REG_CHAR_SIZE;

    //
    // append the subkey to the parent key
    //

    //
    // We made sure the buffer was big enough, so the only way this will
    // fail is if pSubKey is invalid, which will cause an
    // access violation, so no need no test
    //
    Status = RtlAppendUnicodeStringToString(&NameInfo, pSubKey);

    ASSERT(NT_SUCCESS(Status));

    //
    // if the key name isn't at least as long as the shortest
    // classes hive name, leave.
    // This assumes that
    // HKU\\Sid_Classes is shorter than
    // HKU\\Sid\\Software\\Classes
    //
    if (NameInfo.Length < REG_CLASSES_HIVE_MIN_NAMELEN) {
        return STATUS_SUCCESS;
    }

    //
    // remove any terminating pathsep
    //
    if (NameInfo.Buffer[NameInfo.Length / 2 - 1] == L'\\') {
        NameInfo.Length-= sizeof(L'\\');
    }

    //
    // We're done getting the name of the key, save its length
    // for the caller
    //
    pNameInfo->NameLength = NameInfo.Length;

    //
    // cache the name length
    //
    cbName = (USHORT) pNameInfo->NameLength;

    //
    // null terminate the name
    //
    pNameInfo->Name[cbName / REG_CHAR_SIZE] = L'\0';

    if (REG_CLASS_IS_SPECIAL_KEY(hkParent)) {
        pKeySemantics->_fCombinedClasses = TRUE;
    }

    //
    // First, see if we're even in the correct hive -- we can check
    // certain characters in the path to avoid doing extra string compares
    //
    switch (pNameInfo->Name[REG_CLASSES_FIRST_DISTINCT_ICH])
    {
    case L'M':
    case L'm':
        //
        // check if we're in the machine hive
        //
        NameInfo.Length = REG_MACHINE_CLASSES_HIVE_NAMELEN;

        //
        // Compare prefix with the name for the machine classes key
        // Set machine flag if comparison returns equality.
        //
        if (RtlEqualUnicodeString(
                &NameInfo,
                &gMachineClassesName,
                TRUE) != 0) {

            NameInfo.Length = cbName;
            ichClassesKeyNameEnd = REG_MACHINE_CLASSES_HIVE_NAMECCH;

            pKeySemantics->_fMachine = TRUE;

            break;
        }

        return STATUS_SUCCESS;

    case L'U':
    case L'u':
        //
        // check if we're in the users hive
        //
        {
            //
            // This will try to find the user prefix -- it fails
            // if we're not in the user hive and returns a zero-length
            // prefix. Set the flag if it succeeds.
            //
            ichClassesKeyNameEnd = BaseRegGetUserPrefixLength(
                &NameInfo);

            if (!ichClassesKeyNameEnd) {
                return STATUS_SUCCESS;
            }

            pKeySemantics->_fUser = TRUE;

            break;
        }

        //
        // this isn't a class registration because it isn't in any of the
        // correct trees
        //
        return STATUS_SUCCESS;

    default:

        //
        // the appropriate characters weren't in the key name, so
        // this can't be a class registration
        //
        return STATUS_SUCCESS;
    }

    //
    // At this point, we've found the prefix. The next part of the key
    // is the special key -- we look for that now.
    //
    pKeySemantics->_cbPrefixLen = ichClassesKeyNameEnd * REG_CHAR_SIZE;
    pKeySemantics->_ichKeyStart = ichClassesKeyNameEnd;

    //
    // the start of the special key
    // is the character right after the end of the prefix
    //
    if (pKeySemantics->_cbPrefixLen < pNameInfo->NameLength) {
        pKeySemantics->_ichKeyStart++;
    }

    //
    // search for a special subkey of the classes hive --
    // this will return the index in the full path of the end
    // of the special key name.
    //
    ichSpecialKeyNameEnd = BaseRegCchSpecialKeyLen(
        &NameInfo,
        ichClassesKeyNameEnd,
        pKeySemantics);

    //
    // if we find that the entire key is a special key, we're done --
    // there's nothing after it in this case so there's no more to
    // parse
    //
    if (pKeySemantics->_fClassRegParent) {
        return STATUS_SUCCESS;
    }

    //
    // at this point, we know the key itself is a class registration
    //
    pKeySemantics->_fClassRegistration = TRUE;

    pKeySemantics->_cbClassRegKey = (USHORT) pNameInfo->NameLength -
        (pKeySemantics->_cbPrefixLen + pKeySemantics->_cbSpecialKey + REG_CHAR_SIZE);

    return STATUS_SUCCESS;
}


void BaseRegReleaseKeySemantics(SKeySemantics* pKeySemantics)
/*++
Routine Description:

    This function frees resources associated with an SKeySemantics object

Arguments:

    pKeySemantics - pointer to SKeySemantics object whose resources should
        be freed

Return Value:

    None

--*/
{
    if (pKeySemantics->_fAllocedNameBuf) {
        RegClassHeapFree(pKeySemantics->_pFullPath);
    }
}



NTSTATUS BaseRegOpenClassKey(
    IN HKEY hKey,
    IN PUNICODE_STRING lpSubKey,
    IN DWORD dwOptions,
    IN REGSAM samDesired,
    OUT PHKEY phkResult)
/*++
Routine Description:

    This function is used to retry opening a class registration key.

Arguments:

    hKey - Supplies a handle to an open key.  The lpSubKey pathname
        parameter is relative to this key handle.

    lpSubKey - Supplies the downward key path to the key to open.
        lpSubKey is always relative to the key specified by hKey.

    dwOptions -- reserved.

    samDesired -- This access mask describes the desired security access
        for the key.

    phkResult -- Returns the handle to the newly opened key.

Return Value:

    Returns STATUS_SUCCESS if a key was successfully opened, otherwise it
        returns an NTSTATUS error code

    Note:

    The key must be a class registration key in order to be opened

--*/
{
    BYTE                rgNameInfoBuf[REG_MAX_CLASSKEY_LEN + REG_CHAR_SIZE];
    SKeySemantics       keyinfo;
    NTSTATUS            Status;

    //
    // Set up the buffer that will hold the name of the key
    //
    keyinfo._pFullPath = (PKEY_NAME_INFORMATION) rgNameInfoBuf;
    keyinfo._cbFullPath = sizeof(rgNameInfoBuf);
    keyinfo._fAllocedNameBuf = FALSE;

    //
    // get information about this key
    //
    Status = BaseRegGetKeySemantics(hKey, lpSubKey, &keyinfo);

    if (!NT_SUCCESS(Status)) {
        return Status;
    }

    //
    // Use the information above to look in both user and machine
    // hives for the key to be opened
    //
    Status =  BaseRegOpenClassKeyFromLocation(
        &keyinfo,
        hKey,
        lpSubKey,
        samDesired,
        LOCATION_BOTH,
        phkResult);

    BaseRegReleaseKeySemantics(&keyinfo);

    return Status;
}



NTSTATUS BaseRegOpenClassKeyFromLocation(
    SKeySemantics*  pKeyInfo,
    HKEY            hKey,
    PUNICODE_STRING lpSubKey,
    REGSAM          samDesired,
    DWORD           dwLocation,
    HKEY*           phkResult)
/*++
Routine Description:

    This function will try to open a class registration key that has no link
    in the combined classes hive -- it does this by attempting to open the
    class registration in the machine hive.  If it succeeds, it also creates
    a link to the key in the combined classes hive

Arguments:

    pKeyInfo -- structure supplying information about a key

    hKey -- Supplies a handle to an open key.  The lpSubKey pathname
        parameter is relative to this key handle.

    lpSubKey -- Supplies the downward key path to the key to open.
        lpSubKey is always relative to the key specified by hKey.

    samDesired -- This access mask describes the desired security access
        for the key.

    phkResult -- Returns the handle to the newly opened key.  If NULL,
        no open key handle is returned.

    dwLocation -- set of flags that specify where to look for the key.
        If LOCATION_MACHINE is specified, the function looks in machine.
        If LOCATION_USER is specified, the function looks in user.  Both
        flags may be specified simultaneously, so that it will look in both
        places, or LOCATION_BOTH may be specified for this purpose.  If
        the function looks in both places, an existing key in the user hive
        takes precedence over one in the machine hive.

Return Value:

    Returns STATUS_SUCCESS if a key was successfully opened, otherwise it
        returns an NTSTATUS error code

    Note:

--*/
{
    WCHAR*              FullPathBuf;
    USHORT              NewPathLen;
    UNICODE_STRING      ClassRegkey;
    UNICODE_STRING      ClassRegSubkey;
    OBJECT_ATTRIBUTES   Obja;
    NTSTATUS            Status;
    USHORT              PrefixLen;

    //
    // Init locals
    //
    Status = STATUS_OBJECT_NAME_NOT_FOUND;

    NewPathLen = (USHORT) pKeyInfo->_pFullPath->NameLength + REG_CLASSES_SUBTREE_PADDING;

    //
    // Allocate space for the remapped key -- note that if alloca
    // fails, it throws an exception, so we don't check for NULL return value
    //
    FullPathBuf = (WCHAR*) RegClassHeapAlloc(NewPathLen);

    if (!FullPathBuf) {
        return STATUS_NO_MEMORY;
    }

    //
    // Set up a unicode string to use this buffer
    //
    ClassRegkey.MaximumLength = NewPathLen;
    ClassRegkey.Buffer = FullPathBuf;

    ASSERT((dwLocation == LOCATION_USER) || (dwLocation == LOCATION_MACHINE) ||
           (dwLocation == LOCATION_BOTH));

    //
    // Opening the entire key is a two step process.  First, open
    // the class registration portion -- we need to do that from
    // either the machine or user location.  The second step
    // is to open everything after the class registration using the
    // key obtained in the first step.
    //

    //
    // Below we try to find a user or machine version of the
    // class registration
    //

    if ( LOCATION_USER & dwLocation ) {
        //
        // Try the user location -- first, move the key name to
        // the user hive's namespace
        //
        if( pKeyInfo->_fUser ) {
            //
            // in the user's hive we can try a relative open
            //
            InitializeObjectAttributes(
                &Obja,
                lpSubKey,
                OBJ_CASE_INSENSITIVE,
                hKey, // relative path
                NULL);
        } else {
            //
            // we need to do an absolute path open
            //
            Status = BaseRegTranslateToUserClassKey(
                pKeyInfo,
                &ClassRegkey,
                &PrefixLen);

            if (!NT_SUCCESS(Status)) {
                goto cleanup;
            }

            //
            // now try opening the key with the new HKCU string
            //
            InitializeObjectAttributes(
                &Obja,
                &ClassRegkey,
                OBJ_CASE_INSENSITIVE,
                NULL, // using absolute path, no hkey
                NULL);
        }

        Status = NtOpenKey(
            phkResult,
            samDesired,
            &Obja);
    }

    //
    // Only try machine if we failed to open user key above
    // (or didn't even try to open it)
    //
    if ((LOCATION_MACHINE & dwLocation) && !NT_SUCCESS(Status)) {

        //
        // Now try HKLM -- translate the key to the machine
        // namespace
        //
        if( pKeyInfo->_fMachine ) {
            //
            // in the machine hive we can try a relative open
            //
            InitializeObjectAttributes(
                &Obja,
                lpSubKey,
                OBJ_CASE_INSENSITIVE,
                hKey, // relative path
                NULL);
        } else {
            //
            // we need to do an absolute path open
            //
            Status = BaseRegTranslateToMachineClassKey(
                pKeyInfo,
                &ClassRegkey,
                &PrefixLen);

            if (!NT_SUCCESS(Status)) {
                goto cleanup;
            }

            //
            // now try opening the key with the new HKLM string
            //
            InitializeObjectAttributes(
                &Obja,
                &ClassRegkey,
                OBJ_CASE_INSENSITIVE,
                NULL, // using absolute path, no hkey
                NULL);
        }

        Status = NtOpenKey(
            phkResult,
            samDesired,
            &Obja);

        if (!NT_SUCCESS(Status)) {
            goto cleanup;
        }
    }

    //
    // mark this key as a class key from HKCR
    //
    if (NT_SUCCESS(Status)) {
        *phkResult = REG_CLASS_SET_SPECIAL_KEY(*phkResult);
    }

cleanup:

    RegClassHeapFree(FullPathBuf);

    return Status;
}



NTSTATUS BaseRegConstructUserClassPrefix(
    SKeySemantics*  pKeyInfo,
    PUNICODE_STRING pUserClassPrefix)
/*++
Routine Description:

    This function creates a prefix for a class key that is in the user hive

Arguments:

    pKeyInfo         - pointer to struct containing key semantic information
    pUserClassPrefix - out param for the constructed prefix

    Returns: NT_SUCCESS If the function completed successfully.

    Notes:

--*/
{
    UNICODE_STRING UserKey;
    NTSTATUS       Status;

    //
    // The prefix looks like <sid>_Classes
    //

    //
    // First obtain the sid
    //
    if (pKeyInfo->_fUser) {

        UNICODE_STRING SidString;

        //
        // construct a string that contains the user's sid
        //
        KeySemanticsGetSid(pKeyInfo, &SidString);

        RtlInitUnicodeString(&UserKey, REG_USER_HIVE_NAME);

        //
        // create a string that starts with the HKU prefix
        //
        RtlCopyUnicodeString(pUserClassPrefix, &UserKey);

        //
        // append the sid to the user prefix
        //
        Status = RtlAppendUnicodeStringToString(pUserClassPrefix, &SidString);

        if (!NT_SUCCESS(Status)) {
            return Status;
        }

    } else {

        UNICODE_STRING          UsersHive;

        //
        // This will only happen if a special key has been deleted from
        // the user hive
        //
        Status = RtlFormatCurrentUserKeyPath( &UsersHive );

        if (!NT_SUCCESS(Status)) {
            return Status;
        }

        RtlCopyUnicodeString(pUserClassPrefix, &UsersHive );

        RtlFreeUnicodeString(&UsersHive);
    }

    //
    // Append the suffix to the sid
    //
    return RtlAppendUnicodeToString(pUserClassPrefix, REG_USER_HIVE_CLASSES_SUFFIX);
}


NTSTATUS BaseRegTranslateToMachineClassKey(
    SKeySemantics*  pKeyInfo,
    PUNICODE_STRING pMachineClassKey,
    USHORT*         pPrefixLen)
/*++
Routine Description:

    This function translates a class key rooted in HKCR to the machine hive

Arguments:

    pKeyInfo - pointer to struct containing key semantic information -- the
    pMachineClassKey - out param for result of translation
    pPrefixLen - out param for length of the prefix of the resulting translation

    Returns: NT_SUCCESS If the function completed successfully.

    Notes:

--*/
{
    UNICODE_STRING MachineKey;
    UNICODE_STRING ClassSubkey;

    RtlInitUnicodeString(&MachineKey, REG_MACHINE_CLASSES_HIVE_NAME);

    //
    // get the unique class key portion
    //
    KeySemanticsRemovePrefix(pKeyInfo, &ClassSubkey, REMOVEPREFIX_KEEP_INITIAL_PATHSEP);

    //
    // create a string that starts with the HKLM prefix and has the
    // desired class registration key as a subkey
    //
    RtlCopyUnicodeString(pMachineClassKey, &MachineKey);

    *pPrefixLen = REG_MACHINE_CLASSES_HIVE_NAMELEN;

    return RtlAppendUnicodeStringToString(pMachineClassKey, &ClassSubkey);
}


NTSTATUS BaseRegTranslateToUserClassKey(
    SKeySemantics*  pKeyInfo,
    PUNICODE_STRING pUserClassKey,
    USHORT*         pPrefixLen)
/*++
Routine Description:

    This function translates a class key rooted in HKCR to the user hive

Arguments:

    pKeyInfo - pointer to struct containing key semantic information -- the
    pUserClassKey - out param for result of translation
    pPrefixLen - out param for length of the prefix of the resulting translation

    Returns: NT_SUCCESS If the function completed successfully.

    Notes:

--*/
{
    UNICODE_STRING ClassSubkey;
    NTSTATUS       Status;

    //
    // get the unique class key portion
    //
    KeySemanticsRemovePrefix(pKeyInfo, &ClassSubkey, REMOVEPREFIX_KEEP_INITIAL_PATHSEP);

    if (!NT_SUCCESS(Status = BaseRegConstructUserClassPrefix(
        pKeyInfo,
        pUserClassKey))) {
        return Status;
    }

    *pPrefixLen = pUserClassKey->Length;

    //
    // finally, append the class key
    //
    return RtlAppendUnicodeStringToString(pUserClassKey, &ClassSubkey);
}



USHORT BaseRegGetUserPrefixLength(PUNICODE_STRING pFullPath)
/*++
Routine Description:

    This function is used to determine the length of the prefix
    \\Registry\\User\\<Sid>\\Software\Classes or \\Registry\\User\\\<Sid>_classes

Arguments:

    pFullPath          - full path of the registry, rooted at \\Registry

Return Value:

    Returns the length of the prefix (which must be nonzero), 0 if unsuccessful

--*/
{
    UNICODE_STRING UserHive;
    UNICODE_STRING FullPath;
    USHORT         ich;
    USHORT         ichMax;

    FullPath = *pFullPath;

    //
    // set ourselves up to look for the user hive portion
    // of the prefix
    //
    RtlInitUnicodeString(&UserHive, REG_USER_HIVE_NAME);

    if (FullPath.Length <= UserHive.Length) {
        return 0;
    }

    FullPath.Length = UserHive.Length;

    //
    // check for the user hive prefix, leave if not found
    //
    if (!RtlEqualUnicodeString(&UserHive, &FullPath, TRUE)) {
        return 0;
    }

    ichMax = pFullPath->Length / REG_CHAR_SIZE;

    //
    // before looking for the classes subtree, we must skip past
    // the user's sid -- the prefix is in the form
    // \\Registry\\User\\<sid>\\Software\\Classes or
    // \\Registyr\\User\\<sid>_Classes
    //
    for (ich = REG_USER_HIVE_NAMECCH + 1; ich < ichMax; ich++)
    {
        //
        // if we find a pathsep, we cannot be in the combined
        // classes hive or the user classes hive
        //
        if (pFullPath->Buffer[ich] == L'\\') {
            return 0;
        }

        //
        // if we find the underscore character, we are in the combined
        // classes hive or the user classes hive -- i.e. the prefix looks like
        // \\Registry\\User\\<sid>_Classes
        // -- use the underscore to distinguish from other cases
        //
        if (pFullPath->Buffer[ich] == L'_') {

            UNICODE_STRING Suffix;

            RtlInitUnicodeString(&Suffix, REG_USER_HIVE_CLASSES_SUFFIX);

            FullPath.Length = Suffix.Length;
            FullPath.Buffer = &(pFullPath->Buffer[ich]);

            // look for the user classes suffix in the user hive
            if (RtlEqualUnicodeString(&FullPath, &Suffix, TRUE)) {

                return ich + REG_USER_HIVE_CLASSES_SUFFIXCCH;
            }

            return 0;
        }
    }

    return 0;
}



USHORT BaseRegCchSpecialKeyLen(
    PUNICODE_STRING pFullPath,
    USHORT          ichSpecialKeyStart,
    SKeySemantics*  pKeySemantics)
/*++
Routine Description:

    This function is used to determine the length of a special subkey contained
    on the pSpecialKey parameter.  If the entire pFullPath is a special key,
    a flag in pKeySemantics will be set to TRUE

Arguments:

    pFullPath          - full path of the registry, rooted at \\Registry
    ichSpecialKeyStart - index in the full path of the start of the special key path
    pKeySemantics      - pointer to structure which stores semantics information about a key

Return Value:

    Returns the length of the special key if there is a special key in the pSpecialKey
        path, 0 if there is none

    Notes:

    This function depends on the gSpecialSubtree array being a *sorted* list of special
        key names.

--*/
{
    WCHAR* wszSpecialKey;
    USHORT ichSpecialKeyLen;

    ASSERT(pFullPath->Length / REG_CHAR_SIZE >= ichSpecialKeyStart);

    //
    // For hkcr itself, there is no ancestor -- detect this special
    // case and return
    //
    if (pFullPath->Length / REG_CHAR_SIZE == ichSpecialKeyStart) {
        pKeySemantics->_fClassRegParent = TRUE;
        return ichSpecialKeyStart;
    }

    //
    // The special key is now just the parent of this key -- find
    // the immediate ancestor of this key
    //
    wszSpecialKey = wcsrchr(&(pFullPath->Buffer[ichSpecialKeyStart]), L'\\');

    ASSERT(wszSpecialKey);

    //
    // The length of the special key is the difference
    // between the '\' at the end of the special key and the start
    // of the string
    //
    ichSpecialKeyLen = (USHORT)(wszSpecialKey - pFullPath->Buffer);

    //
    // Store the length of the special key name by itself as well
    //
    pKeySemantics->_cbSpecialKey = ichSpecialKeyLen - ichSpecialKeyStart;

    return ichSpecialKeyLen;
}


NTSTATUS BaseRegOpenClassKeyRoot(
    SKeySemantics*  pKeyInfo,
    PHKEY           phkClassRoot,
    PUNICODE_STRING pClassKeyPath,
    BOOL            fMachine)
/*++
Routine Description:

    This function will try to open the class root key appropriate to
    a given key being opened from HKEY_CLASSES_ROOT. The key opened is either
    HKEY_USERS\<Sid>_Classes or HKLM\Software\Classes. If the key exists
    in the user portion, then that the user key will be opened.  Otherwise,
    the machine key is returned. It also returns the unicode string
    subkey name used to open the key specified in
    pKeyInfo relative to the class root key returned in phkClassRoot.

Arguments:

    pKeyInfo -- structure supplying information about a key

    phkClassRoot -- out param for the class root key result of the function

    pClassKeyPath -- Supplies the downward key path to the key to open.
        pClassKeyPath is always relative to the key specified by hKey.

    pfMachine -- out param flag that indicates that whether or not
        this key was opened in the machine hive.

Return Value:

    Returns STATUS_SUCCESS if a key was successfully deleted, otherwise it
        returns an NTSTATUS error code

    Note:

--*/
{
    NTSTATUS Status;
    USHORT   PrefixLen;
    UNICODE_STRING NewFullPath;

    //
    // Allocate space for a full path -- note that
    // we don't check the return value since alloca throws
    // an exception if it fails
    //
    NewFullPath.Buffer = alloca(pClassKeyPath->MaximumLength);
    NewFullPath.MaximumLength = pClassKeyPath->MaximumLength;

    //
    // Translate to appropriate location
    //
    if (fMachine) {

        Status = BaseRegTranslateToMachineClassKey(
            pKeyInfo,
            &NewFullPath,
            &PrefixLen);
    } else {

        Status = BaseRegTranslateToUserClassKey(
            pKeyInfo,
            &NewFullPath,
            &PrefixLen);
    }

    if (!NT_SUCCESS(Status)) {
        return Status;
    }

    //
    // Open the prefix
    //
    {
        UNICODE_STRING RootKey;
        OBJECT_ATTRIBUTES Obja;

        RootKey.Buffer = NewFullPath.Buffer;

        //
        // Calculate the length of the prefix
        //
        RootKey.Length = PrefixLen;

        //
        // now, get ready to open it
        //
        InitializeObjectAttributes(&Obja,
                                   &RootKey,
                                   OBJ_CASE_INSENSITIVE,
                                   NULL, // full path, no hkey
                                   NULL);

        Status =  NtOpenKey(
            phkClassRoot,
            MAXIMUM_ALLOWED,
            &Obja);
    }

    if (NT_SUCCESS(Status)) {

        //
        // Skip past the prefix
        //
        NewFullPath.Buffer += (PrefixLen / REG_CHAR_SIZE);
        NewFullPath.Length -= PrefixLen;

        if (L'\\' == NewFullPath.Buffer[0]) {
            NewFullPath.Length -= REG_CHAR_SIZE;
            NewFullPath.Buffer ++;
        }

        //
        // Copy everything after the prefix
        //
        RtlCopyUnicodeString(pClassKeyPath, &NewFullPath);
    }

    return Status;
}


NTSTATUS
BaseRegMapClassRegistrationKey(
    HKEY              hKey,
    PUNICODE_STRING   pSubKey,
    SKeySemantics*    pKeyInfo,
    PUNICODE_STRING   pDestSubKey,
    BOOL*             pfRetryOnAccessDenied,
    PHKEY             phkDestResult,
    PUNICODE_STRING*  ppSubKeyResult)
/*++
Routine Description:

    This function will map a key from HKEY_CLASSES_ROOT into either the
    user hive or machine hive.  The remapped key is returned in the
    (*phkDestResult, *ppSubKeyResult) pair.

Arguments:

    hKey    -- root of key to remap

    pSubKey -- Supplies the downward key path to the key to remap.
        pSubKey is always relative to the key specified by hKey.

    pKeyInfo -- structure supplying information about a key

    pDestSubKey -- unicode string in which to store result data
        if the key gets remapped

    pfRetryOnAccessDenied -- out param flag to set indicating whether
        failure to open the remapped key because of access denied should
        be retried

    phkDestResult -- out param for root of remapped key

    ppSubKeyResult -- out param for remainder of path of remapped key

Return Value:

    Returns STATUS_SUCCESS if a key was successfully deleted, otherwise it
        returns an NTSTATUS error code

    Note:

--*/
{
    BOOL           fMachine;
    UNICODE_STRING ClassKeyPath;
    NTSTATUS       Status;
    HKEY           hkUser;

    //
    // by default, use machine
    //
    fMachine = TRUE;


    //
    // Check for existence of the key in the
    // user hive
    //
    Status =  BaseRegOpenClassKeyFromLocation(
        pKeyInfo,
        hKey,
        pSubKey,
        MAXIMUM_ALLOWED,
        LOCATION_USER,
        &hkUser);

    if (!NT_SUCCESS(Status)) {

        //
        // a not found error is fine -- this just means that
        // neither key exists already -- in this case we
        // choose to use machine
        //
        if (STATUS_OBJECT_NAME_NOT_FOUND != Status) {
            return Status;
        }

    } else {

        //
        // The user key exists, we choose it over
        // the machine key
        //
        fMachine = FALSE;

        NtClose(hkUser);
    }

    //
    // Get a buffer for the new path
    //
    ClassKeyPath.Buffer = (WCHAR*) RegClassHeapAlloc(
        ClassKeyPath.MaximumLength = ((USHORT) pKeyInfo->_pFullPath->NameLength +
        REG_CLASSES_SUBTREE_PADDING));

    if (!(ClassKeyPath.Buffer)) {
        return STATUS_NO_MEMORY;
    }

    //
    // Remap the key
    //
    Status = BaseRegOpenClassKeyRoot(
        pKeyInfo,
        phkDestResult,
        &ClassKeyPath,
        fMachine);

    if (!NT_SUCCESS(Status)) {

        RegClassHeapFree(ClassKeyPath.Buffer);

        return Status;
    }

    //
    // If the remapped key is in the machine hive, set the flag so that
    // retries are not permitted.
    //
    if (*pfRetryOnAccessDenied && !fMachine) {
        *pfRetryOnAccessDenied = FALSE;
    }

    //
    // phkDestResult, the root portion of the remapped key, was set above.
    // now set the subkey portion and leave
    //
    *pDestSubKey = ClassKeyPath;
    *ppSubKeyResult = pDestSubKey;

    return STATUS_SUCCESS;
}


NTSTATUS BaseRegGetUserAndMachineClass(
    SKeySemantics* pKeySemantics,
    HKEY           hKey,
    REGSAM         samDesired,
    PHKEY          phkMachine,
    PHKEY          phkUser)
/*++
Routine Description:

    This function will return kernel objects corresponding to the user
    and machine components of a given kernel object.

Arguments:

    pKeySemantics -- supplies information about hKey.  This is optional --
        if the caller does not supply it, the function will query for the information.
        This is an optimization for callers that already have this info
        and can save us the time of

    hKey -- key for which to open user and machine versions

    samDesired -- security access mask for one of the returned keys -- see
        note below for important info on this

    phkMachine -- out param for machine version of key

    phkUser -- out param for user version of key

Return Value:

    Returns STATUS_SUCCESS if a key was successfully deleted, otherwise it
        returns an NTSTATUS error code

Notes:

***VERY IMPORTANT!!!***

    One of the two returned keys will alias hKey -- this way we only open
    one object (one trip to the kernel) instead of two.  This means that the caller
    should not blindly call NtClose on the two returned objects -- a == comparison
    between one of the keys and hKey should be made to determine if it that key is
    the alias -- if it is, you should *NOT* call NtClose on it because otherwise the
    owner of hKey will call NtClose on the same handle value after your call to close
    that handle which will cause an exception.  You *should* close the handle that does not
    alias hKey -- if you don't you'll get a handle leak.

    Another important note -- only the new key (non-aliased) will have the access mask
    specified in samDesired -- the aliased key is just hKey, so it has the same access
    mask.  If you want to ensure the correct access on that key, you'll need to explicitly
    duplicate or open that key with the correct access.

--*/
{
    NTSTATUS       Status;
    SKeySemantics  keyinfo;
    SKeySemantics* pKeyInfo;
    UNICODE_STRING EmptyString = {0, 0, 0};
    BYTE           rgNameBuf[REG_MAX_CLASSKEY_LEN + REG_CHAR_SIZE + sizeof(KEY_NAME_INFORMATION)];
    DWORD          dwLocation;
    PHKEY          phkNew;

    //
    // Clear out parameters
    //
    *phkMachine = NULL;
    *phkUser = NULL;

    //
    // Try to use caller supplied key information
    //
    if (pKeySemantics) {
        pKeyInfo = pKeySemantics;
    } else {

        //
        // Set buffer to store info about this key
        //
        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)) {
            return Status;
        }

        pKeyInfo = &keyinfo;
    }

    if (pKeyInfo->_fMachine) {

        *phkMachine = hKey;
        dwLocation = LOCATION_USER;
        phkNew = phkUser;

    } else {

        *phkUser = hKey;
        dwLocation = LOCATION_MACHINE;
        phkNew = phkMachine;

    }

    (void) BaseRegOpenClassKeyFromLocation(
        pKeyInfo,
        hKey,
        &EmptyString,
        (samDesired & KEY_WOW64_RES) | MAXIMUM_ALLOWED,
        dwLocation,
        phkNew);

    if (!pKeySemantics) {
        BaseRegReleaseKeySemantics(&keyinfo);
    }

    return STATUS_SUCCESS;
}


NTSTATUS GetFixedKeyInfo(
    HKEY     hkUser,
    HKEY     hkMachine,
    LPDWORD  pdwUserValues,
    LPDWORD  pdwMachineValues,
    LPDWORD  pdwUserMaxDataLen,
    LPDWORD  pdwMachineMaxDataLen,
    LPDWORD  pdwMaxValueNameLen)
{

    NTSTATUS                Status;
    DWORD                   cUserValues;
    DWORD                   cMachineValues;
    KEY_CACHED_INFORMATION  KeyInfo;
    DWORD                   dwRead;
    DWORD                   cbMaxNameLen;
    DWORD                   cbUserMaxDataLen;
    DWORD                   cbMachineMaxDataLen;

    //
    // Init locals
    //
    cUserValues = 0;
    cMachineValues = 0;
    cbMaxNameLen = 0;
    cbUserMaxDataLen = 0;
    cbMachineMaxDataLen = 0;

    Status = STATUS_OBJECT_NAME_NOT_FOUND;

    //
    // Init out params
    //
    if (pdwUserValues) {
        *pdwUserValues = 0;
    }

    if (pdwMachineValues) {
        *pdwMachineValues = 0;
    }

    if (pdwMaxValueNameLen) {
        *pdwMaxValueNameLen = 0;
    }

    if (pdwUserMaxDataLen) {
        *pdwUserMaxDataLen = 0;
    }

    if (pdwMachineMaxDataLen) {
        *pdwMachineMaxDataLen = 0;
    }

    //
    // Get user information
    //
    if (hkUser) {

        Status = NtQueryKey(
            hkUser,
            KeyCachedInformation,
            &KeyInfo,
            sizeof(KeyInfo),
            &dwRead);

        //
        // KEY_CACHED_INFORMATION is a fixed struct !!!
        //
        ASSERT( Status != STATUS_BUFFER_OVERFLOW);
        //if (STATUS_BUFFER_OVERFLOW == Status) {
        //  Status = STATUS_SUCCESS;
        //}

        if (!NT_SUCCESS(Status)) {
            return Status;
        }

        cUserValues = KeyInfo.Values;
        cbMaxNameLen = KeyInfo.MaxValueNameLen;
        cbUserMaxDataLen = KeyInfo.MaxValueDataLen;
    }

    //
    // Get machine information
    //
    if (hkMachine) {

        Status = NtQueryKey(
            hkMachine,
            KeyCachedInformation,
            &KeyInfo,
            sizeof(KeyInfo),
            &dwRead);

        //
        // KEY_CACHED_INFORMATION is a fixed struct !!!
        //
        ASSERT( Status != STATUS_BUFFER_OVERFLOW);
        //if (STATUS_BUFFER_OVERFLOW == Status) {
        //  Status = STATUS_SUCCESS;
        //}

        if (!NT_SUCCESS(Status)) {
            return Status;
        }

        cMachineValues = KeyInfo.Values;
        cbMachineMaxDataLen = KeyInfo.MaxValueDataLen;

        if (KeyInfo.MaxValueNameLen > cbMaxNameLen) {
            cbMaxNameLen = KeyInfo.MaxValueNameLen;
        }
    }

    if (pdwUserValues) {
        *pdwUserValues = cUserValues;
    }

    if (pdwMachineValues) {
        *pdwMachineValues = cMachineValues;
    }

    if (pdwMaxValueNameLen) {
        *pdwMaxValueNameLen = cbMaxNameLen;
    }

    if (pdwUserMaxDataLen) {
        *pdwUserMaxDataLen = cbUserMaxDataLen;
    }

    if (pdwMachineMaxDataLen) {
        *pdwMachineMaxDataLen = cbMachineMaxDataLen;
    }

    return Status;
}


//#ifdef CLASSES_RETRY_ON_ACCESS_DENIED


NTSTATUS
BaseRegMapClassOnAccessDenied(
    SKeySemantics*    pKeySemantics,
    PHKEY             phkDest,
    PUNICODE_STRING   pDestSubKey,
    BOOL*             pfRetryOnAccessDenied)
/*++
Routine Description:

    This function will remap a key to the user hive when an access denied
    error is encountered creating it in the machine hive

Arguments:

    pKeySemantics -- structure supplying information about a key

    phkDest -- out param for root of remapped key

    pDestSubKey -- out param for remainder of path of remapped key

    pfRetryOnAccessDenied -- in / out param.  If true, we can
      remap it.  On return this value indicates whether or not
      we can do another retry

Return Value:

    Returns STATUS_SUCCESS if a key was successfully deleted, otherwise it
        returns an NTSTATUS error code

    Note:

--*/
{
    NTSTATUS Status;
    UNICODE_STRING NewFullPath;

    Status = STATUS_ACCESS_DENIED;
    NewFullPath.Buffer=NULL;

    if (pKeySemantics->_fCombinedClasses &&
        *pfRetryOnAccessDenied) {

        USHORT         PrefixLen;

        //
        // Close the original key -- we don't need it anymore.
        //
        if(*phkDest) {
            NtClose(*phkDest);
            *phkDest = NULL;
        }


        //
        // No more retries permitted for this key
        //
        *pfRetryOnAccessDenied = FALSE;

        //
        // Get space for the new path -- we will free this below.
        // We avoid using alloca because of stack overflows
        //
        NewFullPath.MaximumLength = (USHORT)(pKeySemantics->_pFullPath->NameLength) + REG_CLASSES_SUBTREE_PADDING;

        NewFullPath.Buffer = RegClassHeapAlloc(NewFullPath.MaximumLength);

        if (!(NewFullPath.Buffer)) {
            return STATUS_NO_MEMORY;
        }

        //
        // Translate this key to the user hive
        //
        Status = BaseRegTranslateToUserClassKey(
            pKeySemantics,
            &NewFullPath,
            &PrefixLen);

        if (NT_SUCCESS(Status)) {

            UNICODE_STRING    Prefix;
            OBJECT_ATTRIBUTES Obja;

            //
            // Allocate space for the new key name to give back to the caller
            //
            pDestSubKey->MaximumLength = NewFullPath.MaximumLength - PrefixLen + 1;
            pDestSubKey->Buffer = (WCHAR*) RegClassHeapAlloc(pDestSubKey->MaximumLength);

            if (!(pDestSubKey->Buffer)) {
                Status = STATUS_NO_MEMORY;
                goto cleanup;
            }

            //
            // Make a string which strips off every thing after the prefix --
            // we will open up to the prefix
            //
            Prefix.Buffer = NewFullPath.Buffer;
            Prefix.Length = PrefixLen;

            //
            // Move our full path past the prefix
            //
            NewFullPath.Buffer += (PrefixLen + REG_CHAR_SIZE) / REG_CHAR_SIZE;
            NewFullPath.Length -= (PrefixLen + REG_CHAR_SIZE);
            //
            // Copy everything after the prefix to the subkey path
            // that we're returning to the caller
            //
            RtlCopyUnicodeString(pDestSubKey, &NewFullPath);

            //
            //Get the original pointer back so we could free it!
            //
            NewFullPath.Buffer -= (PrefixLen + REG_CHAR_SIZE) / REG_CHAR_SIZE;
            NewFullPath.Length += (PrefixLen + REG_CHAR_SIZE);
            //
            // Now open the root for the caller
            //
            InitializeObjectAttributes(&Obja,
                                       &Prefix,
                                       OBJ_CASE_INSENSITIVE,
                                       NULL, // full path, no hkey
                                       NULL);

            Status = NtOpenKey(
                phkDest,
                MAXIMUM_ALLOWED,
                &Obja);

            if(!NT_SUCCESS(Status)) {
                RegClassHeapFree(pDestSubKey->Buffer);
                pDestSubKey->Buffer=NULL;
            }
        }
    }

cleanup:

    //
    // Free the buffer we allocated above
    //
    if(NewFullPath.Buffer) {
        RegClassHeapFree(NewFullPath.Buffer);
    }

    return Status;
}

//#endif // CLASSES_RETRY_ON_ACCESS_DENIED

NTSTATUS
CreateMultipartUserClassKey(
    IN HKEY hKey,
    OUT PHKEY phkResult)
/*++

Routine Description:

    This function creates HKCU\Software\Classes\... subkey
    corresponding to given HKLM\Software\Classes\... subkey

Arguments:

    IN HKEY hKey - handle of HKLM\Software\Classes\... subkey
    OUT PHKEY phkResult - handle of HKCU\Software\Classes\... subkey

Return Value:

    Returns STATUS_SUCCESS on success, other NTSTATUS if failed.

--*/
{
    LPWSTR            KeyBuffer;
    ULONG             NumberOfSubKeys;
    LPWSTR            p;
    ULONG             i;
    LPWSTR            Token;
    UNICODE_STRING    KeyName;
    HANDLE            TempHandle1;
    HANDLE            TempHandle2;
    OBJECT_ATTRIBUTES Obja;
    NTSTATUS          Status;

    ////////////////////////////////////////////////////////////////////////////
    BYTE                rgNameInfoBuf[REG_MAX_CLASSKEY_LEN + REG_CHAR_SIZE];
    SKeySemantics       keyinfo;
    UNICODE_STRING      EmptyString= {0, 0, 0};
    HKEY                hkDestKey=NULL;
    UNICODE_STRING      DestSubkey;
    BOOL                fRetryOnAccessDenied=TRUE;
    //
    // Set up the buffer that will hold the name of the key
    //
    keyinfo._pFullPath = (PKEY_NAME_INFORMATION) rgNameInfoBuf;
    keyinfo._cbFullPath = sizeof(rgNameInfoBuf);
    keyinfo._fAllocedNameBuf = FALSE;

    //
    // get information about this key
    //
    Status = BaseRegGetKeySemantics(hKey, &EmptyString, &keyinfo);

    if (!NT_SUCCESS(Status)) {
        return Status;
    }

    DestSubkey.Buffer=NULL;

    //
    //Remap key to the user hive.
    //
    Status = BaseRegMapClassOnAccessDenied(
                &keyinfo,
                &hkDestKey,
                &DestSubkey,
                &fRetryOnAccessDenied);

    BaseRegReleaseKeySemantics(&keyinfo);

    if(!NT_SUCCESS(Status)) {
        return Status;
    }

    //
    //If we've opened new hkDestKey we must close it.
    //
    if( hkDestKey != hKey ) {
        TempHandle1 = hkDestKey;
    } else {
        TempHandle1 = NULL;
    }

    //
    // Initialize the buffer to be tokenized.
    //

    KeyBuffer = DestSubkey.Buffer;

    //
    //  Find out the number of subkeys to be created
    //
    NumberOfSubKeys = 1;
    p = KeyBuffer;
    while ( ( p = wcschr( p, ( WCHAR )'\\' ) ) != NULL ) {
        p++;
        NumberOfSubKeys++;
    }

    for( i = 0, Token = KeyBuffer; i < NumberOfSubKeys; i++ ) {

        ASSERT(Token != NULL);

        if( ( *Token == ( WCHAR )'\\' ) &&
            ( i != NumberOfSubKeys - 1 ) ) {
            //
            //  If the first character of the key name is '\', and the key
            //  is not the last to be created, then ignore this key name.
            //  This condition can happen if the key name contains
            //  consecutive '\'.
            //  This behavior is consistent with the one we had in the past
            //  when the API used wcstok() to get the key names.
            //  Note that if the key name is an empty string, we return a handle
            //  that is different than hKey, even though both point to the same
            //  key. This is by design.
            //
            Token++;
            continue;
        }

        //
        // Convert the token to a counted Unicode string.
        //
        KeyName.Buffer = Token;
        if (i == NumberOfSubKeys - 1) {
            KeyName.Length = wcslen(Token)*sizeof(WCHAR);
        } else {
            KeyName.Length = (USHORT)(wcschr(Token, ( WCHAR )'\\') - Token)*sizeof(WCHAR);
        }

        //
        // Remember the intermediate handle (NULL the first time through).
        //

        TempHandle2 = TempHandle1;

        //
        // Initialize the OBJECT_ATTRIBUTES structure, close the
        // intermediate key and create or open the key.
        //

        InitializeObjectAttributes(
                &Obja,
                &KeyName,
                OBJ_CASE_INSENSITIVE,
                hkDestKey,
                NULL
                );

        Status = NtCreateKey(
                &TempHandle1,
                MAXIMUM_ALLOWED,
                &Obja,
                0,
                NULL,
                REG_OPTION_NON_VOLATILE,
                NULL
                );

        //
        // Initialize the next object directory (i.e. parent key) handle.
        //

        hkDestKey = TempHandle1;

        //
        // Close the intermediate key.
        // This fails the first time through the loop since the
        // handle is NULL.
        //

        if( TempHandle2 != NULL ) {
            NtClose( TempHandle2 );
        }

        //
        // If creating the key failed, return the error.
        //

        if( ! NT_SUCCESS( Status )) {
            break;
        }

        Token = wcschr( Token, ( WCHAR )'\\') + 1;

    }

    if(DestSubkey.Buffer) {
        RtlFreeHeap(RtlProcessHeap(), 0, DestSubkey.Buffer);
    }

    //
    // Only set the return value once we know we've
    // succeeded.
    //
    if( NT_SUCCESS( Status )) {
        *phkResult = hkDestKey;
    }

    return Status;
}


#if defined(_REGCLASS_MALLOC_INSTRUMENTED_)

BOOL InitializeInstrumentedRegClassHeap()
{
    NTSTATUS Status;

    Status = RtlInitializeCriticalSection(
                    &(gRegClassHeapCritSect));

    return NT_SUCCESS(Status);
}

BOOL CleanupInstrumentedRegClassHeap()
{
    NTSTATUS Status;

    Status = RtlDeleteCriticalSection(
                    &(gRegClassHeapCritSect));

    DbgPrint("WINREG: Instrumented memory data for process id 0x%x\n", NtCurrentTeb()->ClientId.UniqueProcess);
    DbgPrint("WINREG: Classes Heap Maximum Allocated: 0x%x\n", gcbMaxAllocated);
    DbgPrint("WINREG: Classes Heap Maximum Outstanding Allocs: 0x%x\n", gcMaxAllocs);

    if (gcbAllocated || gcAllocs) {

        DbgPrint("WINREG: Classes Heap ERROR!\n");
        DbgPrint("WINREG: Classes Heap not completely freed!\n");
        DbgPrint("WINREG: Classes Heap Leaked 0x%x bytes\n", gcbAllocated);
        DbgPrint("WINREG: Classes Heap Outstanding Allocs: 0x%x\n", gcAllocs);

        DbgBreakPoint();
    } else {
        DbgPrint("WINREG: Classes Heap is OK.\n");
    }

    return NT_SUCCESS(Status);
}


#endif // defined(_REGCLASS_MALLOC_INSTRUMENTED_)

/*++

Routine Description:

    Replaces HKLM\Software\Classes\<something> path with HKCR\<something>

Arguments:

    phKey        - The pointer to the handle of the key to map
    lpSubKey    - The key that is to be opened/created/deleted


Return Value:

    If the [phKey] is in an interesting area return TRUE

    If not, or if an error occurs, return FALSE.

--*/

BOOL
ExtractClassKey(
        IN OUT HKEY *phKey,
        IN OUT PUNICODE_STRING lpSubKey)
{

#define STR_CLASSES1 L"Classes\\"
#define STR_CLASSES2 L"Software\\Classes\\"
#define STR_MACHINE1 L"\\Registry\\Machine\\Software"
#define STR_MACHINE2 L"\\Registry\\Machine"

    static const WCHAR *szCmpStr1[2]={STR_CLASSES1,STR_CLASSES2};
    static const WCHAR *szCmpStr2[2]={STR_MACHINE1,STR_MACHINE2};
    static const USHORT SizeCmpStr1[2]={sizeof(STR_CLASSES1)/sizeof(WCHAR)-2,
                                        sizeof(STR_CLASSES2)/sizeof(WCHAR)-2};
    static const USHORT SizeCmpStr2[2]={sizeof(STR_MACHINE1)/sizeof(WCHAR)-1,
                                        sizeof(STR_MACHINE2)/sizeof(WCHAR)-1};
    static const USHORT LengthCmpStr1[2]={sizeof(STR_CLASSES1)-2*sizeof(WCHAR),
                                          sizeof(STR_CLASSES2)-2*sizeof(WCHAR)};
    static const USHORT LengthCmpStr2[2]={sizeof(STR_MACHINE1)-sizeof(WCHAR),
                                          sizeof(STR_MACHINE2)-sizeof(WCHAR)};

    static const USHORT offset[2]={sizeof(STR_CLASSES1)-sizeof(WCHAR),
                                   sizeof(STR_CLASSES2)-sizeof(WCHAR)};

    static BYTE pBuff[sizeof(STR_MACHINE1)+sizeof(KEY_NAME_INFORMATION)];

    PKEY_NAME_INFORMATION    pNameInfo = (PKEY_NAME_INFORMATION)pBuff;
    ULONG ResultLength;
    NTSTATUS Status;

    int index = 0;



    //if length of the subkey name is less than "Classes" this is
    //not the key we are interested in.
    if(lpSubKey->Length < LengthCmpStr1[index]) {

        return FALSE;
    }

    //see if subkey name starts with "Classes"
    //If length of the subkey name is longer than "Classes",
    //see if we have '\\' in the right place
    //(because it could be something like "ClassesSomething").
    if(_wcsnicmp(lpSubKey->Buffer,szCmpStr1[index],SizeCmpStr1[index]) ||
        (lpSubKey->Length > LengthCmpStr1[index] &&
        lpSubKey->Buffer[SizeCmpStr1[index]] != L'\\')) {

        index = 1;

        //if length of the subkey name is less than "Software\\Classes" this is
        //not the key we are interested in.
        if(lpSubKey->Length < LengthCmpStr1[index]) {

            return FALSE;
        }

        //see if subkey name starts with "Software\\Classes"
        //If length of the subkey name is longer than "Software\\Classes",
        //see if we have '\\' in the right place
        //(because it could be something like "Software\\ClassesSomething").
        if(_wcsnicmp(lpSubKey->Buffer,szCmpStr1[index],SizeCmpStr1[index]) ||
            (lpSubKey->Length > LengthCmpStr1[index] &&
            lpSubKey->Buffer[SizeCmpStr1[index]] != L'\\')) {

            return FALSE;
        }
    }

    //Get the parent key name
    Status = NtQueryKey(
                *phKey,
                KeyNameInformation,
                pNameInfo,
                sizeof(pBuff),
                &ResultLength);

    if (!NT_SUCCESS(Status)) {

        return FALSE;
    }

    if(pNameInfo->NameLength != LengthCmpStr2[index] ||
        _wcsnicmp(pNameInfo->Name,szCmpStr2[index],SizeCmpStr2[index])) {

        return FALSE;
    }

    //Get handle to HKCR
    if( !HKEY_ClassesRoot ) {

        if( LocalOpenClassesRoot(NULL, MAXIMUM_ALLOWED, &HKEY_ClassesRoot) != ERROR_SUCCESS ) {

            return FALSE;
        }
    }


    // cut "Classes\\" or "Software\\Classes\\" from lpSubKey
    if(lpSubKey->Length <= offset[index] ) {
        //user tries to access HKLM\Software\Classes[\]"
        //we will just return him handle to HKCR.
        lpSubKey->Length = 0;

    } else {
        //move pointer after "Classes\\" or "Software\\Classes\\"
        //it will be restored by caller
        lpSubKey->Length -= offset[index];
        lpSubKey->MaximumLength -= offset[index];
        lpSubKey->Buffer += offset[index]/sizeof(WCHAR);
    }

    //replace hKey with HKCR
    *phKey = HKEY_ClassesRoot;

    return TRUE;
}

#endif // LOCAL











