/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    dmreg.c

Abstract:

    Contains the registry access routines for the Config Database Manager

Author:

    John Vert (jvert) 24-Apr-1996

Revision History:

--*/
#include "dmp.h"

#include <align.h>

#if NO_SHARED_LOCKS
extern CRITICAL_SECTION gLockDmpRoot;
#else
extern RTL_RESOURCE     gLockDmpRoot;
#endif

HDMKEY
DmGetRootKey(
    IN DWORD samDesired
    )

/*++

Routine Description:

    Opens the registry key at the root of the cluster registry database

Arguments:

    samDesired - Supplies requested security access

Return Value:

    A handle to the opened registry key.

    NULL on error. LastError will be set to the specific error code.

--*/

{
    DWORD Error;
    PDMKEY Key;

    Key = LocalAlloc(LMEM_FIXED, sizeof(DMKEY)+sizeof(WCHAR));
    if (Key == NULL) {
        CL_UNEXPECTED_ERROR(ERROR_NOT_ENOUGH_MEMORY);
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return(NULL);
    }

    //
    //  Acquire DM root lock to synchronize with DmRollbackRegistry.
    //
    ACQUIRE_SHARED_LOCK(gLockDmpRoot);

    Error = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                          DmpClusterParametersKeyName,
                          0,
                          samDesired,
                          &Key->hKey);
    if (Error != ERROR_SUCCESS) {
        LocalFree(Key);
        SetLastError(Error);
        Key = NULL;
        goto FnExit;
    }
    Key->Name[0] = '\0';
    Key->GrantedAccess = samDesired;
    EnterCriticalSection(&KeyLock);
    InsertHeadList(&KeyList, &Key->ListEntry);
    InitializeListHead(&Key->NotifyList);
    LeaveCriticalSection(&KeyLock);

FnExit:
    RELEASE_LOCK(gLockDmpRoot);

    return((HDMKEY)Key);

}


DWORD
DmCloseKey(
    IN HDMKEY hKey
    )

/*++

Routine Description:

    Closes a handle to an open HDMKEY key.

Arguments:

    hKey - Supplies the handle to be closed.

Return Value:

    ERROR_SUCCESS if successful

    Win32 error code otherwise.

--*/

{
    DWORD Error = ERROR_SUCCESS;
    PDMKEY Key;

    //
    // Nobody better EVER close one of the global keys.
    //
    CL_ASSERT(hKey != DmClusterParametersKey);
    CL_ASSERT(hKey != DmResourcesKey);
    CL_ASSERT(hKey != DmResourceTypesKey);
    CL_ASSERT(hKey != DmQuorumKey);
    CL_ASSERT(hKey != DmGroupsKey);
    CL_ASSERT(hKey != DmNodesKey);
    CL_ASSERT(hKey != DmNetworksKey);
    CL_ASSERT(hKey != DmNetInterfacesKey);

    Key = (PDMKEY)hKey;

    ACQUIRE_SHARED_LOCK(gLockDmpRoot);

    //if the key was deleted and invalidated and couldnt be reopened
    // it will be set to NULL, in this case we dont call regclosekey

    if( Key == NULL ) goto FnExit;
        
    if (ISKEYDELETED(Key))
        goto CleanupKey;

    Error = RegCloseKey(Key->hKey);
    if (Error != ERROR_SUCCESS) 
    {
        CL_LOGFAILURE(Error);
        goto FnExit;
    }

CleanupKey:
    EnterCriticalSection(&KeyLock);
    RemoveEntryList(&Key->ListEntry);
    DmpRundownNotify(Key);
    LeaveCriticalSection(&KeyLock);
    LocalFree(Key);
    
FnExit:    
    RELEASE_LOCK(gLockDmpRoot);
    return(Error);
}


HDMKEY
DmCreateKey(
    IN HDMKEY hKey,
    IN LPCWSTR lpSubKey,
    IN DWORD dwOptions,
    IN DWORD samDesired,
    IN OPTIONAL LPVOID lpSecurityDescriptor,
    OUT LPDWORD lpDisposition
    )

/*++

Routine Description:

    Creates a key in the cluster registry. If the key exists, it
    is opened. If it does not exist, it is created on all nodes in
    the cluster.

Arguments:

    hKey - Supplies the key that the create is relative to.

    lpSubKey - Supplies the key name relative to hKey

    dwOptions - Supplies any registry option flags. 

    samDesired - Supplies desired security access mask

    lpSecurityDescriptor - Supplies security for the newly created key.

    Disposition - Returns whether the key was opened (REG_OPENED_EXISTING_KEY)
        or created (REG_CREATED_NEW_KEY)

Return Value:

    A handle to the specified key if successful

    NULL otherwise. LastError will be set to the specific error code.

--*/

{
    PDMKEY Parent;
    PDMKEY Key=NULL;
    DWORD NameLength;
    DWORD Status = ERROR_SUCCESS;
    HDMKEY NewKey;
    PDM_CREATE_KEY_UPDATE CreateUpdate = NULL;
    DWORD SecurityLength;

    // if this is a request to create a volatile key, refuse it
    // we dont support volatile keys in the cluster hive since
    // we cant roll back the cluster hive then.
    if (dwOptions == REG_OPTION_VOLATILE)
    {
        Status = ERROR_INVALID_PARAMETER;
        goto FnExit;
    }
    
    //
    // Issue a global update to create the key.
    //

    Parent = (PDMKEY)hKey;

    //
    // Allocate the DMKEY structure.
    //
    NameLength = (lstrlenW(Parent->Name) + 1 + lstrlenW(lpSubKey) + 1)*sizeof(WCHAR);
    Key = LocalAlloc(LMEM_FIXED, sizeof(DMKEY)+NameLength);
    if (Key == NULL) {
        CL_UNEXPECTED_ERROR(ERROR_NOT_ENOUGH_MEMORY);
        Status = ERROR_NOT_ENOUGH_MEMORY;
        goto FnExit;
    }

    //
    // Create the key name
    //
    lstrcpyW(Key->Name, Parent->Name);
    if (Key->Name[0] != UNICODE_NULL) {
        lstrcatW(Key->Name, L"\\");
    }
    lstrcatW(Key->Name, lpSubKey);
    Key->GrantedAccess = samDesired;

    //get the length of the security structure
    if (ARGUMENT_PRESENT(lpSecurityDescriptor)) {
        SecurityLength = GetSecurityDescriptorLength(lpSecurityDescriptor);
    } else {
        SecurityLength = 0;
    }


    CreateUpdate = (PDM_CREATE_KEY_UPDATE)LocalAlloc(LMEM_FIXED, sizeof(DM_CREATE_KEY_UPDATE));
    if (CreateUpdate == NULL) {
        CL_UNEXPECTED_ERROR(ERROR_NOT_ENOUGH_MEMORY);
        Status = ERROR_NOT_ENOUGH_MEMORY;
        goto FnExit;
    }

    //
    // Issue the update.
    //
    CreateUpdate->lpDisposition = lpDisposition;
    CreateUpdate->phKey = &Key->hKey;
    CreateUpdate->samDesired = samDesired;
    CreateUpdate->dwOptions = dwOptions;

    if (ARGUMENT_PRESENT(lpSecurityDescriptor)) {
        CreateUpdate->SecurityPresent = TRUE;
    } else {
        CreateUpdate->SecurityPresent = FALSE;
    }

    Status = GumSendUpdateEx(GumUpdateRegistry,
                             DmUpdateCreateKey,
                             3,
                             sizeof(DM_CREATE_KEY_UPDATE),
                             CreateUpdate,
                             (lstrlenW(Key->Name)+1)*sizeof(WCHAR),
                             Key->Name,
                             SecurityLength,
                             lpSecurityDescriptor);

    if (Status != ERROR_SUCCESS)
    {
        goto FnExit;
    }

    EnterCriticalSection(&KeyLock);
    InsertHeadList(&KeyList, &Key->ListEntry);
    InitializeListHead(&Key->NotifyList);
    LeaveCriticalSection(&KeyLock);

FnExit:
    if (CreateUpdate) LocalFree(CreateUpdate);

    if (Status != ERROR_SUCCESS)
    {
        if (Key) LocalFree(Key);
        SetLastError(Status);
        return(NULL);
    }
    else
    {
        return ((HDMKEY)Key);
    }
}


HDMKEY
DmOpenKey(
    IN HDMKEY hKey,
    IN LPCWSTR lpSubKey,
    IN DWORD samDesired
    )

/*++

Routine Description:

    Opens a key in the cluster registry. If the key exists, it
    is opened. If it does not exist, the call fails.

Arguments:

    hKey - Supplies the key that the open is relative to.

    lpSubKey - Supplies the key name relative to hKey

    samDesired - Supplies desired security access mask

Return Value:

    A handle to the specified key if successful

    NULL otherwise. LastError will be set to the specific error code.

--*/

{
    PDMKEY  Parent;
    PDMKEY  Key=NULL;
    DWORD   NameLength;
    DWORD   Status = ERROR_SUCCESS;

    Parent = (PDMKEY)hKey;

    //hold the shared lock
    ACQUIRE_SHARED_LOCK(gLockDmpRoot);

    //check if the key was deleted and invalidated
    if (ISKEYDELETED(Parent))
    {
        Status = ERROR_KEY_DELETED;
        goto FnExit;
    }
    //
    // Allocate the DMKEY structure.
    //
    NameLength = (lstrlenW(Parent->Name) + 1 + lstrlenW(lpSubKey) + 1)*sizeof(WCHAR);
    Key = LocalAlloc(LMEM_FIXED, sizeof(DMKEY)+NameLength);
    if (Key == NULL) {
        Status = ERROR_NOT_ENOUGH_MEMORY;
        CL_UNEXPECTED_ERROR(Status);
        goto FnExit;
    }

    //
    // Open the key on the local machine.
    //
    Status = RegOpenKeyEx(Parent->hKey,
                          lpSubKey,
                          0,
                          samDesired,
                          &Key->hKey);
    if (Status != ERROR_SUCCESS) {
        goto FnExit;
    }

    //
    // Create the key name. only append a trailing backslash
    // if a parent name and subkey are non-null
    //
    lstrcpyW(Key->Name, Parent->Name);
    if ((Key->Name[0] != UNICODE_NULL) && (lpSubKey[0] != UNICODE_NULL)) {
        lstrcatW(Key->Name, L"\\");
    }
    lstrcatW(Key->Name, lpSubKey);
    Key->GrantedAccess = samDesired;

    EnterCriticalSection(&KeyLock);
    InsertHeadList(&KeyList, &Key->ListEntry);
    InitializeListHead(&Key->NotifyList);
    LeaveCriticalSection(&KeyLock);

FnExit:
    RELEASE_LOCK(gLockDmpRoot);

    if (Status != ERROR_SUCCESS)
    {
        if (Key) LocalFree(Key);
        SetLastError(Status);
        return(NULL);
    }
    else
        return((HDMKEY)Key);


}



DWORD
DmEnumKey(
    IN HDMKEY hKey,
    IN DWORD dwIndex,
    OUT LPWSTR lpName,
    IN OUT LPDWORD lpcbName,
    OUT OPTIONAL PFILETIME lpLastWriteTime
    )

/*++

Routine Description:

    Enumerates the subkeys of a cluster registry key.

Arguments:

    hKey - Supplies the registry key for which the subkeys should
           be enumerated.

    dwIndex - Supplies the index to be enumerated.

    KeyName - Returns the name of the dwIndex subkey. The memory
           allocated for this buffer must be freed by the client.

    lpLastWriteTime - Returns the last write time.

Return Value:

    ERROR_SUCCESS if successful

    Win32 error code otherwise

--*/

{
    PDMKEY Key;
    DWORD  Status;
    FILETIME LastTime;

    Key = (PDMKEY)hKey;

    ACQUIRE_SHARED_LOCK(gLockDmpRoot);

    //check if the key was deleted and invalidated
    if (ISKEYDELETED(Key))
    {
        Status = ERROR_KEY_DELETED;
        goto FnExit;
    }
    Status = RegEnumKeyExW(Key->hKey,
                         dwIndex,
                         lpName,
                         lpcbName,
                         NULL,
                         NULL,
                         NULL,
                         &LastTime);

    if (lpLastWriteTime != NULL) {
        *lpLastWriteTime = LastTime;
    }

FnExit:
    RELEASE_LOCK(gLockDmpRoot);
    return(Status);
}


DWORD
DmSetValue(
    IN HDMKEY hKey,
    IN LPCWSTR lpValueName,
    IN DWORD dwType,
    IN CONST BYTE *lpData,
    IN DWORD cbData
    )

/*++

Routine Description:

    This routine sets the named value for the specified
    cluster registry key.

Arguments:

    hKey - Supplies the cluster registry subkey whose value is to be set

    lpValueName - Supplies the name of the value to be set.

    dwType - Supplies the value data type

    lpData - Supplies a pointer to the value data

    cbData - Supplies the length of the value data.

Return Value:

    ERROR_SUCCESS if successful

    Win32 error code otherwise

--*/

{

    DWORD Status= ERROR_SUCCESS;        //initialize to success
    PDMKEY Key;
    DWORD NameLength;
    DWORD ValueNameLength;
    DWORD UpdateLength;
    PDM_SET_VALUE_UPDATE Update;
    PUCHAR Dest;


    Key = (PDMKEY)hKey;

    if (ISKEYDELETED(Key))
        return(ERROR_KEY_DELETED);

    //
    // round lengths such that pointers to the data trailing the structure are
    // aligned on the architecture's natural boundary
    //
    NameLength = (lstrlenW(Key->Name)+1)*sizeof(WCHAR);
    NameLength = ROUND_UP_COUNT( NameLength, sizeof( DWORD_PTR ));

    ValueNameLength = (lstrlenW(lpValueName)+1)*sizeof(WCHAR);
    ValueNameLength = ROUND_UP_COUNT( ValueNameLength, sizeof( DWORD_PTR ));

    UpdateLength = sizeof(DM_SET_VALUE_UPDATE) +
                   NameLength +
                   ValueNameLength +
                   cbData;


    Update = (PDM_SET_VALUE_UPDATE)LocalAlloc(LMEM_FIXED, UpdateLength);
    if (Update == NULL) {
        CL_UNEXPECTED_ERROR(ERROR_NOT_ENOUGH_MEMORY);
        return(ERROR_NOT_ENOUGH_MEMORY);
    }


    Update->lpStatus = &Status;
    Update->NameOffset = FIELD_OFFSET(DM_SET_VALUE_UPDATE, KeyName)+NameLength;
    Update->DataOffset = Update->NameOffset + ValueNameLength;
    Update->DataLength = cbData;
    Update->Type = dwType;
    CopyMemory(Update->KeyName, Key->Name, NameLength);

    Dest = (PUCHAR)Update + Update->NameOffset;
    CopyMemory(Dest, lpValueName, ValueNameLength);

    Dest = (PUCHAR)Update + Update->DataOffset;
    CopyMemory(Dest, lpData, cbData);

    Status = GumSendUpdate(GumUpdateRegistry,
                  DmUpdateSetValue,
                  UpdateLength,
                  Update);


    LocalFree(Update);

    return(Status);

}


DWORD
DmDeleteValue(
    IN HDMKEY hKey,
    IN LPCWSTR lpValueName
    )

/*++

Routine Description:

    Removes the specified value from a given registry subkey

Arguments:

    hKey - Supplies the key whose value is to be deleted.

    lpValueName - Supplies the name of the value to be removed.

Return Value:

    If the function succeeds, the return value is ERROR_SUCCESS.

    If the function fails, the return value is an error value.

--*/

{
    PDMKEY Key;
    DWORD NameLength;
    DWORD ValueNameLength;
    DWORD UpdateLength;
    PDM_DELETE_VALUE_UPDATE Update;
    PUCHAR Dest;
    DWORD Status;

    Key = (PDMKEY)hKey;
    if (ISKEYDELETED(Key))
        return(ERROR_KEY_DELETED);


    //
    // round up length to align pointer to ValueName on natural architecture
    // boundary
    //
    NameLength = (lstrlenW(Key->Name)+1)*sizeof(WCHAR);
    NameLength = ROUND_UP_COUNT( NameLength, sizeof( DWORD_PTR ));

    ValueNameLength = (lstrlenW(lpValueName)+1)*sizeof(WCHAR);

    UpdateLength = sizeof(DM_DELETE_VALUE_UPDATE) +
                   NameLength +
                   ValueNameLength;

        Update = (PDM_DELETE_VALUE_UPDATE)LocalAlloc(LMEM_FIXED, UpdateLength);
    if (Update == NULL) {
        CL_UNEXPECTED_ERROR(ERROR_NOT_ENOUGH_MEMORY);
        return(ERROR_NOT_ENOUGH_MEMORY);
    }


    Update->lpStatus = &Status;
    Update->NameOffset = FIELD_OFFSET(DM_DELETE_VALUE_UPDATE, KeyName)+NameLength;

    CopyMemory(Update->KeyName, Key->Name, NameLength);

    Dest = (PUCHAR)Update + Update->NameOffset;
    CopyMemory(Dest, lpValueName, ValueNameLength);

    Status = GumSendUpdate(GumUpdateRegistry,
                  DmUpdateDeleteValue,
                  UpdateLength,
                  Update);
    LocalFree(Update);

    return(Status);
}


DWORD
DmQueryValue(
    IN HDMKEY hKey,
    IN LPCWSTR lpValueName,
    OUT LPDWORD lpType,
    OUT LPBYTE lpData,
    IN OUT LPDWORD lpcbData
    )

/*++

Routine Description:

    Queries a named value for the specified cluster registry subkey

Arguments:

    hKey - Supplies the subkey whose value should be queried

    lpValueName - Supplies the named value to be queried

    lpType - Returns the type of the value's data

    lpData - Returns the value's data

    lpcbData - Supplies the size (in bytes) of the lpData buffer
               Returns the number of bytes copied into the lpData buffer
               If lpData==NULL, cbData is set to the required buffer
               size and the function returns ERROR_SUCCESS

Return Value:

    ERROR_SUCCESS if successful

    Win32 error code otherwise

--*/

{
    PDMKEY  Key;
    DWORD   Status;

    Key = (PDMKEY)hKey;
    //check if the key was deleted and invalidated

    ACQUIRE_SHARED_LOCK(gLockDmpRoot);

    if (ISKEYDELETED(Key))
    {
        Status = ERROR_KEY_DELETED;
        goto FnExit;
    }

    Status = RegQueryValueEx(Key->hKey,
                           lpValueName,
                           NULL,
                           lpType,
                           lpData,
                           lpcbData);
FnExit:
    RELEASE_LOCK(gLockDmpRoot);
    return(Status);
}


DWORD
DmQueryDword(
    IN  HDMKEY  hKey,
    IN  LPCWSTR lpValueName,
    OUT LPDWORD lpValue,
    IN  LPDWORD lpDefaultValue OPTIONAL
    )

/*++

Routine Description:

    Reads a REG_DWORD registry value. If the value is not present, then
    default to the value supplied in lpDefaultValue (if present).

Arguments:

    hKey        - Open key for the value to be read.

    lpValueName - Unicode name of the value to be read.

    lpValue     - Pointer to the DWORD into which to read the value.

    lpDefaultValue - Optional pointer to a DWORD to use as a default value.

Return Value:

    ERROR_SUCCESS if successful

    Win32 error code otherwise

--*/

{
    PDMKEY  Key;
    DWORD   Status;
    DWORD   ValueType;
    DWORD   ValueSize = sizeof(DWORD);

    Key = (PDMKEY)hKey;

    ACQUIRE_SHARED_LOCK(gLockDmpRoot);

    //make sure the key wasnt deleted/invalidated/reopened while we had a
    //handle open to it
    if (ISKEYDELETED(Key))
    {
        Status = ERROR_KEY_DELETED;
        goto FnExit;
    }
    Status = RegQueryValueEx(Key->hKey,
                             lpValueName,
                             NULL,
                             &ValueType,
                             (LPBYTE)lpValue,
                             &ValueSize);

    if ( Status == ERROR_SUCCESS ) {
        if ( ValueType != REG_DWORD ) {
            Status = ERROR_INVALID_PARAMETER;
        }
    } else {
        if ( ARGUMENT_PRESENT( lpDefaultValue ) ) {
            *lpValue = *lpDefaultValue;
            Status = ERROR_SUCCESS;
        }
    }

FnExit:
    RELEASE_LOCK(gLockDmpRoot);
    return(Status);

} // DmQueryDword


DWORD
DmQueryString(
    IN     HDMKEY   Key,
    IN     LPCWSTR  ValueName,
    IN     DWORD    ValueType,
    IN     LPWSTR  *StringBuffer,
    IN OUT LPDWORD  StringBufferSize,
    OUT    LPDWORD  StringSize
    )

/*++

Routine Description:

    Reads a REG_SZ or REG_MULTI_SZ registry value. If the StringBuffer is
    not large enough to hold the data, it is reallocated.

Arguments:

    Key              - Open key for the value to be read.

    ValueName        - Unicode name of the value to be read.

    ValueType        - REG_SZ or REG_MULTI_SZ.

    StringBuffer     - Buffer into which to place the value data.

    StringBufferSize - Pointer to the size of the StringBuffer. This parameter
                       is updated if StringBuffer is reallocated.

    StringSize       - The size of the data returned in StringBuffer, including
                       the terminating null character.

Return Value:

    The status of the registry query.

--*/
{
    DWORD    status;
    DWORD    valueType;
    WCHAR   *temp;
    DWORD    oldBufferSize = *StringBufferSize;
    BOOL     noBuffer = FALSE;


    if (*StringBufferSize == 0) {
        noBuffer = TRUE;
    }

    *StringSize = *StringBufferSize;

    status = DmQueryValue( Key,
                           ValueName,
                           &valueType,
                           (LPBYTE) *StringBuffer,
                           StringSize
                         );

    if (status == NO_ERROR) {
        if (!noBuffer ) {
            if (valueType == ValueType) {
                return(NO_ERROR);
            }
            else {
                return(ERROR_INVALID_PARAMETER);
            }
        }

        if (*StringSize) status = ERROR_MORE_DATA;
    }

    if (status == ERROR_MORE_DATA) {
        temp = LocalAlloc(LMEM_FIXED, *StringSize);

        if (temp == NULL) {
            *StringSize = 0;
            return(ERROR_NOT_ENOUGH_MEMORY);
        }

        if (!noBuffer) {
            LocalFree(*StringBuffer);
        }

        *StringBuffer = temp;
        *StringBufferSize = *StringSize;

        status = DmQueryValue( Key,
                               ValueName,
                               &valueType,
                               (LPBYTE) *StringBuffer,
                               StringSize
                             );

        if (status == NO_ERROR) {
            if (valueType == ValueType) {
                return(NO_ERROR);
            }
            else {
                *StringSize = 0;
                return(ERROR_INVALID_PARAMETER);
            }
        }
    }

    return(status);

} // DmQueryString


VOID
DmEnumKeys(
    IN HDMKEY RootKey,
    IN PENUM_KEY_CALLBACK Callback,
    IN PVOID Context
    )

/*++

 Routine Description:

     Enumerates the subkeys of the given registry key. For each
     subkey, a string is allocated to hold the subkey name and
     the subkey is opened. The specified callback function is
     called and passed the subkey handle and subkey name.

     The callback function is responsible for closing the subkey
     handle and freeing the subkey name.

 Arguments:

    RootKey - Supplies a handle to the key whose subkeys are to
              be enumerated.

    Callback - Supplies the callback routine.

    Context - Supplies an arbitrary context to be passed to the
              callback routine.

 Return Value:

    None.

--*/
{
    PWSTR KeyName;
    HDMKEY SubKey;
    DWORD Index;
    DWORD Status;
    FILETIME FileTime;
    PWSTR NameBuf;
    DWORD NameBufSize;
    DWORD OrigNameBufSize;

    //
    // Find the length of the longest subkey name.
    //
    Status = DmQueryInfoKey(RootKey,
                            NULL,
                            &NameBufSize,
                            NULL,
                            NULL,
                            NULL,
                            NULL,
                            NULL);
    if (Status != ERROR_SUCCESS) {
        CL_UNEXPECTED_ERROR(Status);
        return;
    }

    NameBufSize = (NameBufSize + 1)*sizeof(WCHAR);
    NameBuf = LocalAlloc(LMEM_FIXED, NameBufSize);
    if (NameBuf == NULL) {
        CL_UNEXPECTED_ERROR(ERROR_NOT_ENOUGH_MEMORY);
    }
    OrigNameBufSize = NameBufSize;

    //
    // Enumerate the subkeys
    //
    Index = 0;
    do {
        NameBufSize = OrigNameBufSize;
        Status = DmEnumKey( RootKey,
                            Index,
                            NameBuf,
                            &NameBufSize,
                            NULL);

        if (Status == ERROR_SUCCESS) {
            KeyName = LocalAlloc(LMEM_FIXED, (wcslen(NameBuf)+1)*sizeof(WCHAR));
            if (KeyName != NULL) {

                wcscpy(KeyName, NameBuf);

                //
                // Open the key
                //
                SubKey = DmOpenKey( RootKey,
                                    KeyName,
                                    MAXIMUM_ALLOWED);
                if (SubKey == NULL) {
                    Status = GetLastError();
                    CL_UNEXPECTED_ERROR(Status);
                    LocalFree(KeyName);
                } else {
                    (Callback)(SubKey,
                               KeyName,
                               Context);
                }

            } else {
                CL_UNEXPECTED_ERROR(ERROR_NOT_ENOUGH_MEMORY);
            }
        }

        Index++;
    } while ( Status == ERROR_SUCCESS );

    LocalFree(NameBuf);

} // DmEnumKeys


VOID
DmEnumValues(
    IN HDMKEY RootKey,
    IN PENUM_VALUE_CALLBACK Callback,
    IN PVOID Context
    )

/*++

 Routine Description:

     Enumerates the values of the given registry key. For each
     value, a string is allocated to hold the value name and a
     buffer is allocated to hold its data. The specified callback
     function is called and passed the value name and data.

     The callback function must not free either the value name
     or its buffer. If it needs this data after the callback
     returns, it must copy it.

 Arguments:

    RootKey - Supplies a handle to the key whose values are to
              be enumerated.

    Callback - Supplies the callback routine.

    Context - Supplies an arbitrary context to be passed to the
              callback routine.

 Return Value:

    None.

--*/
{
    DWORD Index;
    DWORD Status;
    PWSTR NameBuf;
    DWORD NameBufSize;
    DWORD ValueCount;
    DWORD MaxValueLen;
    DWORD MaxNameLen;
    PVOID ValueBuf;
    DWORD cbName;
    DWORD cbData;
    DWORD dwType;
    BOOL Continue;

    //
    // Find the length of the longest value name and data.
    //
    Status = DmQueryInfoKey(RootKey,
                            NULL,
                            NULL,
                            &ValueCount,
                            &MaxNameLen,
                            &MaxValueLen,
                            NULL,
                            NULL);
    if (Status != ERROR_SUCCESS) {
        CL_UNEXPECTED_ERROR(Status);
        return;
    }

    NameBuf = CsAlloc((MaxNameLen+1)*sizeof(WCHAR));
    ValueBuf = CsAlloc(MaxValueLen);

    //
    // Enumerate the values
    //
    for (Index=0; Index<ValueCount; Index++) {
        cbName = MaxNameLen+1;
        cbData = MaxValueLen;
        Status = DmEnumValue(RootKey,
                             Index,
                             NameBuf,
                             &cbName,
                             &dwType,
                             ValueBuf,
                             &cbData);
        if (Status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_CRITICAL,
                       "[DM] DmEnumValue for index %1!d! of key %2!ws! failed %3!d!\n",
                       Index,
                       ((PDMKEY)(RootKey))->Name,
                       Status);
        } else {
            Continue = (Callback)(NameBuf,
                                  ValueBuf,
                                  dwType,
                                  cbData,
                                  Context);
            if (!Continue) {
                break;
            }
        }
    }

    CsFree(NameBuf);
    CsFree(ValueBuf);

} // DmEnumValues


DWORD
DmQueryInfoKey(
    IN  HDMKEY  hKey,
    OUT LPDWORD SubKeys,
    OUT LPDWORD MaxSubKeyLen,
    OUT LPDWORD Values,
    OUT LPDWORD MaxValueNameLen,
    OUT LPDWORD MaxValueLen,
    OUT LPDWORD lpcbSecurityDescriptor,
    OUT PFILETIME FileTime
    )

/*++

Routine Description:

Arguments:

Return Value:

    ERROR_SUCCESS if successful

    Win32 error code otherwise

--*/

{
    PDMKEY  Key;
    DWORD   Ignored;
    DWORD   Status;

    Key = (PDMKEY)hKey;  

    ACQUIRE_SHARED_LOCK(gLockDmpRoot);

    //make sure the key wasnt deleted/invalidated/reopened while we had a
    //handle open to it
    if (ISKEYDELETED(Key))
    {
        Status = ERROR_KEY_DELETED;
        goto FnExit;
    }

    Status = RegQueryInfoKeyW( Key->hKey,
                             NULL,
                             &Ignored,
                             NULL,
                             SubKeys,
                             MaxSubKeyLen,
                             &Ignored,
                             Values,
                             MaxValueNameLen,
                             MaxValueLen,
                             lpcbSecurityDescriptor,
                             FileTime);

FnExit:
    RELEASE_LOCK(gLockDmpRoot);
    return(Status);

} // DmQueryInfoKey


DWORD
DmDeleteKey(
    IN HDMKEY hKey,
    IN LPCWSTR lpSubKey
    )

/*++

Routine Description:

    Deletes the specified key. A key that has subkeys cannot
    be deleted.

Arguments:

    hKey - Supplies a handle to a currently open key.

    lpSubKey - Points to a null-terminated string specifying the
        name of the key to delete. This parameter cannot be NULL,
        and the specified key must not have subkeys.

Return Value:

    If the function succeeds, the return value is ERROR_SUCCESS.

    If the function fails, the return value is an error value.

--*/

{
    PDMKEY                      Key;
    DWORD                       NameLength;
    DWORD                       UpdateLength;
    PDM_DELETE_KEY_UPDATE Update;
    DWORD                       Status;

    Key = (PDMKEY)hKey;

    //make sure the key wasnt deleted/invalidated/reopened while we had a
    //handle open to it
    if (ISKEYDELETED(Key))
        return(ERROR_KEY_DELETED);

    NameLength = (lstrlenW(Key->Name) + 1 + lstrlenW(lpSubKey) + 1)*sizeof(WCHAR);
        UpdateLength = NameLength + sizeof(DM_DELETE_KEY_UPDATE);

    Update = (PDM_DELETE_KEY_UPDATE)LocalAlloc(LMEM_FIXED, UpdateLength);

    if (Update == NULL) {
        CL_UNEXPECTED_ERROR(ERROR_NOT_ENOUGH_MEMORY);
        return(ERROR_NOT_ENOUGH_MEMORY);
    }


    Update->lpStatus = &Status;
    CopyMemory(Update->Name, Key->Name, (lstrlenW(Key->Name) + 1) * sizeof(WCHAR));
    if (Update->Name[0] != '\0') {
        lstrcatW(Update->Name, L"\\");
    }
    lstrcatW(Update->Name, lpSubKey);

    Status = GumSendUpdate(GumUpdateRegistry,
                  DmUpdateDeleteKey,
                  sizeof(DM_DELETE_KEY_UPDATE)+NameLength,
                  Update);

    LocalFree(Update);
    return(Status);
}


DWORD
DmDeleteTree(
    IN HDMKEY hKey,
    IN LPCWSTR lpSubKey
    )
/*++

Routine Description:

    Deletes the specified registry subtree. All subkeys are
    deleted.

Arguments:

    hKey - Supplies a handle to a currently open key.

    lpSubKey - Points to a null-terminated string specifying the
        name of the key to delete. This parameter cannot be NULL.
        Any subkeys of the specified key will also be deleted.

Return Value:

    If the function succeeds, the return value is ERROR_SUCCESS.

    If the function fails, the return value is an error value.

--*/

{
    HDMKEY Subkey;
    DWORD i;
    DWORD Status;
    LPWSTR KeyBuffer=NULL;
    DWORD MaxKeyLen;
    DWORD NeededSize;

    Subkey = DmOpenKey(hKey,
                       lpSubKey,
                       MAXIMUM_ALLOWED);
    if (Subkey == NULL) {
        Status = GetLastError();
        return(Status);
    }

    //
    // Get the size of name buffer we will need.
    //
    Status = DmQueryInfoKey(Subkey,
                            NULL,
                            &MaxKeyLen,
                            NULL,
                            NULL,
                            NULL,
                            NULL,
                            NULL);
    if (Status != ERROR_SUCCESS) {
        CL_UNEXPECTED_ERROR( Status );
        DmCloseKey(Subkey);
        return(Status);
    }
    KeyBuffer = LocalAlloc(LMEM_FIXED, (MaxKeyLen+1)*sizeof(WCHAR));
    if (KeyBuffer == NULL) {
        CL_UNEXPECTED_ERROR( ERROR_NOT_ENOUGH_MEMORY );
        DmCloseKey(Subkey);
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    //
    // Enumerate the subkeys and apply ourselves recursively to each one.
    //
    i=0;
    do {
        NeededSize = MaxKeyLen+1;
        Status = DmEnumKey(Subkey,
                           i,
                           KeyBuffer,
                           &NeededSize,
                           NULL);
        if (Status == ERROR_SUCCESS) {
            //
            // Call ourselves recursively on this keyname.
            //
            DmDeleteTree(Subkey, KeyBuffer);

        } else {
            //
            // Some odd error, keep going with the next key.
            //
            ++i;
        }

    } while ( Status != ERROR_NO_MORE_ITEMS );

    DmCloseKey(Subkey);

    Status = DmDeleteKey(hKey, lpSubKey);

    if (KeyBuffer != NULL) {
        LocalFree(KeyBuffer);
    }
    return(Status);
}


DWORD
DmEnumValue(
    IN HDMKEY hKey,
    IN DWORD dwIndex,
    OUT LPWSTR lpValueName,
    IN OUT LPDWORD lpcbValueName,
    OUT LPDWORD lpType,
    OUT LPBYTE lpData,
    IN OUT LPDWORD lpcbData
    )

/*++

Routine Description:

    Enumerates the specified value of a registry subkey

Arguments:

    hKey - Supplies the registry key handle

    dwIndex - Supplies the index of the value to be enumerated

    lpValueName - Points to a buffer that receives the name of the value,
        including the terminating null character

    lpcbValueName - Points to a variable that specifies the size, in characters,
        of the buffer pointed to by the lpValueName parameter. This size should
        include the terminating null character. When the function returns, the
        variable pointed to by lpcbValueName contains the number of characters
        stored in the buffer. The count returned does not include the terminating
        null character.

    lpType - Returns the value data type

    lpData - Points to a buffer that receives the data for the value entry. This
        parameter can be NULL if the data is not required.

    lpcbData - Points to a variable that specifies the size, in bytes, of the
        buffer pointed to by the lpData parameter. When the function returns, the
        variable pointed to by the lpcbData parameter contains the number of bytes
        stored in the buffer. This parameter can be NULL, only if lpData is NULL.

Return Value:

    If the function succeeds, the return value is ERROR_SUCCESS.

    If the function fails, the return value is an error value.

--*/

{
    PDMKEY  Key;
    DWORD   Status;
    DWORD   cbValueName = *lpcbValueName;

    Key = (PDMKEY)hKey;

    ACQUIRE_SHARED_LOCK(gLockDmpRoot);

    //make sure the key wasnt deleted/invalidated/reopened while we had a
    //handle open to it
    if (ISKEYDELETED(Key))
    {
        Status = ERROR_KEY_DELETED;
        goto FnExit;
    }


    Status = RegEnumValueW(Key->hKey,
                         dwIndex,
                         lpValueName,
                         lpcbValueName,
                         NULL,
                         lpType,
                         lpData,
                         lpcbData);

    //
    //  The following code is to mask the registry behavior by which RegEnumValue does not necessarily 
    //  fill in lpValueName (even though we specify a large enough buffer) when lpData buffer is a 
    //  valid buffer but a little too small.
    //
    if ( Status == ERROR_MORE_DATA )
    {
        DWORD       dwError;
        
        dwError = RegEnumValueW( Key->hKey,
                                 dwIndex,
                                 lpValueName,
                                 &cbValueName,
                                 NULL,
                                 NULL,
                                 NULL,
                                 NULL );

        if ( ( dwError != ERROR_SUCCESS ) && 
             ( dwError != ERROR_MORE_DATA ) )  
        {
            Status = dwError;
        } else
        {
            *lpcbValueName = cbValueName;
        }
    }

FnExit:
    RELEASE_LOCK(gLockDmpRoot);
    return(Status);


}


DWORD
DmAppendToMultiSz(
    IN HDMKEY hKey,
    IN LPCWSTR lpValueName,
    IN LPCWSTR lpString
    )

/*++

Routine Description:

    Adds another string to a REG_MULTI_SZ value. If the value does
    not exist, it will be created.

Arguments:

    hKey - Supplies the key where the value exists. This key must
           have been opened with KEY_READ | KEY_SET_VALUE access

    lpValueName - Supplies the name of the value.

    lpString - Supplies the string to be appended to the REG_MULTI_SZ value

Return Value:

    ERROR_SUCCESS if successful

    Win32 error code otherwise

--*/

{
    DWORD ValueLength = 512;
    DWORD ReturnedLength;
    LPWSTR ValueData;
    DWORD StringLength;
    DWORD Status;
    DWORD cbValueData;
    PWSTR s;
    DWORD Type;

    StringLength = (lstrlenW(lpString)+1)*sizeof(WCHAR);
retry:
    ValueData = LocalAlloc(LMEM_FIXED, ValueLength + StringLength);
    if (ValueData == NULL) {
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    cbValueData = ValueLength;
    Status = DmQueryValue(hKey,
                          lpValueName,
                          &Type,
                          (LPBYTE)ValueData,
                          &cbValueData);
    if (Status == ERROR_MORE_DATA) {
        //
        // The existing value is too large for our buffer.
        // Retry with a larger buffer.
        //
        ValueLength = cbValueData;
        LocalFree(ValueData);
        goto retry;
    }
    if (Status == ERROR_FILE_NOT_FOUND) {
        //
        // The value does not currently exist. Create the
        // value with our data.
        //
        s = ValueData;

    } else if (Status == ERROR_SUCCESS) {
        //
        // A value already exists. Append our string to the
        // MULTI_SZ.
        //
        s = (PWSTR)((PCHAR)ValueData + cbValueData) - 1;
    } else {
        LocalFree(ValueData);
        return(Status);
    }

    CopyMemory(s, lpString, StringLength);
    s += (StringLength / sizeof(WCHAR));
    *s++ = L'\0';

    Status = DmSetValue(hKey,
                        lpValueName,
                        REG_MULTI_SZ,
                        (CONST BYTE *)ValueData,
                        (DWORD)((s-ValueData)*sizeof(WCHAR)));
    LocalFree(ValueData);

    return(Status);
}


DWORD
DmRemoveFromMultiSz(
    IN HDMKEY hKey,
    IN LPCWSTR lpValueName,
    IN LPCWSTR lpString
    )
/*++

Routine Description:

    Removes a string from a REG_MULTI_SZ value.

Arguments:

    hKey - Supplies the key where the value exists. This key must
           have been opened with READ | KEY_SET_VALUE access

    lpValueName - Supplies the name of the value.

    lpString - Supplies the string to be removed from the REG_MULTI_SZ value

Return Value:

    ERROR_SUCCESS if successful

    Win32 error code otherwise

--*/

{
    DWORD Status;
    LPWSTR Buffer=NULL;
    DWORD BufferSize;
    DWORD DataSize;
    LPWSTR Current;
    DWORD CurrentLength;
    DWORD i;
    LPWSTR Next;
    PCHAR Src, Dest;
    DWORD NextLength;
    DWORD MultiLength;


    BufferSize = 0;
    Status = DmQueryString(hKey,
                           lpValueName,
                           REG_MULTI_SZ,
                           &Buffer,
                           &BufferSize,
                           &DataSize);
    if (Status != ERROR_SUCCESS) {
        goto FnExit;
    }

    MultiLength = DataSize/sizeof(WCHAR);
    Status = ClRtlMultiSzRemove(Buffer,
                                &MultiLength,
                                lpString);
    if (Status == ERROR_SUCCESS) {
        //
        // Set the new value back.
        //
        Status = DmSetValue(hKey,
                            lpValueName,
                            REG_MULTI_SZ,
                            (CONST BYTE *)Buffer,
                            MultiLength * sizeof(WCHAR));

    } else if (Status == ERROR_FILE_NOT_FOUND) {
        Status = ERROR_SUCCESS;
    }

FnExit:
    if (Buffer) LocalFree(Buffer);
    return(Status);
}


DWORD
DmGetKeySecurity(
    IN HDMKEY hKey,
    IN SECURITY_INFORMATION RequestedInformation,
    OUT PSECURITY_DESCRIPTOR pSecurityDescriptor,
    IN LPDWORD lpcbSecurityDescriptor
    )
/*++

Routine Description:

    Retrieves a copy of the security descriptor protecting
    the specified cluster registry key.

Arguments:

    hKey - Supplies the handle of the key

    RequestedInformation - Specifies a SECURITY_INFORMATION structure that
        indicates the requested security information.

    pSecurityDescriptor - Points to a buffer that receives a copy of the
        requested security descriptor.

    lpcbSecurityDescriptor - Points to a variable that specifies the size,
        in bytes, of the buffer pointed to by the pSecurityDescriptor parameter.
        When the function returns, the variable contains the number of bytes
        written to the buffer.


Return Value:

    ERROR_SUCCESS if successful

    Win32 error code otherwise

--*/

{
    DWORD Status;
    PDMKEY Key = (PDMKEY)hKey;

    ACQUIRE_SHARED_LOCK(gLockDmpRoot);

    //make sure the key wasnt deleted/invalidated/reopened while we had a
    //handle open to it
    if (ISKEYDELETED(Key))
    {
        Status = ERROR_KEY_DELETED;
        goto FnExit;
    }

    Status = RegGetKeySecurity(Key->hKey,
                               RequestedInformation,
                               pSecurityDescriptor,
                               lpcbSecurityDescriptor);

FnExit:
    RELEASE_LOCK(gLockDmpRoot);
    return(Status);
}


DWORD
DmSetKeySecurity(
    IN HDMKEY hKey,
    IN SECURITY_INFORMATION SecurityInformation,
    IN PSECURITY_DESCRIPTOR pSecurityDescriptor
    )
/*++

Routine Description:

    Sets the security on the specified registry key.

Arguments:

    hKey - Supplies a handle to a currently open key.

    SecurityInformation - Supplies the type of security information to
        be set.

    pRpcSecurityDescriptor - Supplies the security information

Return Value:

    ERROR_SUCCESS if successful

    Win32 error code otherwise

--*/

{
    DWORD Status;
    PDMKEY Key = (PDMKEY)hKey;

    //make sure the key wasnt deleted/invalidated/reopened while we had a
    //handle open to it

    if (ISKEYDELETED(Key))
        return(ERROR_KEY_DELETED);

    Status = GumSendUpdateEx(GumUpdateRegistry,
                             DmUpdateSetSecurity,
                             4,
                             sizeof(SecurityInformation),
                             &SecurityInformation,
                             (lstrlenW(Key->Name)+1)*sizeof(WCHAR),
                             Key->Name,
                             GetSecurityDescriptorLength(pSecurityDescriptor),
                             pSecurityDescriptor,
                             sizeof(Key->GrantedAccess),
                             &Key->GrantedAccess);

    return(Status);
}



DWORD
DmCommitRegistry(
    VOID
    )
/*++

Routine Description:

    Flushes the registry to disk, producing a new persistent cluster registry state.

Arguments:

    None

Return Value:

    ERROR_SUCCESS if successful

    Win32 error code otherwise

--*/

{
    DWORD Status;

    ACQUIRE_SHARED_LOCK(gLockDmpRoot);

    Status = RegFlushKey(DmpRoot);

    RELEASE_LOCK(gLockDmpRoot);

    if (Status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL,
                   "[DM] DmCommitRegistry failed to flush dirty data %1!d!\n",
                   Status);
    }
    return(Status);
}


DWORD
DmRollbackRegistry(
    VOID
    )
/*++

Routine Description:

    Rolls the registry back to the last previously committed state.

Arguments:

    None

Return Value:

    ERROR_SUCCESS if successful

    Win32 error code otherwise

--*/

{
    DWORD       Status;
    BOOLEAN     WasEnabled;

    ACQUIRE_EXCLUSIVE_LOCK(gLockDmpRoot);
    //hold the key lock as well
    EnterCriticalSection(&KeyLock);


    Status = ClRtlEnableThreadPrivilege(SE_RESTORE_PRIVILEGE,
                                &WasEnabled);

    if (Status != ERROR_SUCCESS)
    {
        ClRtlLogPrint(LOG_CRITICAL,
               "[DM] DmRollbackRegistry failed to restore privilege %1!d!\n",
               Status);
        goto FnExit;
    }

    //
    // Restart the registry watcher thread so it is not trying to use
    // DmpRoot while we are messing with things.
    //
    DmpRestartFlusher();

    //
    // Close any open handles
    //
    DmpInvalidateKeys();


    Status = NtRestoreKey(DmpRoot,
                              NULL,
                              REG_REFRESH_HIVE);

    ClRtlRestoreThreadPrivilege(SE_RESTORE_PRIVILEGE,
                       WasEnabled);

    if (Status != ERROR_SUCCESS)
    {
        ClRtlLogPrint(LOG_CRITICAL,
               "[DM] DmRollbackRegistry: NtRestoreKey failed %1!d!\n",
               Status);
        goto FnExit;
    }

    //
    // Reopen handles
    //
    RegCloseKey(DmpRoot);
    Status = RegOpenKeyW(HKEY_LOCAL_MACHINE,
                         DmpClusterParametersKeyName,
                         &DmpRoot);
    if (Status != ERROR_SUCCESS)
    {
        ClRtlLogPrint(LOG_CRITICAL,
                   "[DM] DmRollbackRegistry failed to reopen DmpRoot %1!d!\n",
                   Status);
        goto FnExit;
    }
    DmpReopenKeys();

FnExit:
    //release the locks
    LeaveCriticalSection(&KeyLock);
    RELEASE_LOCK(gLockDmpRoot);
    if (Status != ERROR_SUCCESS)
    {
        ClRtlLogPrint(LOG_CRITICAL,
                   "[DM] DmRollbackRegistry failed to flush dirty data %1!d!\n",
                   Status);
    }

    return(Status);
}


DWORD
DmRtlCreateKey(
    IN HDMKEY hKey,
    IN LPCWSTR lpSubKey,
    IN DWORD dwOptions,
    IN DWORD samDesired,
    IN OPTIONAL LPVOID lpSecurityDescriptor,
    OUT HDMKEY *  phkResult,
    OUT LPDWORD lpDisposition
    )

/*++

Routine Description:
    Wrapper function for DmCreateKey. Its definition corresponds to
    ClusterRegCreateKey.  This should be used instead of DmCreateKey
    when passing to ClRtl* funtions.
--*/
{
    DWORD status;
    
    *phkResult = DmCreateKey(
                        hKey,
                        lpSubKey,
                        dwOptions,
                        samDesired,
                        lpSecurityDescriptor,
                        lpDisposition
                   );
    if (*phkResult == NULL)
        status=GetLastError();
    else 
        status = ERROR_SUCCESS;    
    return status;
 }
                    


DWORD
DmRtlOpenKey(
    IN HDMKEY hKey,
    IN LPCWSTR lpSubKey,
    IN DWORD samDesired,
    OUT HDMKEY * phkResult
    )

/*++

Routine Description:
    Wrapper function for DmOpenKey. Its definition corresponds to
    ClusterRegOpenKey.  This should be used instead of DmOpenKey when 
    passing to ClRtl* funtions. See DmOpenKey for argument description
--*/
{    
    DWORD   status;

    *phkResult = DmOpenKey(
                    hKey,
                    lpSubKey,
                    samDesired
                    );
    if (*phkResult == NULL)
        status=GetLastError();
    else
        status=ERROR_SUCCESS;
    return status;
}